'''
    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 <http://www.gnu.org/licenses/>.
'''
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'))