''' Copyright (C) 2021 - 2024 Akaneyu This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ''' import bpy from . import operators from . import app def get_icon_id(icon_name): session = app.get_session() if icon_name in session.icons: return session.icons[icon_name].icon_id else: return 0 def menu_func(self, context): area_session = app.get_area_session(context) layout = self.layout if context.area.spaces.active.mode != 'UV' \ and context.area.spaces.active.image != None \ and context.area.spaces.active.image.source != 'VIEWER': layout.separator() layout.label(text='Image Editor+') if area_session.selection: layout.operator(operators.IMAGE_EDITOR_PLUS_OT_make_selection.bl_idname, text='Select Box', icon_value=get_icon_id('select')) layout.operator(operators.IMAGE_EDITOR_PLUS_OT_cancel_selection.bl_idname, text='Deselect', icon_value=get_icon_id('deselect')) else: layout.operator(operators.IMAGE_EDITOR_PLUS_OT_make_selection.bl_idname, text='Select Box', icon_value=get_icon_id('select')) layout.menu(IMAGE_EDITOR_PLUS_MT_edit_menu.bl_idname, text='Edit') layout.menu(IMAGE_EDITOR_PLUS_MT_layers_menu.bl_idname, text='Pasted Layers') layout.menu(IMAGE_EDITOR_PLUS_MT_transform_menu.bl_idname, text='Transform') layout.menu(IMAGE_EDITOR_PLUS_MT_transform_layer_menu.bl_idname, text='Transform Layer') layout.menu(IMAGE_EDITOR_PLUS_MT_offset_menu.bl_idname, text='Offset') layout.menu(IMAGE_EDITOR_PLUS_MT_adjust_menu.bl_idname, text='Adjust') layout.menu(IMAGE_EDITOR_PLUS_MT_filter_menu.bl_idname, text='Filter') def reset_scale_properties(self, context): if self.reset: self.width_pixels = self.original_width self.height_pixels = self.original_height self.property_unset('width_percent') self.property_unset('height_percent') self.property_unset('keep_aspect_ratio') self.property_unset('scale_layers') self.reset = False def update_scale_width_properties(self, context): if self.skip_property_update: return if self.keep_aspect_ratio: self.skip_property_update = True ratio = self.original_width / self.original_height self.height_pixels = int(self.width_pixels / ratio) self.height_percent = self.width_percent self.skip_property_update = False def update_scale_height_properties(self, context): if self.skip_property_update: return if self.keep_aspect_ratio: self.skip_property_update = True ratio = self.original_width / self.original_height self.width_pixels = int(self.height_pixels * ratio) self.width_percent = self.height_percent self.skip_property_update = False class IMAGE_EDITOR_PLUS_OT_scale_dialog(bpy.types.Operator): """Scale the image""" bl_idname = 'image_editor_plus.scale_image_dialog' bl_label = "Scale Image" reset: bpy.props.BoolProperty(options={'SKIP_SAVE'}, update=reset_scale_properties) unit: bpy.props.EnumProperty(options={'SKIP_SAVE'}, items=( ('pixels', 'Pixels', 'In pixels'), ('percent', 'Percent', 'In percent'))) width_pixels: bpy.props.IntProperty(name='Width', min=1, options={'SKIP_SAVE'}, update=update_scale_width_properties) height_pixels: bpy.props.IntProperty(name='Height', min=1, options={'SKIP_SAVE'}, update=update_scale_height_properties) width_percent: bpy.props.FloatProperty(name='Width', subtype='PERCENTAGE', min=1.0, soft_max=200.0, default=100.0, options={'SKIP_SAVE'}, update=update_scale_width_properties) height_percent: bpy.props.FloatProperty(name='Height', subtype='PERCENTAGE', min=1.0, soft_max=200.0, default=100.0, options={'SKIP_SAVE'}, update=update_scale_height_properties) keep_aspect_ratio: bpy.props.BoolProperty(name='Keep Aspect Ratio', default=True, options={'SKIP_SAVE'}, update=update_scale_width_properties) scale_layers: bpy.props.BoolProperty(name='Scale Layers', default=True, options={'SKIP_SAVE'}, description='Only if Keep Aspect Ratio is turned on') original_width: bpy.props.IntProperty() original_height: bpy.props.IntProperty() skip_property_update: bpy.props.BoolProperty() def invoke(self, context, event): wm = context.window_manager img = context.area.spaces.active.image if not img: return {'CANCELLED'} width, height = img.size self.original_width = width self.original_height = height self.width_pixels = width self.height_pixels = height return wm.invoke_props_dialog(self) def execute(self, context): if self.unit == 'pixels': width = self.width_pixels height = self.height_pixels else: # percent width = int(self.original_width * self.width_percent / 100.0) height = int(self.original_height * self.height_percent / 100.0) if width < 1: width = 1 if height < 1: height = 1 scale_layers = False if self.keep_aspect_ratio: scale_layers = self.scale_layers bpy.ops.image_editor_plus.scale('EXEC_DEFAULT', False, width=width, height=height, scale_layers=scale_layers) return {'FINISHED'} def draw(self, context): layout = self.layout row = layout.row() row.prop(self, "unit", expand=True) row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Width') if self.unit == 'pixels': row.prop(self, 'width_pixels', text='') else: row.prop(self, 'width_percent', text='') row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Height') if self.unit == 'pixels': row.prop(self, 'height_pixels', text='') else: row.prop(self, 'height_percent', text='') row = layout.split(align=True) row.column() row.prop(self, 'keep_aspect_ratio') row = layout.split(align=True) row.column() if self.keep_aspect_ratio: row.prop(self, 'scale_layers') else: row.label(text='No layers scaled.') row = layout.split(factor=0.7) row.column() row.prop(self, 'reset', text='Reset', toggle=True) def reset_canvas_size_properties(self, context): if self.reset: self.width = self.original_width self.height = self.original_height self.expand_from_center = False self.use_background_color = False self.reset = False class IMAGE_EDITOR_PLUS_OT_change_canvas_size_dialog(bpy.types.Operator): """Change the canvas size""" bl_idname = 'image_editor_plus.change_canvas_size_dialog' bl_label = "Canvas Size" reset: bpy.props.BoolProperty(options={'SKIP_SAVE'}, update=reset_canvas_size_properties) width: bpy.props.IntProperty(name='Width', min=1, options={'SKIP_SAVE'}) height: bpy.props.IntProperty(name='Height', min=1, options={'SKIP_SAVE'}) expand_from_center: bpy.props.BoolProperty(name='Expand from Center', default=False) use_background_color: bpy.props.BoolProperty(name='Use Background Color', default=False) original_width: bpy.props.IntProperty() original_height: bpy.props.IntProperty() def invoke(self, context, event): wm = context.window_manager img = context.area.spaces.active.image if not img: return {'CANCELLED'} width, height = img.size self.original_width = width self.original_height = height self.width = width self.height = height return wm.invoke_props_dialog(self) def execute(self, context): width = self.width height = self.height if width < 1: width = 1 if height < 1: height = 1 bpy.ops.image_editor_plus.change_canvas_size('EXEC_DEFAULT', False, width=width, height=height, expand_from_center=self.expand_from_center, use_background_color=self.use_background_color) return {'FINISHED'} def draw(self, context): layout = self.layout row = layout.row() row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Width') row.prop(self, 'width', text='') row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Height') row.prop(self, 'height', text='') row = layout.split(align=True) row.column() row.prop(self, 'expand_from_center') row = layout.split(align=True) row.column() row.prop(self, 'use_background_color') row = layout.split(align=True) row.column() row = layout.split(factor=0.7) row.column() row.prop(self, 'reset', text='Reset', toggle=True) def preview_offset_properties(self, context): if self.preview: update_offset_properties(self, context) else: img = app.get_target_image(context) if img: app.revert_image_cache(img) app.refresh_image(context) def reset_offset_properties(self, context): if self.reset: self.offset_properties.property_unset('offset_x') self.offset_properties.property_unset('offset_y') self.property_unset('offset_edge_behavior') update_offset_properties(self, context) self.reset = False def update_offset_properties(self, context): if self.preview: bpy.ops.image_editor_plus.offset('EXEC_DEFAULT', False, offset_x=self.offset_properties.offset_x, offset_y=self.offset_properties.offset_y, offset_edge_behavior=self.offset_edge_behavior) # wrap these properties to change their attributes dynamically class IMAGE_EDITOR_PLUS_OffsetPropertyGroup(bpy.types.PropertyGroup): offset_x: bpy.props.IntProperty() offset_y: bpy.props.IntProperty() class IMAGE_EDITOR_PLUS_OT_offset_dialog(bpy.types.Operator): """Offset the image""" bl_idname = 'image_editor_plus.offset_dialog' bl_label = "Offset" preview: bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'}, update=preview_offset_properties) reset: bpy.props.BoolProperty(options={'SKIP_SAVE'}, update=reset_offset_properties) offset_properties: bpy.props.PointerProperty(options={'SKIP_SAVE'}, type=IMAGE_EDITOR_PLUS_OffsetPropertyGroup) offset_edge_behavior: bpy.props.EnumProperty(options={'SKIP_SAVE'}, items=( ('wrap', 'Wrap', 'Wrap image around'), ('edge', 'Edge', 'Repeat edge pixels')), update=update_offset_properties) def invoke(self, context, event): wm = context.window_manager img = app.get_target_image(context) if not img: return {'CANCELLED'} app.cache_image(img) width, height = img.size selection = app.get_target_selection(context) if selection: width = selection[1][0] - selection[0][0] height = selection[1][1] - selection[0][1] IMAGE_EDITOR_PLUS_OffsetPropertyGroup.offset_x = \ bpy.props.IntProperty(name='Offset X', subtype='FACTOR', min=-width + 1, max=width - 1, update=(lambda _self, context: update_offset_properties(self, context))) IMAGE_EDITOR_PLUS_OffsetPropertyGroup.offset_y = \ bpy.props.IntProperty(name='Offset Y', subtype='FACTOR', min=-width + 1, max=width - 1, update=(lambda _self, context: update_offset_properties(self, context))) update_offset_properties(self, context) return wm.invoke_props_dialog(self) def execute(self, context): bpy.ops.image_editor_plus.offset('EXEC_DEFAULT', False, offset_x=self.offset_properties.offset_x, offset_y=self.offset_properties.offset_y, offset_edge_behavior=self.offset_edge_behavior) return {'FINISHED'} def cancel(self, context): img = app.get_target_image(context) if not img: return {'CANCELLED'} app.revert_image_cache(img) app.clear_image_cache() app.refresh_image(context) def draw(self, context): layout = self.layout row = layout.split(align=True) row.column() row.prop(self, 'preview', text='Preview', toggle=True, icon='VIEWZOOM') layout.separator() row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Offset X') row.prop(self.offset_properties, 'offset_x', text='') row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Offset Y') row.prop(self.offset_properties, 'offset_y', text='') row = layout.row() row.prop(self, "offset_edge_behavior", expand=True) row = layout.split(factor=0.7) row.column() row.prop(self, 'reset', text='Reset', toggle=True) def preview_adjust_color_properties(self, context): if self.preview: update_adjust_color_properties(self, context) else: img = app.get_target_image(context) if img: app.revert_image_cache(img) app.refresh_image(context) def reset_adjust_color_properties(self, context): if self.reset: self.property_unset('adjust_hue') self.property_unset('adjust_lightness') self.property_unset('adjust_saturation') update_adjust_color_properties(self, context) self.reset = False def update_adjust_color_properties(self, context): if self.preview: bpy.ops.image_editor_plus.adjust_color('EXEC_DEFAULT', False, adjust_hue=self.adjust_hue, adjust_lightness=self.adjust_lightness, adjust_saturation=self.adjust_saturation) class IMAGE_EDITOR_PLUS_OT_adjust_color_dialog(bpy.types.Operator): """Adjust hue/saturation/lightness of the image""" bl_idname = 'image_editor_plus.adjust_color_dialog' bl_label = "Hue/Saturation" preview: bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'}, update=preview_adjust_color_properties) reset: bpy.props.BoolProperty(options={'SKIP_SAVE'}, update=reset_adjust_color_properties) adjust_hue: bpy.props.FloatProperty(name='Hue', subtype='FACTOR', min=-180, max=180, default=0, options={'SKIP_SAVE'}, update=update_adjust_color_properties) adjust_lightness: bpy.props.FloatProperty(name='Lightness', subtype='PERCENTAGE', min=0, max=200, default=100, options={'SKIP_SAVE'}, update=update_adjust_color_properties) adjust_saturation: bpy.props.FloatProperty(name='Saturation', subtype='PERCENTAGE', min=0, max=200, default=100, options={'SKIP_SAVE'}, update=update_adjust_color_properties) def invoke(self, context, event): wm = context.window_manager img = app.get_target_image(context) if not img: return {'CANCELLED'} app.cache_image(img, need_hsl=True) update_adjust_color_properties(self, context) return wm.invoke_props_dialog(self) def execute(self, context): bpy.ops.image_editor_plus.adjust_color('EXEC_DEFAULT', False, adjust_hue=self.adjust_hue, adjust_lightness=self.adjust_lightness, adjust_saturation=self.adjust_saturation) return {'FINISHED'} def cancel(self, context): img = app.get_target_image(context) if not img: return {'CANCELLED'} app.revert_image_cache(img) app.clear_image_cache() app.refresh_image(context) def draw(self, context): layout = self.layout row = layout.split(align=True) row.column() row.prop(self, 'preview', text='Preview', toggle=True, icon='VIEWZOOM') layout.separator() row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Hue') row.prop(self, 'adjust_hue', text='') row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Lightness') row.prop(self, 'adjust_lightness', text='') row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Saturation') row.prop(self, 'adjust_saturation', text='') row = layout.split(factor=0.7) row.column() row.prop(self, 'reset', text='Reset', toggle=True) def preview_adjust_brightness_properties(self, context): if self.preview: update_adjust_brightness_properties(self, context) else: img = app.get_target_image(context) if img: app.revert_image_cache(img) app.refresh_image(context) def reset_adjust_brightness_properties(self, context): if self.reset: self.property_unset('adjust_brightness') self.property_unset('adjust_contrast') update_adjust_brightness_properties(self, context) self.reset = False def update_adjust_brightness_properties(self, context): if self.preview: bpy.ops.image_editor_plus.adjust_brightness('EXEC_DEFAULT', False, adjust_brightness=self.adjust_brightness, adjust_contrast=self.adjust_contrast) class IMAGE_EDITOR_PLUS_OT_adjust_brightness_dialog(bpy.types.Operator): """Adjust brightness/contrast of the image""" bl_idname = 'image_editor_plus.adjust_brightness_dialog' bl_label = "Brightness/Contrast" preview: bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'}, update=preview_adjust_brightness_properties) reset: bpy.props.BoolProperty(options={'SKIP_SAVE'}, update=reset_adjust_brightness_properties) adjust_brightness: bpy.props.FloatProperty(name='Brightness', subtype='PERCENTAGE', min=0, max=200, default=100, options={'SKIP_SAVE'}, update=update_adjust_brightness_properties) adjust_contrast: bpy.props.FloatProperty(name='Contrast', subtype='PERCENTAGE', min=0, max=200, default=100, options={'SKIP_SAVE'}, update=update_adjust_brightness_properties) def invoke(self, context, event): wm = context.window_manager img = app.get_target_image(context) if not img: return {'CANCELLED'} app.cache_image(img) update_adjust_brightness_properties(self, context) return wm.invoke_props_dialog(self) def execute(self, context): bpy.ops.image_editor_plus.adjust_brightness('EXEC_DEFAULT', False, adjust_brightness=self.adjust_brightness, adjust_contrast=self.adjust_contrast) return {'FINISHED'} def cancel(self, context): img = app.get_target_image(context) if not img: return {'CANCELLED'} app.revert_image_cache(img) app.clear_image_cache() app.refresh_image(context) def draw(self, context): layout = self.layout row = layout.split(align=True) row.column() row.prop(self, 'preview', text='Preview', toggle=True, icon='VIEWZOOM') layout.separator() row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Brightness') row.prop(self, 'adjust_brightness', text='') row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Contrast') row.prop(self, 'adjust_contrast', text='') row = layout.split(factor=0.7) row.column() row.prop(self, 'reset', text='Reset', toggle=True) def preview_adjust_gamma_properties(self, context): if self.preview: update_adjust_gamma_properties(self, context) else: img = app.get_target_image(context) if img: app.revert_image_cache(img) app.refresh_image(context) def reset_adjust_gamma_properties(self, context): if self.reset: self.property_unset('adjust_gamma') update_adjust_gamma_properties(self, context) self.reset = False def update_adjust_gamma_properties(self, context): if self.preview: bpy.ops.image_editor_plus.adjust_gamma('EXEC_DEFAULT', False, adjust_gamma=self.adjust_gamma) class IMAGE_EDITOR_PLUS_OT_adjust_gamma_dialog(bpy.types.Operator): """Adjust gamma of the image""" bl_idname = 'image_editor_plus.adjust_gamma_dialog' bl_label = "Gamma" preview: bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'}, update=preview_adjust_gamma_properties) reset: bpy.props.BoolProperty(options={'SKIP_SAVE'}, update=reset_adjust_gamma_properties) adjust_gamma: bpy.props.FloatProperty(name='Gamma', subtype='FACTOR', min=0.01, soft_max=3.0, default=1.0, options={'SKIP_SAVE'}, update=update_adjust_gamma_properties) def invoke(self, context, event): wm = context.window_manager img = app.get_target_image(context) if not img: return {'CANCELLED'} app.cache_image(img) update_adjust_gamma_properties(self, context) return wm.invoke_props_dialog(self) def execute(self, context): bpy.ops.image_editor_plus.adjust_gamma('EXEC_DEFAULT', False, adjust_gamma=self.adjust_gamma) return {'FINISHED'} def cancel(self, context): img = app.get_target_image(context) if not img: return {'CANCELLED'} app.revert_image_cache(img) app.clear_image_cache() app.refresh_image(context) def draw(self, context): layout = self.layout row = layout.split(align=True) row.column() row.prop(self, 'preview', text='Preview', toggle=True, icon='VIEWZOOM') layout.separator() row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Gamma') row.prop(self, 'adjust_gamma', text='') row = layout.split(factor=0.7) row.column() row.prop(self, 'reset', text='Reset', toggle=True) def preview_adjust_color_curve_properties(self, context): if not self.preview: img = app.get_target_image(context) if img: app.revert_image_cache(img) app.refresh_image(context) def reset_adjust_color_curve_properties(self, context): if self.reset: app.reset_curve_mapping() self.reset = False def update_adjust_color_curve_properties(self, context): if self.update_preview: self.update_preview = False if self.preview: bpy.ops.image_editor_plus.adjust_color_curve('EXEC_DEFAULT', False) class IMAGE_EDITOR_PLUS_OT_adjust_color_curve_dialog(bpy.types.Operator): """Adjust color curve of the image""" bl_idname = 'image_editor_plus.adjust_curve_dialog' bl_label = "Adjust Color Curve" preview: bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'}, update=preview_adjust_color_curve_properties, description='Preview manually (Need an update operation)') update_preview: bpy.props.BoolProperty(options={'SKIP_SAVE'}, update=update_adjust_color_curve_properties, description='Update preview') reset: bpy.props.BoolProperty(update=reset_adjust_color_curve_properties, options={'SKIP_SAVE'}) def invoke(self, context, event): wm = context.window_manager img = app.get_target_image(context) if not img: return {'CANCELLED'} app.cache_image(img) app.reset_curve_mapping() return wm.invoke_props_dialog(self) def execute(self, context): bpy.ops.image_editor_plus.adjust_color_curve('EXEC_DEFAULT', False) return {'FINISHED'} def cancel(self, context): img = app.get_target_image(context) if not img: return {'CANCELLED'} app.revert_image_cache(img) app.clear_image_cache() app.refresh_image(context) def draw(self, context): layout = self.layout row = layout.split(align=True) row.column() row.prop(self, 'preview', text='Preview', toggle=True, icon='VIEWZOOM') row.prop(self, 'update_preview', text='Update', toggle=True, icon='FILE_REFRESH') layout.separator() curve_node = app.get_curve_node() col = layout.column(align=True) col.template_curve_mapping(curve_node, 'mapping', type='COLOR') row = layout.split(factor=0.7) row.column() row.prop(self, 'reset', text='Reset', toggle=True) def preview_replace_color_properties(self, context): if self.preview: update_replace_color_properties(self, context) else: img = app.get_target_image(context) if img: app.revert_image_cache(img) app.refresh_image(context) def reset_replace_color_properties(self, context): if self.reset: self.property_unset('source_color') self.property_unset('replace_color') self.property_unset('color_threshold') update_replace_color_properties(self, context) self.reset = False def update_replace_color_properties(self, context): if self.preview: bpy.ops.image_editor_plus.replace_color('EXEC_DEFAULT', False, source_color=self.source_color, replace_color=self.replace_color, color_threshold=self.color_threshold) class IMAGE_EDITOR_PLUS_OT_replace_color_dialog(bpy.types.Operator): """Replace one color in the image with another""" bl_idname = 'image_editor_plus.replace_color_dialog' bl_label = "Replace Color" preview: bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'}, update=preview_replace_color_properties) reset: bpy.props.BoolProperty(options={'SKIP_SAVE'}, update=reset_replace_color_properties) source_color: bpy.props.FloatVectorProperty(name='Source Color', subtype='COLOR_GAMMA', min=0, max=1.0, size=3, default=(1.0, 1.0, 1.0), options={'SKIP_SAVE'}, update=update_replace_color_properties) # no alpha replace_color: bpy.props.FloatVectorProperty(name='Replace Color', subtype='COLOR_GAMMA', min=0, max=1.0, size=4, default=(0, 0, 0, 1.0), options={'SKIP_SAVE'}, update=update_replace_color_properties) color_threshold: bpy.props.FloatProperty(name='Threshold', subtype='FACTOR', min=0, max=1.0, default=0.1, options={'SKIP_SAVE'}, update=update_replace_color_properties) def invoke(self, context, event): wm = context.window_manager img = app.get_target_image(context) if not img: return {'CANCELLED'} app.cache_image(img) update_replace_color_properties(self, context) return wm.invoke_props_dialog(self) def execute(self, context): bpy.ops.image_editor_plus.replace_color('EXEC_DEFAULT', False, source_color=self.source_color, replace_color=self.replace_color, color_threshold=self.color_threshold) return {'FINISHED'} def cancel(self, context): img = app.get_target_image(context) if not img: return {'CANCELLED'} app.revert_image_cache(img) app.clear_image_cache() app.refresh_image(context) def draw(self, context): layout = self.layout row = layout.split(align=True) row.column() row.prop(self, 'preview', text='Preview', toggle=True, icon='VIEWZOOM') layout.separator() row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Source Color') row.prop(self, 'source_color', text='') row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Replace Color') row.prop(self, 'replace_color', text='') row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Threshold') row.prop(self, 'color_threshold', text='') row = layout.split(factor=0.7) row.column() row.prop(self, 'reset', text='Reset', toggle=True) def preview_blur_properties(self, context): if self.preview: update_blur_properties(self, context) else: img = app.get_target_image(context) if img: app.revert_image_cache(img) app.refresh_image(context) def reset_blur_properties(self, context): if self.reset: self.property_unset('blur_size') self.property_unset('expand_layer') update_blur_properties(self, context) self.reset = False def update_blur_properties(self, context): if self.preview: bpy.ops.image_editor_plus.blur('EXEC_DEFAULT', False, blur_size=self.blur_size, expand_layer=False) class IMAGE_EDITOR_PLUS_OT_blur_dialog(bpy.types.Operator): """Blur the image""" bl_idname = 'image_editor_plus.blur_dialog' bl_label = "Blur (Gaussian Blur)" preview: bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'}, update=preview_blur_properties) reset: bpy.props.BoolProperty(options={'SKIP_SAVE'}, update=reset_blur_properties) blur_size: bpy.props.FloatProperty(name='Size', subtype='FACTOR', min=0, soft_max=10.0, default=3.0, options={'SKIP_SAVE'}, update=update_blur_properties) expand_layer: bpy.props.BoolProperty(name='Expand Layer', default=True, options={'SKIP_SAVE'}, update=update_blur_properties) def invoke(self, context, event): session = app.get_session() wm = context.window_manager img = app.get_target_image(context) if not img: return {'CANCELLED'} app.cache_image(img) layer = app.get_active_layer(context) if layer: session.cached_layer_location = layer.location[:] update_blur_properties(self, context) return wm.invoke_props_dialog(self) def execute(self, context): bpy.ops.image_editor_plus.blur('EXEC_DEFAULT', False, blur_size=self.blur_size, expand_layer=self.expand_layer) return {'FINISHED'} def cancel(self, context): img = app.get_target_image(context) if not img: return {'CANCELLED'} app.revert_image_cache(img) app.clear_image_cache() app.refresh_image(context) def draw(self, context): layout = self.layout row = layout.split(align=True) row.column() row.prop(self, 'preview', text='Preview', toggle=True, icon='VIEWZOOM') layout.separator() row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Size') row.prop(self, 'blur_size', text='') row = layout.split(align=True) row.column() row.prop(self, 'expand_layer') row = layout.split(factor=0.7) row.column() row.prop(self, 'reset', text='Reset', toggle=True) def preview_sharpen_properties(self, context): if self.preview: update_sharpen_properties(self, context) else: img = app.get_target_image(context) if img: app.revert_image_cache(img) app.refresh_image(context) def reset_sharpen_properties(self, context): if self.reset: self.property_unset('sharpen_radius') self.property_unset('sharpen_amount') self.property_unset('sharpen_threshold') update_sharpen_properties(self, context) self.reset = False def update_sharpen_properties(self, context): if self.preview: bpy.ops.image_editor_plus.sharpen('EXEC_DEFAULT', False, sharpen_radius=self.sharpen_radius, sharpen_amount=self.sharpen_amount, sharpen_threshold=self.sharpen_threshold) class IMAGE_EDITOR_PLUS_OT_sharpen_dialog(bpy.types.Operator): """Sharpen the image""" bl_idname = 'image_editor_plus.sharpen_dialog' bl_label = "Sharpen (Unsharp Mask)" preview: bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'}, update=preview_sharpen_properties) reset: bpy.props.BoolProperty(update=reset_sharpen_properties, options={'SKIP_SAVE'}) sharpen_radius: bpy.props.FloatProperty(name='Radius', subtype='FACTOR', min=0, soft_max=10.0, default=3.0, options={'SKIP_SAVE'}, update=update_sharpen_properties) sharpen_amount: bpy.props.FloatProperty(name='Amount', subtype='FACTOR', min=0, soft_max=10.0, default=0.5, options={'SKIP_SAVE'}, update=update_sharpen_properties) sharpen_threshold: bpy.props.FloatProperty(name='Threshold', subtype='FACTOR', min=0, soft_max=1.0, default=0, options={'SKIP_SAVE'}, update=update_sharpen_properties) def invoke(self, context, event): wm = context.window_manager img = app.get_target_image(context) if not img: return {'CANCELLED'} app.cache_image(img) update_sharpen_properties(self, context) return wm.invoke_props_dialog(self) def execute(self, context): bpy.ops.image_editor_plus.sharpen('EXEC_DEFAULT', False, sharpen_radius=self.sharpen_radius, sharpen_amount=self.sharpen_amount, sharpen_threshold=self.sharpen_threshold) return {'FINISHED'} def cancel(self, context): img = app.get_target_image(context) if not img: return {'CANCELLED'} app.revert_image_cache(img) app.clear_image_cache() app.refresh_image(context) def draw(self, context): layout = self.layout row = layout.split(align=True) row.column() row.prop(self, 'preview', text='Preview', toggle=True, icon='VIEWZOOM') layout.separator() row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Radius') row.prop(self, 'sharpen_radius', text='') row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Amount') row.prop(self, 'sharpen_amount', text='') row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Threshold') row.prop(self, 'sharpen_threshold', text='') row = layout.split(factor=0.7) row.column() row.prop(self, 'reset', text='Reset', toggle=True) def preview_add_noise_properties(self, context): if self.preview: update_add_noise_properties(self, context) else: img = app.get_target_image(context) if img: app.revert_image_cache(img) app.refresh_image(context) def reset_add_noise_properties(self, context): if self.reset: self.property_unset('add_noise_intensity') update_add_noise_properties(self, context) self.reset = False def update_add_noise_properties(self, context): if self.preview: bpy.ops.image_editor_plus.add_noise('EXEC_DEFAULT', False, add_noise_intensity=self.add_noise_intensity) class IMAGE_EDITOR_PLUS_OT_add_noise_dialog(bpy.types.Operator): """Add some noise to the image""" bl_idname = 'image_editor_plus.add_noise_dialog' bl_label = "Add Noise (Gaussian Noise)" preview: bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'}, update=preview_add_noise_properties) reset: bpy.props.BoolProperty(options={'SKIP_SAVE'}, update=reset_add_noise_properties) add_noise_intensity: bpy.props.FloatProperty(name='Intensity', subtype='FACTOR', min=0, soft_max=10.0, default=0.1, options={'SKIP_SAVE'}, update=update_add_noise_properties) def invoke(self, context, event): wm = context.window_manager img = app.get_target_image(context) if not img: return {'CANCELLED'} app.cache_image(img) update_add_noise_properties(self, context) return wm.invoke_props_dialog(self) def execute(self, context): bpy.ops.image_editor_plus.add_noise('EXEC_DEFAULT', False, add_noise_intensity=self.add_noise_intensity) return {'FINISHED'} def cancel(self, context): img = app.get_target_image(context) if not img: return {'CANCELLED'} app.revert_image_cache(img) app.clear_image_cache() app.refresh_image(context) def draw(self, context): layout = self.layout row = layout.split(align=True) row.column() row.prop(self, 'preview', text='Preview', toggle=True, icon='VIEWZOOM') layout.separator() row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Intensity') row.prop(self, 'add_noise_intensity', text='') row = layout.split(factor=0.7) row.column() row.prop(self, 'reset', text='Reset', toggle=True) def preview_pixelize_properties(self, context): if self.preview: update_pixelize_properties(self, context) else: img = app.get_target_image(context) if img: app.revert_image_cache(img) app.refresh_image(context) def reset_pixelize_properties(self, context): if self.reset: self.property_unset('pixelize_pixel_size') update_pixelize_properties(self, context) self.reset = False def update_pixelize_properties(self, context): if self.preview: bpy.ops.image_editor_plus.pixelize('EXEC_DEFAULT', False, pixelize_pixel_size=self.pixelize_pixel_size) class IMAGE_EDITOR_PLUS_OT_pixelize_dialog(bpy.types.Operator): """Pixelize the image""" bl_idname = 'image_editor_plus.pixelize_dialog' bl_label = "Pixelize" preview: bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'}, update=preview_pixelize_properties) reset: bpy.props.BoolProperty(options={'SKIP_SAVE'}, update=reset_pixelize_properties) pixelize_pixel_size: bpy.props.IntProperty(name='Pixel Size', subtype='FACTOR', min=1, soft_max=64, default=16, options={'SKIP_SAVE'}, update=update_pixelize_properties) def invoke(self, context, event): wm = context.window_manager img = app.get_target_image(context) if not img: return {'CANCELLED'} app.cache_image(img) update_pixelize_properties(self, context) return wm.invoke_props_dialog(self) def execute(self, context): bpy.ops.image_editor_plus.pixelize('EXEC_DEFAULT', False, pixelize_pixel_size=self.pixelize_pixel_size) return {'FINISHED'} def cancel(self, context): img = app.get_target_image(context) if not img: return {'CANCELLED'} app.revert_image_cache(img) app.clear_image_cache() app.refresh_image(context) def draw(self, context): layout = self.layout row = layout.split(align=True) row.column() row.prop(self, 'preview', text='Preview', toggle=True, icon='VIEWZOOM') layout.separator() row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Pixel Size') row.prop(self, 'pixelize_pixel_size', text='') row = layout.split(factor=0.7) row.column() row.prop(self, 'reset', text='Reset', toggle=True) def preview_make_seamless_properties(self, context): if self.preview: update_make_seamless_properties(self, context) else: img = app.get_target_image(context) if img: app.revert_image_cache(img) app.refresh_image(context) def update_make_seamless_properties(self, context): if self.preview: bpy.ops.image_editor_plus.make_seamless('EXEC_DEFAULT', False) class IMAGE_EDITOR_PLUS_OT_make_seamless_dialog(bpy.types.Operator): """Turn the image into seamless tile""" bl_idname = 'image_editor_plus.make_seamless_dialog' bl_label = "Make Seamless" preview: bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'}, update=preview_make_seamless_properties) def invoke(self, context, event): wm = context.window_manager img = app.get_target_image(context) if not img: return {'CANCELLED'} app.cache_image(img) update_make_seamless_properties(self, context) return wm.invoke_props_dialog(self) def execute(self, context): bpy.ops.image_editor_plus.make_seamless('EXEC_DEFAULT', False) return {'FINISHED'} def cancel(self, context): img = app.get_target_image(context) if not img: return {'CANCELLED'} app.revert_image_cache(img) app.clear_image_cache() app.refresh_image(context) def draw(self, context): layout = self.layout row = layout.split(align=True) row.column() row.prop(self, 'preview', text='Preview', toggle=True, icon='VIEWZOOM') def preview_normal_map_properties(self, context): if self.preview: update_normal_map_properties(self, context) else: img = app.get_target_image(context) if img: app.revert_image_cache(img) app.refresh_image(context) def reset_normal_map_properties(self, context): if self.reset: self.property_unset('scale') self.property_unset('flip_x') self.property_unset('flip_y') self.property_unset('full_z') update_normal_map_properties(self, context) self.reset = False def update_normal_map_properties(self, context): if self.preview: bpy.ops.image_editor_plus.normal_map('EXEC_DEFAULT', False, scale=self.scale, flip_x=self.flip_x, flip_y=self.flip_y, full_z=self.full_z) class IMAGE_EDITOR_PLUS_OT_normal_map_dialog(bpy.types.Operator): """Generate a normal map""" bl_idname = 'image_editor_plus.normal_map_dialog' bl_label = "Normal Map" preview: bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'}, update=preview_normal_map_properties) reset: bpy.props.BoolProperty(options={'SKIP_SAVE'}, update=reset_normal_map_properties) scale: bpy.props.FloatProperty(name='Scale', subtype='FACTOR', min=0, soft_max=250.0, default=10.0, options={'SKIP_SAVE'}, update=update_normal_map_properties) flip_x: bpy.props.BoolProperty(name='Flip X', default=False, options={'SKIP_SAVE'}, update=update_normal_map_properties) flip_y: bpy.props.BoolProperty(name='Flip Y', default=False, options={'SKIP_SAVE'}, update=update_normal_map_properties) full_z: bpy.props.BoolProperty(name='Full Range for Z', default=False, options={'SKIP_SAVE'}, update=update_normal_map_properties) def invoke(self, context, event): session = app.get_session() wm = context.window_manager img = app.get_target_image(context) if not img: return {'CANCELLED'} app.cache_image(img, need_hsl=True) layer = app.get_active_layer(context) if layer: session.cached_layer_location = layer.location[:] update_normal_map_properties(self, context) return wm.invoke_props_dialog(self) def execute(self, context): bpy.ops.image_editor_plus.normal_map('EXEC_DEFAULT', False, scale=self.scale, flip_x=self.flip_x, flip_y=self.flip_y, full_z=self.full_z) return {'FINISHED'} def cancel(self, context): img = app.get_target_image(context) if not img: return {'CANCELLED'} app.revert_image_cache(img) app.clear_image_cache() app.refresh_image(context) def draw(self, context): layout = self.layout row = layout.split(align=True) row.column() row.prop(self, 'preview', text='Preview', toggle=True, icon='VIEWZOOM') layout.separator() row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Scale') row.prop(self, 'scale', text='') row = layout.split(align=True) row.column() row.prop(self, 'flip_x') row = layout.split(align=True) row.column() row.prop(self, 'flip_y') row = layout.split(align=True) row.column() row.prop(self, 'full_z') row = layout.split(factor=0.7) row.column() row.prop(self, 'reset', text='Reset', toggle=True) class IMAGE_EDITOR_PLUS_UL_layer_list(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): icon_value = 0 img = bpy.data.images.get(item.name) if img: icon_value = bpy.types.UILayout.icon(img) row = layout.row() row.alignment = 'EXPAND' row.prop(item, 'label', text='', emboss=False, icon_value=icon_value) hide_icon = 'HIDE_ON' if item.hide else 'HIDE_OFF' row.prop(item, 'hide', text='', emboss=False, icon=hide_icon) class IMAGE_EDITOR_PLUS_MT_edit_menu(bpy.types.Menu): bl_idname = "IMAGE_EDITOR_PLUS_MT_edit_menu" bl_label = "Edit" def draw(self, context): layout = self.layout layout.operator(operators.IMAGE_EDITOR_PLUS_OT_fill_with_fg_color.bl_idname, text='Fill with FG Color', icon_value=get_icon_id('fill')) layout.operator(operators.IMAGE_EDITOR_PLUS_OT_fill_with_bg_color.bl_idname, text='Fill with BG Color', icon_value=get_icon_id('fill')) layout.operator(operators.IMAGE_EDITOR_PLUS_OT_clear.bl_idname, text='Clear', icon_value=get_icon_id('clear')) layout.operator(operators.IMAGE_EDITOR_PLUS_OT_cut.bl_idname, text='Cut', icon_value=get_icon_id('cut')) layout.operator(operators.IMAGE_EDITOR_PLUS_OT_copy.bl_idname, text='Copy', icon='COPYDOWN') layout.operator(operators.IMAGE_EDITOR_PLUS_OT_paste.bl_idname, text='Paste', icon='PASTEDOWN') layout.operator(operators.IMAGE_EDITOR_PLUS_OT_crop.bl_idname, text='Crop', icon_value=get_icon_id('crop')) class IMAGE_EDITOR_PLUS_MT_layers_menu(bpy.types.Menu): bl_idname = "IMAGE_EDITOR_PLUS_MT_layers_menu" bl_label = "Pasted Layers" def draw(self, context): layout = self.layout layout.operator(operators.IMAGE_EDITOR_PLUS_OT_deselect_layer.bl_idname, text='Deselect Layer', icon_value=get_icon_id('deselect')) layout.operator(operators.IMAGE_EDITOR_PLUS_OT_move_layer.bl_idname, text='Move', icon_value=get_icon_id('move')) layout.operator(operators.IMAGE_EDITOR_PLUS_OT_rotate_layer.bl_idname, text='Rotate', icon_value=get_icon_id('rotate')) layout.operator(operators.IMAGE_EDITOR_PLUS_OT_scale_layer.bl_idname, text='Scale', icon_value=get_icon_id('scale')) layout.operator(operators.IMAGE_EDITOR_PLUS_OT_delete_layer.bl_idname, text='Delete', icon='TRASH') layout.operator(operators.IMAGE_EDITOR_PLUS_OT_merge_layers.bl_idname, text='Merge Layers', icon_value=get_icon_id('check')) class IMAGE_EDITOR_PLUS_MT_transform_menu(bpy.types.Menu): bl_idname = "IMAGE_EDITOR_PLUS_MT_transform_menu" bl_label = "Transform" def draw(self, context): layout = self.layout op = layout.operator(operators.IMAGE_EDITOR_PLUS_OT_flip.bl_idname, text='Flip Horizontally', icon_value=get_icon_id('flip_horz')) op.is_vertically = False op = layout.operator(operators.IMAGE_EDITOR_PLUS_OT_flip.bl_idname, text='Flip Vertically', icon_value=get_icon_id('flip_vert')) op.is_vertically = True op = layout.operator(operators.IMAGE_EDITOR_PLUS_OT_rotate.bl_idname, text="Rotate 90\u00b0 Left", icon_value=get_icon_id('rot_left')) op.is_left = True op = layout.operator(operators.IMAGE_EDITOR_PLUS_OT_rotate.bl_idname, text="Rotate 90\u00b0 Right", icon_value=get_icon_id('rot_right')) op.is_left = False op = layout.operator(IMAGE_EDITOR_PLUS_OT_scale_dialog.bl_idname, text='Scale...', icon_value=get_icon_id('scale')) op = layout.operator(IMAGE_EDITOR_PLUS_OT_change_canvas_size_dialog.bl_idname, text='Canvas Size...', icon_value=get_icon_id('scale')) class IMAGE_EDITOR_PLUS_MT_transform_layer_menu(bpy.types.Menu): bl_idname = "IMAGE_EDITOR_PLUS_MT_transform_layer_menu" bl_label = "Transform Layer" def draw(self, context): layout = self.layout op = layout.operator(operators.IMAGE_EDITOR_PLUS_OT_flip_layer.bl_idname, text='Flip Horizontally', icon_value=get_icon_id('flip_horz')) op.is_vertically = False op = layout.operator(operators.IMAGE_EDITOR_PLUS_OT_flip_layer.bl_idname, text='Flip Vertically', icon_value=get_icon_id('flip_vert')) op.is_vertically = True op = layout.operator(operators.IMAGE_EDITOR_PLUS_OT_rotate_layer.bl_idname, text="Rotate 90\u00b0 Left", icon_value=get_icon_id('rot_left')) op.is_left = True op = layout.operator(operators.IMAGE_EDITOR_PLUS_OT_rotate_layer.bl_idname, text="Rotate 90\u00b0 Right", icon_value=get_icon_id('rot_right')) op.is_left = False op = layout.operator(operators.IMAGE_EDITOR_PLUS_OT_rotate_layer_arbitrary.bl_idname, text="Rotate Arbitrary", icon_value=get_icon_id('rotate')) op = layout.operator(operators.IMAGE_EDITOR_PLUS_OT_scale_layer.bl_idname, text="Scale", icon_value=get_icon_id('scale')) class IMAGE_EDITOR_PLUS_MT_offset_menu(bpy.types.Menu): bl_idname = "IMAGE_EDITOR_PLUS_MT_offset_menu" bl_label = "Offset" def draw(self, context): layout = self.layout layout.operator(IMAGE_EDITOR_PLUS_OT_offset_dialog.bl_idname, text='Offset...', icon_value=get_icon_id('offset')) class IMAGE_EDITOR_PLUS_MT_adjust_menu(bpy.types.Menu): bl_idname = "IMAGE_EDITOR_PLUS_MT_adjust_menu" bl_label = "Adjust" def draw(self, context): layout = self.layout layout.operator(IMAGE_EDITOR_PLUS_OT_adjust_color_dialog.bl_idname, text='Hue/Saturation...', icon_value=get_icon_id('adjust')) layout.operator(IMAGE_EDITOR_PLUS_OT_adjust_brightness_dialog.bl_idname, text='Brightness/Contrast...', icon_value=get_icon_id('adjust')) layout.operator(IMAGE_EDITOR_PLUS_OT_adjust_gamma_dialog.bl_idname, text='Gamma...', icon_value=get_icon_id('adjust')) layout.operator(IMAGE_EDITOR_PLUS_OT_adjust_color_curve_dialog.bl_idname, text='Color Curve...', icon_value=get_icon_id('color_curve')) layout.operator(IMAGE_EDITOR_PLUS_OT_replace_color_dialog.bl_idname, text="Replace Color...", icon='COLOR') class IMAGE_EDITOR_PLUS_MT_filter_menu(bpy.types.Menu): bl_idname = "IMAGE_EDITOR_PLUS_MT_filter_menu" bl_label = "Filter" def draw(self, context): layout = self.layout layout.operator(IMAGE_EDITOR_PLUS_OT_blur_dialog.bl_idname, text="Blur...", icon_value=get_icon_id('filter')) layout.operator(IMAGE_EDITOR_PLUS_OT_sharpen_dialog.bl_idname, text="Sharpen...", icon_value=get_icon_id('filter')) layout.operator(IMAGE_EDITOR_PLUS_OT_add_noise_dialog.bl_idname, text="Add Noise...", icon_value=get_icon_id('filter')) layout.operator(IMAGE_EDITOR_PLUS_OT_pixelize_dialog.bl_idname, text="Pixelize...", icon_value=get_icon_id('filter')) layout.operator(IMAGE_EDITOR_PLUS_OT_make_seamless_dialog.bl_idname, text='Make Seamless...', icon_value=get_icon_id('filter')) layout.operator(IMAGE_EDITOR_PLUS_OT_normal_map_dialog.bl_idname, text='Normal Map...', icon_value=get_icon_id('filter')) class IMAGE_EDITOR_PLUS_PT_select_panel(bpy.types.Panel): bl_label = "Select" bl_space_type = "IMAGE_EDITOR" bl_region_type = "UI" bl_category = "Image Editor+" @classmethod def poll(cls, context): return context.area.spaces.active.mode != 'UV' \ and context.area.spaces.active.image != None \ and context.area.spaces.active.image.source != 'VIEWER' def draw(self, context): area_session = app.get_area_session(context) wm = context.window_manager props = wm.imageeditorplus_properties layout = self.layout if area_session.selecting: row = layout.row() row.enabled = False row.operator(operators.IMAGE_EDITOR_PLUS_OT_make_selection.bl_idname, text='Selecting...') else: if area_session.selection: row = layout.row() row.operator(operators.IMAGE_EDITOR_PLUS_OT_make_selection.bl_idname, text='Select Box', icon_value=get_icon_id('select')) row = layout.row() row.operator(operators.IMAGE_EDITOR_PLUS_OT_cancel_selection.bl_idname, text='Deselect', icon_value=get_icon_id('deselect')) else: row = layout.row() row.operator(operators.IMAGE_EDITOR_PLUS_OT_make_selection.bl_idname, text='Select Box', icon_value=get_icon_id('select')) if area_session.selection: img = context.area.spaces.active.image width, height = img.size selection = app.get_selection(context) x1 = selection[0][0] y1 = height - selection[1][1] x2 = selection[1][0] y2 = height - selection[0][1] row = layout.row() row.label(text='({}, {}) - ({}, {})'.format(x1, y1, x2, y2), icon_value=get_icon_id('select')) class IMAGE_EDITOR_PLUS_PT_edit_panel(bpy.types.Panel): bl_label = "Edit" bl_space_type = "IMAGE_EDITOR" bl_region_type = "UI" bl_category = "Image Editor+" @classmethod def poll(cls, context): return context.area.spaces.active.mode != 'UV' \ and context.area.spaces.active.image != None \ and context.area.spaces.active.image.source != 'VIEWER' def draw(self, context): wm = context.window_manager props = wm.imageeditorplus_properties layout = self.layout row = layout.split(factor=0.9) col = row.column() row2 = col.split(align=True) row2.prop(props, 'foreground_color', text='') row2.prop(props, 'background_color', text='') row.operator(operators.IMAGE_EDITOR_PLUS_OT_swap_colors.bl_idname, text='', icon='FILE_REFRESH', emboss=False) fill_clear_box = layout.box() row = fill_clear_box.row() row.operator(operators.IMAGE_EDITOR_PLUS_OT_fill_with_fg_color.bl_idname, text='Fill', icon_value=get_icon_id('fill')) row.operator(operators.IMAGE_EDITOR_PLUS_OT_clear.bl_idname, text='Clear', icon_value=get_icon_id('clear')) copy_paste_box = layout.box() row = copy_paste_box.row() row.operator(operators.IMAGE_EDITOR_PLUS_OT_cut.bl_idname, text='Cut', icon_value=get_icon_id('cut')) row.operator(operators.IMAGE_EDITOR_PLUS_OT_copy.bl_idname, text='Copy', icon='COPYDOWN') row.operator(operators.IMAGE_EDITOR_PLUS_OT_paste.bl_idname, text='Paste', icon='PASTEDOWN') row = layout.row() row.operator(operators.IMAGE_EDITOR_PLUS_OT_crop.bl_idname, text='Crop', icon_value=get_icon_id('crop')) class IMAGE_EDITOR_PLUS_PT_layers_panel(bpy.types.Panel): bl_label = "Pasted Layers" bl_space_type = "IMAGE_EDITOR" bl_region_type = "UI" bl_category = "Image Editor+" @classmethod def poll(cls, context): return context.area.spaces.active.mode != 'UV' \ and context.area.spaces.active.image != None \ and context.area.spaces.active.image.source != 'VIEWER' def draw(self, context): layout = self.layout img = context.area.spaces.active.image if img: img_props = img.imageeditorplus_properties layers = img_props.layers row = layout.row() row.template_list("IMAGE_EDITOR_PLUS_UL_layer_list", "", img_props, "layers", img_props, "selected_layer_index", rows=2) if layers: row = layout.row() row.operator(operators.IMAGE_EDITOR_PLUS_OT_deselect_layer.bl_idname, text='Deselect Layer', icon_value=get_icon_id('deselect')) row = layout.split(align=True) row.operator(operators.IMAGE_EDITOR_PLUS_OT_move_layer.bl_idname, text='', icon_value=get_icon_id('move')) op = row.operator(operators.IMAGE_EDITOR_PLUS_OT_change_image_layer_order.bl_idname, text='', icon="TRIA_UP") op.up = True op = row.operator(operators.IMAGE_EDITOR_PLUS_OT_change_image_layer_order.bl_idname, text='', icon="TRIA_DOWN") op.up = False row.operator(operators.IMAGE_EDITOR_PLUS_OT_delete_layer.bl_idname, text='', icon='TRASH') row = layout.row() row.operator(operators.IMAGE_EDITOR_PLUS_OT_merge_layers.bl_idname, text='Merge Layers', icon_value=get_icon_id('check')) class IMAGE_EDITOR_PLUS_PT_transform_panel(bpy.types.Panel): bl_label = "Transform" bl_space_type = "IMAGE_EDITOR" bl_region_type = "UI" bl_category = "Image Editor+" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): return context.area.spaces.active.mode != 'UV' \ and context.area.spaces.active.image != None \ and context.area.spaces.active.image.source != 'VIEWER' def draw(self, context): wm = context.window_manager layout = self.layout row = layout.split(factor=0.8) op = row.operator(operators.IMAGE_EDITOR_PLUS_OT_flip.bl_idname, text='Flip', icon_value=get_icon_id('flip_horz')) op.is_vertically = False op = row.operator(operators.IMAGE_EDITOR_PLUS_OT_flip.bl_idname, text='', icon_value=get_icon_id('flip_vert')) op.is_vertically = True row = layout.split(factor=0.8) op = row.operator(operators.IMAGE_EDITOR_PLUS_OT_rotate.bl_idname, text='Rotate', icon_value=get_icon_id('rot_left')) op.is_left = True op = row.operator(operators.IMAGE_EDITOR_PLUS_OT_rotate.bl_idname, text='', icon_value=get_icon_id('rot_right')) op.is_left = False row = layout.row() op = row.operator(IMAGE_EDITOR_PLUS_OT_scale_dialog.bl_idname, text='Scale...', icon_value=get_icon_id('scale')) row = layout.row() op = row.operator(IMAGE_EDITOR_PLUS_OT_change_canvas_size_dialog.bl_idname, text='Canvas Size...', icon_value=get_icon_id('scale')) class IMAGE_EDITOR_PLUS_PT_transform_layer_panel(bpy.types.Panel): bl_label = "Transform Layer" bl_space_type = "IMAGE_EDITOR" bl_region_type = "UI" bl_category = "Image Editor+" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): return context.area.spaces.active.mode != 'UV' \ and context.area.spaces.active.image != None \ and context.area.spaces.active.image.source != 'VIEWER' def draw(self, context): wm = context.window_manager layout = self.layout img = context.area.spaces.active.image if img: img_props = img.imageeditorplus_properties layers = img_props.layers selected_layer_index = img_props.selected_layer_index if selected_layer_index != -1 and selected_layer_index < len(layers): layer = layers[selected_layer_index] row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Location') row.prop(layer, 'location', text='') row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Rotation') row.prop(layer, 'rotation', text='') row = layout.split(align=True) row.alignment = 'RIGHT' row.label(text='Scale') row.prop(layer, 'scale', text='') row = layout.split(factor=0.8) op = row.operator(operators.IMAGE_EDITOR_PLUS_OT_flip_layer.bl_idname, text='Flip', icon_value=get_icon_id('flip_horz')) op.is_vertically = False op = row.operator(operators.IMAGE_EDITOR_PLUS_OT_flip_layer.bl_idname, text='', icon_value=get_icon_id('flip_vert')) op.is_vertically = True row = layout.split(factor=0.6) op = row.operator(operators.IMAGE_EDITOR_PLUS_OT_rotate_layer.bl_idname, text='Rotate', icon_value=get_icon_id('rot_left')) op.is_left = True row = row.split(factor=0.5) op = row.operator(operators.IMAGE_EDITOR_PLUS_OT_rotate_layer.bl_idname, text='', icon_value=get_icon_id('rot_right')) op.is_left = False row.operator(operators.IMAGE_EDITOR_PLUS_OT_rotate_layer_arbitrary.bl_idname, text='', icon_value=get_icon_id('rotate')) row = layout.row() row.operator(operators.IMAGE_EDITOR_PLUS_OT_scale_layer.bl_idname, text='Scale', icon_value=get_icon_id('scale')) class IMAGE_EDITOR_PLUS_PT_offset_panel(bpy.types.Panel): bl_label = "Offset" bl_space_type = "IMAGE_EDITOR" bl_region_type = "UI" bl_category = "Image Editor+" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): return context.area.spaces.active.mode != 'UV' \ and context.area.spaces.active.image != None \ and context.area.spaces.active.image.source != 'VIEWER' def draw(self, context): wm = context.window_manager layout = self.layout row = layout.row() row.operator(IMAGE_EDITOR_PLUS_OT_offset_dialog.bl_idname, text='Offset...', icon_value=get_icon_id('offset')) class IMAGE_EDITOR_PLUS_PT_adjust_panel(bpy.types.Panel): bl_label = "Adjust" bl_space_type = "IMAGE_EDITOR" bl_region_type = "UI" bl_category = "Image Editor+" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): return context.area.spaces.active.mode != 'UV' \ and context.area.spaces.active.image != None \ and context.area.spaces.active.image.source != 'VIEWER' def draw(self, context): wm = context.window_manager layout = self.layout row = layout.row() row.operator(IMAGE_EDITOR_PLUS_OT_adjust_color_dialog.bl_idname, text='Hue/Saturation...', icon_value=get_icon_id('adjust')) row = layout.row() row.operator(IMAGE_EDITOR_PLUS_OT_adjust_brightness_dialog.bl_idname, text='Brightness/Contrast...', icon_value=get_icon_id('adjust')) row = layout.row() row.operator(IMAGE_EDITOR_PLUS_OT_adjust_gamma_dialog.bl_idname, text='Gamma...', icon_value=get_icon_id('adjust')) row = layout.row() row.operator(IMAGE_EDITOR_PLUS_OT_adjust_color_curve_dialog.bl_idname, text='Color Curve...', icon_value=get_icon_id('color_curve')) row = layout.row() row.operator(IMAGE_EDITOR_PLUS_OT_replace_color_dialog.bl_idname, text='Replace Color...', icon='COLOR') class IMAGE_EDITOR_PLUS_PT_filter_panel(bpy.types.Panel): bl_label = "Filter" bl_space_type = "IMAGE_EDITOR" bl_region_type = "UI" bl_category = "Image Editor+" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): return context.area.spaces.active.mode != 'UV' \ and context.area.spaces.active.image != None \ and context.area.spaces.active.image.source != 'VIEWER' def draw(self, context): wm = context.window_manager layout = self.layout row = layout.row() row.operator(IMAGE_EDITOR_PLUS_OT_blur_dialog.bl_idname, text='Blur...', icon_value=get_icon_id('filter')) row = layout.row() row.operator(IMAGE_EDITOR_PLUS_OT_sharpen_dialog.bl_idname, text='Sharpen...', icon_value=get_icon_id('filter')) row = layout.row() row.operator(IMAGE_EDITOR_PLUS_OT_add_noise_dialog.bl_idname, text='Add Noise...', icon_value=get_icon_id('filter')) row = layout.row() row.operator(IMAGE_EDITOR_PLUS_OT_pixelize_dialog.bl_idname, text='Pixelize...', icon_value=get_icon_id('filter')) row = layout.row() row.operator(IMAGE_EDITOR_PLUS_OT_make_seamless_dialog.bl_idname, text='Make Seamless...', icon_value=get_icon_id('filter')) row = layout.row() row.operator(IMAGE_EDITOR_PLUS_OT_normal_map_dialog.bl_idname, text='Normal Map...', icon_value=get_icon_id('filter'))