#!/usr/bin/env python

#
# Author: Maksym Pyatnytskyy
# Version: 0.3
#
#
# Version history
#     0.3: speed improvement (using tiles)
#     0.2: initial working version
#
# Implementation of Subtractive Chromatic Noise Reduction (SCNR) from PixInsight.
# 	http://www.pixinsight.com/doc/legacy/LE/21_noise_reduction/scnr/scnr.html
#
# General structure of the script is based on "python_fu_test_discolour_layer_v4" sample script by Jose F. Maldonado (with bugfixes).
#

from gimpfu import *

def mp_astro_killing_green(img, layer, mode, maskAmount, debug = False):

    # Indicates that the process has started.
    progressMessage = "Killing Green " + layer.name + "." + " Mode = " + str(mode)
    if mode == 0 or mode == 1:
        progressMessage += "; Amount = " + str(maskAmount)
    gimp.progress_init(progressMessage)

    # Set up an undo group, so the operation will be undone in one step.
    pdb.gimp_image_undo_group_start(img)

    # Get the layer position.
    pos = 0;
    for i in range(len(img.layers)):
        if(img.layers[i] == layer):
            pos = i

    # Create a new layer to save the results (otherwise is not possible to undo the operation).
    newLayer = gimp.Layer(img, layer.name + " temp", layer.width, layer.height, layer.type, layer.opacity, layer.mode)
    img.add_layer(newLayer, pos)
    layerName = layer.name
    
    # Clear the new layer.
    pdb.gimp_edit_clear(newLayer)
    newLayer.flush()

    try:
        
        if(len(layer.get_pixel(0, 0)) < 3):
            raise ValueError('Not an RGB* image')
        
        # still working with 8-bit RGB, even in GIMP 2.9
        maxPixelComponentValue = 255.

        ## Calculate the number of tiles.
        ## M.P.: In GIMP 2.9.5 (or >2.8) basic size of tile is greater than 64x64 (128x128 in GIMP 2.9.5).
        ## So we have to define actual tile size first.
        srcTile = layer.get_tile(False, 0, 0)
        tile_basic_width = srcTile.ewidth
        tile_basic_height = srcTile.eheight
        
        if debug:
            pdb.gimp_message("tile_basic_width, tile_basic_height: " + str(tile_basic_width) + ", " + str(tile_basic_height))

        if tile_basic_width > layer.width or tile_basic_height > layer.height:
            raise ValueError('Something goes wrong: tile_basic_width > layer.width or tile_basic_height > layer.height')

        if tile_basic_width == layer.width:
            tn = 1
        else:
            tn = int(layer.width / tile_basic_width)
            if(layer.width % tile_basic_width > 0):
                tn += 1

        if tile_basic_height == layer.height:
            tm = 1
        else:
            tm = int(layer.height / tile_basic_height)
            if(layer.height % tile_basic_height > 0):
                tm += 1
                
        if debug:
            pdb.gimp_message("tn, tm: " + str(tn) + ", " + str(tm))

        # Iterate over the tiles.
       
        for i in range(tn):
            for j in range(tm):
                # Update the progress bar.
                gimp.progress_update(float(i*tm + j) / float(tn*tm))
        
                # Get the tiles.
                srcTile = layer.get_tile(False, j, i)
                dstTile = newLayer.get_tile(False, j, i)

                # Iterate over the pixels of each tile.
                for x in range(srcTile.ewidth):
                    for y in range(srcTile.eheight):
  
                        # Get the pixel...
                        pixel = srcTile[x, y]

                        red, green, blue = ord(pixel[0]), ord(pixel[1]), ord(pixel[2])
                        gNew = green

                        if mode == 0:
                            # Maximum Mask Protection
                            # m = max(R, B)
                            # G' = G * (1 - a) * (1 - m) + m * G
                            m = max(red, blue) / maxPixelComponentValue
                            gNew = int(green * (1.0 - maskAmount) * (1.0 - m) + m * green)
                        elif mode == 1:
                            #Additive Mask Protection
                            # m = min(1, R + B)
                            # G' = G * (1 - a) * (1 - m) + m * G (same as Maximum Mask)
                            m = min(maxPixelComponentValue, red + blue) / maxPixelComponentValue
                            gNew = int(green * (1.0 - maskAmount) * (1.0 - m) + m * green)
                        elif mode == 2:
                            # Average Neutral Protection
                            # m = 0.5 * (R + B)
                            # G' = min(G, m)
                            m = 0.5 * (red + blue)
                            gNew = int(min(green, m))
                        elif mode == 3:
                            # Maximum Neutral Protection
                            # m = max(R, B) (same as Maximum Mask)
                            # G' = min(G, m) (same as Average Neutral)
                            m = max(red, blue)
                            gNew = int(min(green, m))
                        else:
                            raise ValueError('Mode is not implemented')

                        newColor = chr(red) + chr(gNew) + chr(blue)

                        # If the image has an alpha channel (or any other channel) copy his values.
                        if(len(pixel) > 3):
                            for k in range(len(pixel) - 3):
                                newColor += pixel[k + 3]

                        # Save the value in the result layer.
                        dstTile[x, y] = newColor

        # Update the new layer.
        newLayer.flush()
        newLayer.merge_shadow(True)
        newLayer.update(0, 0, newLayer.width, newLayer.height)

        # Remove the old layer.
        img.remove_layer(layer)
        
        # Change the name of the new layer (two layers can not have the same name).
        newLayer.name = layerName

    except Exception as err:
        gimp.message("Unexpected error: " + str(err))

    # Close the undo group.
    pdb.gimp_image_undo_group_end(img)

    # End progress.
    pdb.gimp_progress_end()

register(
    "python_fu_astro_killing_green",
    "Killing Green (SCNR)",
    "Uses very simple SCRN algorithms (from PixInsight) to reduce greenish noise",
    "Max Pyatnytskyy",
    "Max Pyatnytskyy",
    "2016",
    "<Image>/Filters/M.P. Astro/Killing green (SCNR)...",
    "RGB*",
    [
        (PF_RADIO, "mode", "Mode:", 2, (("Maximum Mask *", 0), ("Additive Mask *", 1), ("Average Neutral", 2), ("Additive Neutral", 3))),
        (PF_SLIDER, "maskAmount", "* Amount (Masked modes only)", 0.7, (0, 1, 0.01)),
        #(PF_BOOL, "debug", "debug messages", False),
    ],
    [],
    mp_astro_killing_green)

main()