From 212c776db410aee5007b1fa8e38ab5abb025cf97 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Sat, 15 Feb 2020 14:35:22 -0500 Subject: [PATCH 1/3] Add Bake Image Alpha operator. --- korman/operators/op_image.py | 79 ++++++++++++++++++++++++++++++++++-- korman/ui/ui_menus.py | 27 +++++++++--- 2 files changed, 98 insertions(+), 8 deletions(-) diff --git a/korman/operators/op_image.py b/korman/operators/op_image.py index 09ba99b..1d9450e 100644 --- a/korman/operators/op_image.py +++ b/korman/operators/op_image.py @@ -33,13 +33,82 @@ _CUBE_FACES = { "frontFace": "FR", } -class ImageOperator: +class PlasmaBakeImageAlphaOperator(bpy.types.Operator): + bl_idname = "image.plasma_bake_image_alpha" + bl_label = "Bake Image Alpha" + bl_description = "Bake an image's calculated alpha to another image's alpha channel" + + filepath = StringProperty(name="Alpha Image", + subtype="FILE_PATH", + options=set()) + + def execute(self, context): + with ConsoleToggler(True), ExportProgressLogger() as self._report: + try: + self._execute(context) + except ExportError as error: + self.report({"ERROR"}, str(error)) + return {"CANCELLED"} + else: + return {"FINISHED"} + + def _execute(self, context): + self._report.progress_add_step("Preparing Images") + self._report.progress_add_step("Baking Alpha") + self._report.progress_add_step("Creating Image") + self._report.progress_start("BAKING IMAGE ALPHA") + if not Path(self.filepath).is_file(): + raise ExportError("No image found at '{}'".format(self.filepath)) + + self._report.progress_advance() + images, im_diffuse = bpy.data.images, context.space_data.image + with TemporaryObject(images.load(self.filepath), images.remove) as im_alpha: + width, height = self._check_image_scale(im_diffuse, im_alpha) + + self._report.progress_advance() + pixels = self._calc_alpha(im_diffuse, im_alpha, width, height) + + self._report.progress_advance() + self._make_image(im_diffuse.name, self.filepath, width, height, pixels, context) + self._report.progress_end() + + def _check_image_scale(self, im_diffuse, im_alpha): + sz_diffuse, sz_alpha = tuple(im_diffuse.size), tuple(im_alpha.size) + if tuple(im_diffuse.size) != tuple(im_alpha.size): + self._report.warn("Image sizes do not match, rescaling alpha image") + im_alpha.scale(*sz_diffuse) + return sz_diffuse + + def _calc_alpha(self, im_diffuse, im_alpha, width, height): + # DO NOT REMOVE this copying... Trying to operate directly on the bpy_prop_collection + # is slower than molasses running uphill in Siberia! + pixels_diffuse, pixels_alpha = list(im_diffuse.pixels), tuple(im_alpha.pixels) + for i in range(width * height): + alpha_idx = i * 4 + pixels_diffuse[alpha_idx + 3] = sum(pixels_alpha[alpha_idx:alpha_idx+2]) / 3.0 + return pixels_diffuse + + def _make_image(self, name_diffuse, name_alpha, width, height, pixels, context=None): + name = "{}_ALPHAGEN_{}.png".format(Path(name_diffuse).stem, Path(name_alpha).stem) + im_dest = bpy.data.images.new(name, width, height, True) + im_dest.pixels = pixels + im_dest.update() + im_dest.pack(as_png=True) + if context and context.space_data: + context.space_data.image = im_dest + return im_dest + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + @classmethod def poll(cls, context): - return context.scene.render.engine == "PLASMA_GAME" + space = context.space_data + return context.scene.render.engine == "PLASMA_GAME" and space and space.image -class PlasmaBuildCubeMapOperator(ImageOperator, bpy.types.Operator): +class PlasmaBuildCubeMapOperator(bpy.types.Operator): bl_idname = "image.plasma_build_cube_map" bl_label = "Build Cubemap" bl_description = "Builds a Blender cubemap from six images" @@ -207,6 +276,10 @@ class PlasmaBuildCubeMapOperator(ImageOperator, bpy.types.Operator): with GLTexture(image=blimage, fast=True) as glimage: return glimage.image_data + @classmethod + def poll(cls, context): + return context.scene.render.engine == "PLASMA_GAME" + def _scale_images(self, face_widths, face_heights, face_data): self._report.progress_advance() self._report.progress_range = len(BLENDER_CUBE_MAP) diff --git a/korman/ui/ui_menus.py b/korman/ui/ui_menus.py index 1f51912..e9ee128 100644 --- a/korman/ui/ui_menus.py +++ b/korman/ui/ui_menus.py @@ -13,7 +13,8 @@ # You should have received a copy of the GNU General Public License # along with Korman. If not, see . -from ..operators.op_mesh import * +import bpy +import functools class PlasmaMenu: @classmethod @@ -32,14 +33,30 @@ class PlasmaAddMenu(PlasmaMenu, bpy.types.Menu): layout.operator("mesh.plasma_ladder_add", text="Ladder", icon="COLLAPSEMENU") -def build_plasma_menu(self, context): +class PlasmaImageMenu(PlasmaMenu, bpy.types.Menu): + bl_idname = "menu.plasma_image" + bl_label = "Plasma" + bl_description = "Plasma Image Operators" + + def draw(self, context): + layout = self.layout + + layout.operator("image.plasma_bake_image_alpha", icon="IMAGE_RGB_ALPHA") + + +def _build_plasma_menu(menu_operator, self, context): if context.scene.render.engine != "PLASMA_GAME": return self.layout.separator() - self.layout.menu("menu.plasma_add", icon="URL") + self.layout.menu(menu_operator, icon="URL") + +build_plasma_add_menu = functools.partial(_build_plasma_menu, "menu.plasma_add") +build_plasma_image_menu = functools.partial(_build_plasma_menu, "menu.plasma_image") def register(): - bpy.types.INFO_MT_add.append(build_plasma_menu) + bpy.types.INFO_MT_add.append(build_plasma_add_menu) + bpy.types.IMAGE_MT_image.append(build_plasma_image_menu) def unregister(): - bpy.types.INFO_MT_add.remove(build_plasma_menu) + bpy.types.INFO_MT_add.remove(build_plasma_add_menu) + bpy.types.IMAGE_MT_image.remove(build_plasma_image_menu) From b98d315115cb28755dbbe6e2b5e30cd8c9cb0f59 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Sat, 15 Feb 2020 18:45:01 -0500 Subject: [PATCH 2/3] Add cubemap builder to Plasma Image menu. --- korman/operators/op_image.py | 13 +++++-------- korman/ui/ui_menus.py | 1 + 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/korman/operators/op_image.py b/korman/operators/op_image.py index 1d9450e..93925c3 100644 --- a/korman/operators/op_image.py +++ b/korman/operators/op_image.py @@ -127,15 +127,8 @@ class PlasmaBuildCubeMapOperator(bpy.types.Operator): default="", options={"HIDDEN"}) - def __init__(self): - self._report = ExportProgressLogger() - self._report.progress_add_step("Finding Face Images") - self._report.progress_add_step("Loading Face Images") - self._report.progress_add_step("Scaling Face Images") - self._report.progress_add_step("Generating Cube Map") - def execute(self, context): - with ConsoleToggler(True) as _: + with ConsoleToggler(True), ExportProgressLogger() as self._report: try: self._execute() except ExportError as error: @@ -145,6 +138,10 @@ class PlasmaBuildCubeMapOperator(bpy.types.Operator): return {"FINISHED"} def _execute(self): + self._report.progress_add_step("Finding Face Images") + self._report.progress_add_step("Loading Face Images") + self._report.progress_add_step("Scaling Face Images") + self._report.progress_add_step("Generating Cube Map") self._report.progress_start("BUILDING CUBE MAP") if not Path(self.filepath).is_file(): raise ExportError("No cube image found at '{}'".format(self.filepath)) diff --git a/korman/ui/ui_menus.py b/korman/ui/ui_menus.py index e9ee128..18a9a31 100644 --- a/korman/ui/ui_menus.py +++ b/korman/ui/ui_menus.py @@ -42,6 +42,7 @@ class PlasmaImageMenu(PlasmaMenu, bpy.types.Menu): layout = self.layout layout.operator("image.plasma_bake_image_alpha", icon="IMAGE_RGB_ALPHA") + layout.operator("image.plasma_build_cube_map", icon="MATCUBE") def _build_plasma_menu(menu_operator, self, context): From 84ab9206c34cc2b76aa69b20d927adf8eb956795 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Sat, 15 Feb 2020 19:07:29 -0500 Subject: [PATCH 3/3] Add clamp alpha operator. This is useful to force textures to compress to DXT1 if you want to maintain some cheap transparency. --- korman/operators/op_image.py | 73 +++++++++++++++++++++++------------- korman/ui/ui_menus.py | 2 + 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/korman/operators/op_image.py b/korman/operators/op_image.py index 93925c3..9734ab4 100644 --- a/korman/operators/op_image.py +++ b/korman/operators/op_image.py @@ -33,15 +33,7 @@ _CUBE_FACES = { "frontFace": "FR", } -class PlasmaBakeImageAlphaOperator(bpy.types.Operator): - bl_idname = "image.plasma_bake_image_alpha" - bl_label = "Bake Image Alpha" - bl_description = "Bake an image's calculated alpha to another image's alpha channel" - - filepath = StringProperty(name="Alpha Image", - subtype="FILE_PATH", - options=set()) - +class ImageOperator: def execute(self, context): with ConsoleToggler(True), ExportProgressLogger() as self._report: try: @@ -52,6 +44,23 @@ class PlasmaBakeImageAlphaOperator(bpy.types.Operator): else: return {"FINISHED"} + def _execute(self, context): + raise NotImplementedError() + + @classmethod + def poll(cls, context): + return context.scene.render.engine == "PLASMA_GAME" + + +class PlasmaBakeImageAlphaOperator(ImageOperator, bpy.types.Operator): + bl_idname = "image.plasma_bake_image_alpha" + bl_label = "Bake Image Alpha" + bl_description = "Bake an image's calculated alpha to another image's alpha channel" + + filepath = StringProperty(name="Alpha Image", + subtype="FILE_PATH", + options=set()) + def _execute(self, context): self._report.progress_add_step("Preparing Images") self._report.progress_add_step("Baking Alpha") @@ -105,10 +114,10 @@ class PlasmaBakeImageAlphaOperator(bpy.types.Operator): @classmethod def poll(cls, context): space = context.space_data - return context.scene.render.engine == "PLASMA_GAME" and space and space.image + return super().poll(context) and space and space.image -class PlasmaBuildCubeMapOperator(bpy.types.Operator): +class PlasmaBuildCubeMapOperator(ImageOperator, bpy.types.Operator): bl_idname = "image.plasma_build_cube_map" bl_label = "Build Cubemap" bl_description = "Builds a Blender cubemap from six images" @@ -127,17 +136,7 @@ class PlasmaBuildCubeMapOperator(bpy.types.Operator): default="", options={"HIDDEN"}) - def execute(self, context): - with ConsoleToggler(True), ExportProgressLogger() as self._report: - try: - self._execute() - except ExportError as error: - self.report({"ERROR"}, str(error)) - return {"CANCELLED"} - else: - return {"FINISHED"} - - def _execute(self): + def _execute(self, context): self._report.progress_add_step("Finding Face Images") self._report.progress_add_step("Loading Face Images") self._report.progress_add_step("Scaling Face Images") @@ -273,10 +272,6 @@ class PlasmaBuildCubeMapOperator(bpy.types.Operator): with GLTexture(image=blimage, fast=True) as glimage: return glimage.image_data - @classmethod - def poll(cls, context): - return context.scene.render.engine == "PLASMA_GAME" - def _scale_images(self, face_widths, face_heights, face_data): self._report.progress_advance() self._report.progress_range = len(BLENDER_CUBE_MAP) @@ -305,3 +300,29 @@ class PlasmaBuildCubeMapOperator(bpy.types.Operator): min_width, min_height) self._report.progress_increment() return min_width, min_height, tuple(result_data) + + +class PlasmaClampAlphaOperator(ImageOperator, bpy.types.Operator): + bl_idname = "image.plasma_clamp_image_alpha" + bl_label = "Clamp Image Alpha" + bl_description = "Clamp an image's alpha to fully opaque or transparent" + bl_options = {"UNDO"} + + def _execute(self, context): + self._report.progress_add_step("Clamping Alpha") + self._report.progress_start("CLAMPING ALPHA") + self._report.progress_advance() + + image = context.space_data.image + pixels = list(image.pixels) + width, height = image.size + # significant figures: round up the even + for i in range(3, (width * height * 4), 4): + pixels[i] = 1.0 if pixels[i] >= 0.5 else 0.0 + image.pixels = pixels + self._report.progress_end() + + @classmethod + def poll(cls, context): + space = context.space_data + return super().poll(context) and space and space.image diff --git a/korman/ui/ui_menus.py b/korman/ui/ui_menus.py index 18a9a31..c3e1638 100644 --- a/korman/ui/ui_menus.py +++ b/korman/ui/ui_menus.py @@ -42,6 +42,8 @@ class PlasmaImageMenu(PlasmaMenu, bpy.types.Menu): layout = self.layout layout.operator("image.plasma_bake_image_alpha", icon="IMAGE_RGB_ALPHA") + layout.operator("image.plasma_clamp_image_alpha", icon="IMAGE_ALPHA") + layout.separator() layout.operator("image.plasma_build_cube_map", icon="MATCUBE")