#!/usr/bin/env python

#   Gimp-Python - allows the writing of Gimp plugins in Python.
#   Copyright (C) 1997  James Henstridge <james@daa.com.au>
#
#   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 2 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, write to the Free Software
#   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

# Copyright (C) 2008  Paul Taney
# bug reports or suggestions to paultaney@yahoo.com
# my examples were
#    retinex.py plugin -- Copyright (C) 2007, John Fremlin
#    clothify.py plugin -- Copyright (C) 1997, James Henstridge <james@daa.com.au>
#    foggify.py plugin -- Copyright (C) 1997, James Henstridge <james@daa.com.au>
#    average_layer.py plugin -- Copyright (C) 2008, Elmar Hoefner

TESTING = 0
if not TESTING:
    from gimpfu import *
import sys, os.path
import numpy
#import scipy # BREAKS on my installation

# in the future I hopw to use scipy clustering
# from scipy.cluster.vq import *  # has kmeans2 (http://hackmap.blogspot.com/2007/09/k-means-clustering-in-scipy.html)


if not TESTING:
    gettext.install("gimp20-python", gimp.locale_directory, unicode=True)


line_template ="""
<line x1="%s" y1="%s" x2="%s" y2="%s"
style="stroke:rgb(99,99,99);stroke-width:2"/>
"""

def getSVG(x1, y1, x2, y2):
    d = {"x1":str(x1), "y1":str(y1), "x2":str(x2), "y2":str(y2)}
    x1=str(x1); y1=str(y1); x2=str(x2); y2=str(y2)
    #if 1: print ">>>%r<<<" % d
    return line_template % (x1, y1, x2, y2)
  
header1 = """<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

<svg width="100%" height="100%" version="1.1"
xmlns="http://www.w3.org/2000/svg">

<!-- This SVG was generated by gimpfu plug-in at <Images>/Filters/Render/Stroke to SVG... -->
"""
  
header2 = """<!-- This is an SVG snippet representing a line. -->
<!-- This should not be considered a complete SVG file. -->
<!-- For instance, we dont handle line color (much). -->
"""

footer = """</svg>
"""
def writeSVG(filename, line, sx, sy, density):

    # line = flip_Y(line)
    # line = interpolate(line)
    # line = uniqify_x(line)

    stride = int((len(line)-1)/density/2)  # divide by 1.5 because... unzip() doubles the length
    if 1: print "writing %s, stride=%r" % (filename, stride)
    if stride < 1: stride = 1

    count = 1 
    acc = []
    for x1, y1, x2, y2 in unzip(line):
        if count % stride == 0:
            x1, y1, x2, y2 = x1*sx, y1*sy, x2*sx, y2*sy 
            acc.append(getSVG(x1, y1, x2, y2))
        count += 1

    """bug:  if linelength was an odd number it lost the last point"""
    if len(line)%2 != 0:
        acc.append(getSVG(line[-2][0]*sx, line[-2][1]*sy, line[-1][0]*sx, line[-1][1]*sy)) 

    FH = open(filename, 'w')
    FH.write(header1)
    #FH.write(header2)
    FH.write(" ".join(acc))
    FH.write(footer)
    FH.close()
    #pdb.gimp_message("wrote %s" % filename)

def writeCSV(filename, line, sx, sy, density):
    stride = int((len(line)-1)/density) 
    if 1: print "writing %s, stride=%r" % (filename, stride)
    if stride < 1: stride = 1

    count = 1 
    acc = []
    for x1, y1 in line:
        if count % stride == 0:
            acc.append("%.2f, %.2f," % (x1*sx, y1*sy))
        count += 1

    if len(line)%2 != 0:
        s = "%.2f, %.2f," % (line[-1][0]*sx, line[-1][1]*sy)
        acc.append(s)  # dont miss one

    FH = open(filename, 'w')
    FH.write("# CSV file generated by stroke_to_vector.py\n")
    FH.write("# a gimpfu plug-in at <Images>/Filters/Render/Stroke to SVG\n")
    FH.write("\n".join(acc))
    FH.write("\n")
    FH.close()
    #pdb.gimp_message("wrote %s" % filename)

def write_tuple(filename, line, sx, sy, density):

    # line = flip_Y(line)
    # line = interpolate(line)
    # line = uniqify_x(line)

    stride = int((len(line)-1)/density) 
    if 1: print "writing %s, stride=%r" % (filename, stride)
    if stride < 1: stride = 1

    count = 1 
    acc = []
    for x1, y1 in line:
        if count % stride == 0:
            x1, y1 = x1*sx, y1*sy
            acc.append("    (%.2f, %.2f)," % (x1, y1))
        count += 1

    """bug:  if linelength was an odd number it lost the last point"""
    if len(line)%2 != 0:
        s = "    (%.2f, %.2f)," % (line[-1][0]*sx, line[-1][1]*sy)
        acc.append(s)  # dont miss one

    FH = open(filename, 'w')
    FH.write("# file generated by stroke_to_vector.py\n")
    FH.write("# a gimpfu plug-in at <Images>/Filters/Render/Stroke to SVG...\n")
    FH.write("# scalefactors  %r, %r\n" % (sx, sy))
    FH.write("line = (\n")
    FH.write("\n".join(acc))
    FH.write("\n    )\n")
    FH.close()
    #pdb.gimp_message("wrote %s" % filename)

def unzip(L):
    """change 2-tuples to 4-tuple s for linedrawing""" 
    M = []
    while len(L)>=2:
        M.append((L[0][0], L[0][1], L[1][0], L[1][1])) 
        L = L[2:]
    return M


def vanderwalt(image, f):
    """thanks to Stefan van der Walt"""
    [ width, height, channels ] = image.shape
    RED, GRN, BLU = 0, 1, 2
    bluemask = (image[...,BLU] > f*image[...,GRN]) & \
               (image[...,BLU] > f*image[...,RED])

    return bluemask*3  # restore color depth or it is not seen as a gimp.Image


def read_in(drawable):
    """numpy array to gimp.Image, afaict.
    by John Fremlin"""
    width = drawable.width
    height = drawable.height
    bpp = drawable.bpp
    pr = drawable.get_pixel_rgn(0, 0, width, height, False)
#     image = numpy.zeros((height,width,3),"d")
#     for y in range(height):
#         for x in range(width):
#             image[y,x,:] = struct.unpack('BBB',pr[x,y][0:3])
           
#         progress_update("read-in", (y / float(height)))

    a = numpy.fromstring(pr[:,:],"B")
    assert(a.size == width * height * bpp)
    image = numpy.array(a.reshape(height,width,bpp),"d")[:,:,0:min(bpp,3)]  # Travis O. page 133
    return image/256.0  # demoting to bytes because "d" promoted the array momentarily.
                        # (its a promotion/demotion trick.  Why?  I dunno...)

def write_out(drawable, image):
    """by John Fremlin"""
    byte_image = numpy.array((image*256).round(0),"B")  # promoting.  Why? I dunno...
    width = drawable.width
    height = drawable.height
    bpp = drawable.bpp
    sys.stderr.write("write_out::drawable.width=%i drawable.height=%i drawable.bpp=%i (%i)\n" % (width, height, bpp, width*height*bpp))
    assert(width)
    assert(height)

    pr = drawable.get_pixel_rgn(0, 0, width, height, True)
    assert(byte_image.size == width * height * bpp)
    # blit!
    pr[:,:] = byte_image.tostring()
    # doesnt return anything, so a pixelregion is real estate!  He"s blitting.

#     for y in range(height):
#         for x in range(width):
#             pr[x,y] = struct.pack('BBB',*byte_image[y,x,:])
           
#         progress_update("write-out", (y / float(height)))

def progress_update(stage, proportion_done):
    gimp.progress_update(proportion_done*0.10)

#def retinex_plugin(img, drawable, new_layer_name, opacity, logscale, flatten):
def stroke_to_vector_plugin(original, drawable, new_layer_name, factor, flatten, scalefactorx, scalefactory, density, swap, outputformat, filename):

    #assert not pdb.gimp_selection.is_empty  # perhaps they must select an area for me to get the "drawable"?

    width, height, bpp = drawable.width, drawable.height, 3
    assert(width)
    assert(height)

    original.undo_group_start()

    try:
        image = read_in(drawable)

        bluemask_x3 = vanderwalt(image, factor)

        if 1 or swap:
            line = numpy.array(bluemask_x3.nonzero()).swapaxes(0,1).tolist()
        else:
            line = numpy.array(bluemask_x3.nonzero()).tolist()

        """output now -- before the trouble starts"""
        if 0: print "outputformat= >>>%s<<<" %  outputformat
        if outputformat == "SVG":
            writeSVG(filename, line, scalefactorx, scalefactory, density)
        elif outputformat == "python":
            write_tuple(filename, line, scalefactorx, scalefactory, density)
        elif outputformat == "CSV":
            writeCSV(filename, line, scalefactorx, scalefactory, density)
        else:
            sys.stdout.write("No output format selected.\n")

        opacity = 100
        #new_layer = gimp.Layer(bluemask, new_layer_name, width, height, 
                               #RGB_IMAGE, opacity, NORMAL_MODE)  # error: must be a gimp.Image, not a numpy.array

        David_says = """You are passing an RGB array to this function. Are you certain that
        your destination drawable does not have an alpha channel (thus requiring
        you to pass an RGBA array)?"""

        if drawable.has_alpha:
            img = gimp.Image(width, height, RGBA_IMAGE)  # note: could use original here (but this is all there is to making a gimp.image)
            new_layer = gimp.Layer(img, new_layer_name, width, height, 
                               RGBA_IMAGE, opacity, NORMAL_MODE)
        else:
            img = gimp.Image(width, height, RGB_IMAGE)
            new_layer = gimp.Layer(img, new_layer_name, width, height, 
                               RGB_IMAGE, opacity, NORMAL_MODE)


        # this section fails in write_out()

        #b = numpy.array((bluemask.nonzero()*3).reshape(height, width, 3),"b") # 'tuple' object has no attribute 'reshape'
        #a = numpy.array(bluemask.nonzero())  # note: nonzero() returns the indices of the elements of a which are not zero.

        write_out(new_layer, bluemask_x3)    
        #write_out(new_layer, image)    
        original.add_layer(new_layer, 0)

        if flatten:
            original.flatten()

    finally:
        original.undo_group_end()


if not TESTING:
        register(
        "python-fu-stroke-to-vector",  # name
	"Render bluest pixels to a new layer and optionally save vector. (v0.1)",  # blurb
        N_("""Retain the bluest pixels in a new layer and save as a vector
where blue sub-pixels are greater than red and green by some factor 
(default 40%).  Why just blue?  because I am handling stripcharts with
blue ink on them.  If you have another application, I could add RADIOs."""),  # help
        "Paul Taney",  # author
        "Paul Taney",  # copyright
        "2008",  # date
        N_("Stroke To SVG..."),  # gimp menu
        "RGB*",  # image arguments?
        [
            (PF_IMAGE, "image", "Input image", None),
            (PF_DRAWABLE, "drawable", "Input drawable", None),
            (PF_STRING, "name", _("New _layer name"), _("blueline")),
            #(PF_SLIDER, "opacity", _("Op_acity"),    25, (0, 100, 1)),
            (PF_SLIDER, "factor", _("How blue?"),    1.4, (1, 2, .1)),
            (PF_BOOL,   "flatten", _("_Flatten layers together after processing"), False),
            (PF_FLOAT,  "scalefactorx", "scale factor x",    "1"),    	
            (PF_FLOAT,  "scalefactory", "scale factor y",    "1"),    	
            (PF_SLIDER, "density", _("Line _density"),    50, (20, 200, 10)),
            (PF_BOOL,   "swapaxes", _("Swap axes"), False),
	    (PF_RADIO,  "outputformat", "Output format:",  "None", (
		("None", "None"),
		("SVG",  "SVG"),
		("CSV",  "CSV"),
		("python tuple", "python"))), 	
            #(PF_FILENAME, "filename", "Output file:", os.path.expanduser("~/tmp.svg")),  # Beazley p326, fails trying to convert to Unicode...
            (PF_FILENAME, "filename", "Output file:", "tmp.svg"), 
        ],
        [],
        stroke_to_vector_plugin,
        menu="<Image>/Filters/Render/",
        domain=("gimp20-python", gimp.locale_directory)
        )

if not TESTING:
    main() # gimpfu
else:
    L = [(25, 26), 
         (50, 51), 
         (100, 101), 
         (150, 151), 
         (200, 201), 
         (250, 251), 
         (300, 301), 
         (350, 351), 
         (400, 401),
         (450, 451),
         (500, 501), 
         (550, 551), 
         (600, 601),
         (650, 651),
         (700, 701), 
         (750, 751), 
         (800, 801),
         (850, 851),
         (900, 901), 
         (950, 951), 
         (1050, 1051),
         ]

    #getSVG(25, 26, 50, 51)
    density = 1
    #writeSVG("tmp1.svg", [(25, 26), (50, 51), (100, 101)], 1, 1, density)
    density = 3
    writeSVG("tmp3.svg", L, 1, 1, density)
    density = 4
    writeSVG("tmp4.svg", L, 1, 1, density)
    density = 5
    writeSVG("tmp5.svg", L, 1, 1, density)
    density = 6
    writeSVG("tmp6.svg", L, .2, .4, density)

    density = 3
    write_tuple("tmp3.dat", L, 1, 1, density)
    density = 4
    write_tuple("tmp4.dat", L, 1, 1, density)
    density = 5
    write_tuple("tmp5.dat", L, 1, 1, density)
    density = 6
    write_tuple("tmp6.dat", L, .2, .4, density)

    density = 6
    writeCSV("tmp7.csv", L, .2, .4, density)

Note = """
find . -name \*scm -exec grep gimp-image {} \; | grep -v width | grep -v heig | perl -ne '@a=split; print "$a[0]\n"' | sort -d | uniq >
ALL_IMAGE_CMDS.txt 
(gimp-context-set-background
(gimp-drawable-fill
(gimp-drawable-set-visible
(gimp-image-add-channel
(gimp-image-add-hguide
(gimp-image-add-layer
(gimp-image-add-vguide
(gimp-image-clean-all
(gimp-image-convert-indexed
(gimp-image-convert-rgb
(gimp-image-crop
(gimp-image-delete
(gimp-image-delete-guide
(gimp-image-flatten
(gimp-image-lower-layer
(gimp-image-lower-layer-to-bottom
(gimp-image-merge-down
(gimp-image-merge-visible-layers
(gimp-image-raise-layer
(gimp-image-remove-channel
(gimp-image-remove-layer
(gimp-image-resize
(gimp-image-scale
(gimp-image-set-active-channel
(gimp-image-set-active-layer
(gimp-image-set-colormap
(gimp-image-set-component-active
(gimp-image-set-filename
(gimp-image-undo-disable
(gimp-image-undo-enable
(gimp-image-undo-group-end
(gimp-image-undo-group-start
"""
