/* ImageMagick operations edited by Alan Gibson (aka "snibgo"; snibgo at earthling dot net). 1-Apr-2014 Minor corrections to Geometry_widget and Alpha. Added loads of widgets and Menuactions. Not fully tested. 5-Apr-2014 Many more menu actions. Reorganised Magick menu. 10-Apr-2014 Many more menu actions. 11-Apr-2014 jcupitt Split to separate _magick.def Add 0-ary and 2-ary system Put utility funcs into a Magick class 11-Apr-2014 snibgo Added VirtualPixelBack for cases where background is only relevant when VP=Background 17-Apr-2014 snibgo Many small changes. 2-May-2014 jcupitt Added Magick.version 30-June-2014 Put single-quotes around command exe to help win 1-July-2014 Automatically fall back to gm if we can't find convert 17-July-2014 better GM support Last update: 17-July-2014. For details of ImageMagick operations, see http://www.imagemagick.org/script/command-line-options.php etc. */ /* Put these in a class to avoid filling the main namespace with IM stuff. */ Magick = class { // turn $PATH into [["comp1", "comp2"], ["comp1", "comp2"] ..] system_search_path = map path_parse (split (equal path_sep) (expand "$PATH")) { path_sep = ':', expand "$SEP" == "/" = ';'; } // the first name[.exe] on $PATH, or "" search_for name = hits?0, hits != [] = "" { exe_name = name ++ expand "$EXEEXT"; form_path p = path_absolute (p ++ [exe_name]); paths = map form_path system_search_path; hits = dropwhile (equal []) (map search paths); } // first gm on path, or "" gm_path = search_for "gm"; // first convert on $PATH, or "" // we check for the convert we ship first convert_path = vips_convert, vips_convert != "" = search_for "convert" { // the convert we ship with the vips binary on some platforms, or "" vips_convert = search (path_absolute convert) { vipshome = path_parse (expand "$VIPSHOME"); convert = vipshome ++ ["bin", "convert" ++ expand "$EXEEXT"]; } } use_gm_pref = Workspaces.Preferences.USE_GRAPHICSMAGICK; // Are we in GM or IM mode? use_gm = true, use_gm_pref && gm_path != "" = false, !use_gm_pref && convert_path != "" = false, convert_path != "" = true, gm_path != "" = error "neither IM nor GM executable found"; command_path = gm_path, use_gm = convert_path; // try to get the version as eg. [6, 7, 7, 10] // GM versions are smaller, typically [1, 3, 18] version = map parse_int (split (member ".-") version_string) { [output] = vips_call "system" ["'" ++ command_path ++ "' -version"] [$log=>true]; version_string = (split (equal ' ') output)?1, use_gm = (split (equal ' ') output)?2; } // make a command-line ... args is a [str] we join with spaces command args = "'" ++ command_path ++ "' " ++ join_sep " " args' { args' = ["convert"] ++ args, use_gm = args; } // capabilities ... different versions support different features, we // turn features on and off based on these // would probably be better to test for caps somehow has_intensity = false, use_gm = version?0 > 6 || version?1 > 7; has_channel = false, use_gm = version?0 > 6 || version?1 > 7; system0 cmd = system_image0 cmd; system cmd x = map_unary (system_image cmd) x; system2 cmd x y = map_binary (system_image2 cmd) x y; system3 cmd x y z = map_trinary (system_image3 cmd) x y z; radius_widget = Scale "Radius" 0 100 10; sigma_widget = Scale "Sigma" 0.1 10 1; angle_widget = Scale "Angle (degrees)" (-360) 360 0; text_widget = String "Text to draw" "AaBbCcDdEe"; gamma_widget = Scale "Gamma" 0 10 1; colors_widget = Scale "Colors" 1 10 3; resize_widget = Scale "Resize (percent)" 0 500 100; fuzz_widget = Scale "Fuzz (percent)" 0 100 0; blur_rad_widget = Scale "Radius (0=auto)" 0 100 0; // a colour with no enclosing quotes ... use this if we know there are // some quotes at an outer level print_colour_nq triple = concat ["#", concat (map fmt triple)] { fmt x = reverse (take 2 (reverse (print_base 16 (x + 256)))); } // we need the quotes because # is the comment character in *nix print_colour triple = "\"" ++ print_colour_nq triple ++ "\""; Foreground triple = class Colour "sRGB" triple { _flag = "-fill " ++ print_colour triple; Colour_edit space triple = this.Foreground triple; } foreground_widget = Foreground [0, 0, 0]; GeneralCol triple = class Colour "sRGB" triple { _flag = print_colour_nq triple; Colour_edit space triple = this.GeneralCol triple; } generalcol_widget = GeneralCol [0, 0, 0]; Background triple = class Colour "sRGB" triple { isNone = Toggle "None (transparent black)" false; _flag = "-background " ++ if isNone then "None" else print_colour triple; Colour_edit space triple = this.Background triple; } background_widget = Background [255, 255, 255]; Bordercol triple = class Colour "sRGB" triple { _flag = "-bordercolor " ++ print_colour triple; Colour_edit space triple = this.Bordercol triple; } bordercol_widget = Bordercol [0, 0, 0]; Mattecol triple = class Colour "sRGB" triple { _flag = "-mattecolor " ++ print_colour triple; Colour_edit space triple = this.Mattecol triple; } mattecol_widget = Mattecol [189, 189, 189]; // FIXME: Undercolour, like many others, can have alpha channel. // How does user input this? With a slider? Undercol triple = class Colour "sRGB" triple { isNone = Toggle "None (transparent black)" true; _flag = if isNone then "" else ("-undercolor " ++ print_colour triple); Colour_edit space triple = this.Undercol triple; } undercol_widget = Undercol [0, 0, 0]; changeCol_widget = class { _vislevel = 3; colour = GeneralCol [0, 0, 0]; fuzz = fuzz_widget; nonMatch = Toggle "change non-matching colours" false; } Alpha alpha = class Option_string "Alpha" [ "On", "Off", "Set", "Opaque", "Transparent", "Extract", "Copy", "Shape", "Remove", "Background" ] alpha { _flag = "-alpha " ++ alpha; Option_edit caption labels value = this.Alpha labels?value; } alpha_widget = Alpha "On"; Antialias value = class Toggle "Antialias" value { _flag = "-antialias", value = "+antialias"; Toggle_edit caption value = this.Antialias value; } antialias_widget = Antialias true; Builtin builtin = class Option_string "Builtin" [ // See http://www.imagemagick.org/script/formats.php "rose:", "logo:", "wizard:", "granite:", "netscape:" ] builtin { _flag = builtin; Option_edit caption labels value = this.Builtin labels?value; } builtin_widget = Builtin "rose:"; channels_widget = class { // FIXME? Can we grey-out alpha when we have no alpha channel, // show CMY(K) instead of RGB(K) etc? // Yes, perhaps we can create different widgets for RGB, RGBA, CMY, CMYK, CMYA, CMYKA. ChanR valueR = class Toggle "Red" valueR { _flag = "R", valueR = ""; Toggle_edit caption valueR = this.ChanR valueR; } channelR = ChanR true; ChanG valueG = class Toggle "Green" valueG { _flag = "G", valueG = ""; Toggle_edit caption valueG = this.ChanG valueG; } channelG = ChanG true; ChanB valueB = class Toggle "Blue" valueB { _flag = "B", valueB = ""; Toggle_edit caption valueB = this.ChanB valueB; } channelB = ChanB true; ChanK valueK = class Toggle "Black" valueK { _flag = "K", valueK = ""; Toggle_edit caption valueK = this.ChanK valueK; } channelK = ChanK true; ChanA valueA = class Toggle "Alpha" valueA { _flag = "A", valueA = ""; Toggle_edit caption valueA = this.ChanA valueA; } channelA = ChanA false; ChanSy valueSy = class Toggle "Sync" valueSy { _flag = ",sync", valueSy = ""; Toggle_edit caption valueSy = this.ChanSy valueSy; } channelSy = ChanSy true; _rgbka = concat [channelR._flag, channelG._flag, channelB._flag, channelK._flag, channelA._flag ]; _flag = "", _rgbka == "" || !has_channel = concat [ "-channel ", _rgbka, channelSy._flag ]; } ch_widget = channels_widget; Colorspace colsp = class Option_string "Colorspace" [ "CIELab", "CMY", "CMYK", "Gray", "HCL", "HCLp", "HSB", "HSI", "HSL", "HSV", "HWB", "Lab", "LCH", "LCHab", "LCHuv", "LMS", "Log", "Luv", "OHTA", "Rec601Luma", "Rec601YCbCr", "Rec709Luma", "Rec709YCbCr", "RGB", "scRGB", "sRGB", "Transparent", "XYZ", "YCbCr", "YDbDr", "YCC", "YIQ", "YPbPr", "YUV" ] colsp { _flag = colsp; Option_edit caption labels value = this.Colorspace labels?value; } colorspace_widget = Colorspace "sRGB"; Compose comp = class Option_string "Compose method" [ "Atop", "Blend", "Blur", "Bumpmap", "ChangeMask", "Clear", "ColorBurn", "ColorDodge", "Colorize", "CopyBlack", "CopyBlue", "CopyCyan", "CopyGreen", "Copy", "CopyMagenta", "CopyOpacity", "CopyRed", "CopyYellow", "Darken", "DarkenIntensity", "DivideDst", "DivideSrc", "Dst", "Difference", "Displace", "Dissolve", "Distort", "DstAtop", "DstIn", "DstOut", "DstOver", "Exclusion", "HardLight", "Hue", "In", "Lighten", "LightenIntensity", "LinearBurn", "LinearDodge", "LinearLight", "Luminize", "Mathematics", "MinusDst", "MinusSrc", "Modulate", "ModulusAdd", "ModulusSubtract", "Multiply", "None", "Out", "Overlay", "Over", "PegtopLight", "PinLight", "Plus", "Replace", "Saturate", "Screen", "SoftLight", "Src", "SrcAtop", "SrcIn", "SrcOut", "SrcOver", "VividLight", "Xor" ] comp { _flag = "-compose " ++ comp; Option_edit caption labels value = this.Compose labels?value; } compose_widget = Compose "Over"; // FIXME: Some compose mehods (Displace, Distort, Mathematics) need a string. // FIXME: we could use a class that does both -compose and -intensity, for methods LightenIntensity, DarkenIntensity, CopyOpacity, CopyBlack coordinate_widget = class { _vislevel = 3; x = Expression "X" 0; y = Expression "Y" 0; _flag = concat [print x.expr, ",", print y.expr]; }; Distort distort = class Option_string "Distort" [ "Affine", "AffineProjection", "ScaleRotateTranslate", "SRT", "Perspective", "PerspectiveProjection", "BilinearForward", "BilinearReverse", "Polynomial", "Arc", "Polar", "DePolar", "Barrel", "BarrelInverse", "Shepards", "Resize" ] distort { _flag = distort; Option_edit caption labels value = this.Distort labels?value; } distort_widget = Distort "SRT"; Dither dither = class Option_string "Dither" [ "None", "FloydSteinberg", "Riemersma" ] dither { _flag = "-dither " ++ dither; Option_edit caption labels value = this.Dither labels?value; } dither_widget = Dither "FloydSteinberg"; Evaluate eval = class Option_string "Evaluate operation" [ "Abs", "Add", "AddModulus", "And", "Cos", "Cosine", "Divide", "Exp", "Exponential", "GaussianNoise", "ImpulseNoise", "LaplacianNoise", "LeftShift", "Log", "Max", "Mean", "Median", "Min", "MultiplicativeNoise", "Multiply", "Or", "PoissonNoise", "Pow", "RightShift", "Set", "Sin", "Sine", "Subtract", "Sum", "Threshold", "ThresholdBlack", "ThresholdWhite", "UniformNoise", "Xor" ] eval { _flag = "-evaluate " ++ eval; Option_edit caption labels value = this.Evaluate labels?value; } evaluate_widget = Evaluate "Add"; Filter filt = class Option_string "Filter" [ "default", "Bartlett", "Blackman", "Bohman", "Box", "Catrom", "Cosine", "Cubic", "Gaussian", "Hamming", "Hann", "Hermite", "Jinc", "Kaiser", "Lagrange", "Lanczos", "Lanczos2", "Lanczos2Sharp", "LanczosRadius", "LanczosSharp", "Mitchell", "Parzen", "Point", "Quadratic", "Robidoux", "RobidouxSharp", "Sinc", "SincFast", "Spline", "Triangle", "Welch" ] filt { _flag = if filt == "default" then "" else "-filter " ++ filt; Option_edit caption labels value = this.Filter labels?value; } filter_widget = Filter "default"; Function func = class Option_string "Function" [ "Polynomial", "Sinusoid", "Arcsin", "Arctan" ] func { _flag = func; Option_edit caption labels value = this.Function labels?value; } function_widget = Function "Polynomial"; // "Polynomial (a[n], a[n-1], ... a[1], a[0])", // "Sinusoid (freq, phase, amp, bias)", // "Arcsin (width, centre, range, bias)", // "Arctan (slope, centre, range, bias)" Gravity gravity = class Option_string "Gravity" [ "None", "Center", "East", "Forget", "NorthEast", "North", "NorthWest", "SouthEast", "South", "SouthWest", "West", "Static" ] gravity { _flag = "-gravity " ++ gravity; Option_edit caption labels value = this.Gravity labels?value; } gravity_widget = Gravity "Center"; ImageType imagetype = class Option_string "Image type" [ "Bilevel", "ColorSeparation", "ColorSeparationAlpha", "ColorSeparationMatte", "Grayscale", "GrayscaleAlpha", "GrayscaleMatte", "Optimize", "Palette", "PaletteBilevelAlpha", "PaletteBilevelMatte", "PaletteAlpha", "PaletteMatte", "TrueColorAlpha", "TrueColorMatte", "TrueColor" ] imagetype { _flag = "-type " ++ imagetype; Option_edit caption labels value = this.ImageType labels?value; } imagetype_widget = ImageType "TrueColor"; Intensity intensity = class Option_string "Intensity (gray conversion)" [ "Average", "Brightness", "Lightness", "MS", "Rec601Luma", "Rec601Luminance", "Rec709Luma", "Rec709Luminance", "RMS" ] intensity { _flag = "-intensity " ++ intensity, has_intensity = ""; Option_edit caption labels value = this.Intensity labels?value; } intensity_widget = Intensity "Rec709Luminance"; Interpolate interp = class Option_string "Interpolate" [ "default", "Average", "Average4", "Average9", "Average16", "Background", "Bilinear", "Blend", "Integer", "Mesh", "Nearest", "NearestNeighbor", "Spline" ] interp { _flag = if interp == "default" then "" else "-interpolate " ++ interp; Option_edit caption labels value = this.Interpolate labels?value; } interpolate_widget = Interpolate "default"; Kernel kernel = class Option_string "Kernel" [ "Unity", "Gaussian", "DoG", "LoG", "Blur", "Comet", "Binomial", "Laplacian", "Sobel", "FreiChen", "Roberts", "Prewitt", "Compass", "Kirsch", "Diamond", "Square", "Rectangle", "Disk", "Octagon", "Plus", "Cross", "Ring", "Peaks", "Edges", "Corners", "Diagonals", "LineEnds", "LineJunctions", "Ridges", "ConvexHull", "ThinSe", "Skeleton", "Chebyshev", "Manhattan", "Octagonal", "Euclidean" // FIXME: custom kernel ] kernel { _flag = kernel; Option_edit caption labels value = this.Kernel labels?value; } kernel_widget = Kernel "Unity"; ModColSp msp = class Option_string "modulate colorspace" [ "HCL", "HCLp", "HSB", "HSI", "HSL", "HSV", "HWB", "LCH" ] msp { _flag = "-set option:modulate:colorspace " ++ msp; Option_edit caption labels value = this.ModColSp labels?value; } ModColSp_widget = ModColSp "HSL"; MorphMeth morph = class Option_string "Method" [ "Correlate", "Convolve", "Dilate", "Erode", "Close", "Open", "DilateIntensity", "ErodeIntensity", "CloseIntensity", "OpenIntensity", "Smooth", "EdgeOut", "EdgeIn", "Edge", "TopHat", "BottomHat", "HitAndMiss", "Thinning", "Thicken", "Distance", "IterativeDistance" ] morph { _flag = morph; Option_edit caption labels value = this.MorphMeth labels?value; } morphmeth_widget = MorphMeth "Dilate"; Noise noise = class Option_string "Noise" [ "Gaussian", "Impulse", "Laplacian", "Multiplicative", "Poisson", "Random", "Uniform" ] noise { _flag = "+noise " ++ noise; Option_edit caption labels value = this.Noise labels?value; } noise_widget = Noise "Gaussian"; Pattern pattern = class Option_string "Noise" [ // See http://www.imagemagick.org/script/formats.php "bricks", "checkerboard", "circles", "crosshatch", "crosshatch30", "crosshatch45", "gray0", "gray5", "gray10", "gray15", "gray20", "gray25", "gray30", "gray35", "gray40", "gray45", "gray50", "gray55", "gray60", "gray65", "gray70", "gray75", "gray80", "gray85", "gray90", "gray95", "gray100", "hexagons", "horizontal", "horizontal2", "horizontal3", "horizontalsaw", "hs_bdiagonal", "hs_cross", "hs_diagcross", "hs_fdiagonal", "hs_horizontal", "hs_vertical", "left30", "left45", "leftshingle", "octagons", "right30", "right45", "rightshingle", "smallfishscales", "vertical", "vertical2", "vertical3", "verticalbricks", "verticalleftshingle", "verticalrightshingle", "verticalsaw" ] pattern { _flag = "pattern:" ++ pattern; Option_edit caption labels value = this.Pattern labels?value; } pattern_widget = Pattern "bricks"; ResizeType resizet = class Option_string "Resize type" [ "resize", "scale", "sample", "adaptive-resize" ] resizet { _flag = resizet; Option_edit caption labels value = this.ResizeType labels?value; } ResizeType_widget = ResizeType "resize"; Size_widget = class { _vislevel = 3; width = Expression "Width (pixels)" 64; height = Expression "Height (pixels)" 64; _flag = "-size " ++ print width.expr ++ "x" ++ print height.expr; }; StatType statt = class Option_string "Statistic type" [ "Gradient", "Maximum", "Mean", "Median", "Minimum", "Mode", "Nonpeak", "StandardDeviation" ] statt { _flag = statt; Option_edit caption labels value = this.StatType labels?value; } StatType_widget = StatType "Mean"; VirtualPixel vp = class Option_string "Virtual pixel" [ "Background", "Black", "CheckerTile", "Dither", "Edge", "Gray", "HorizontalTile", "HorizontalTileEdge", "Mirror", "None", "Random", "Tile", "Transparent", "VerticalTile", "VerticalTileEdge", "White" ] vp { _flag = "-virtual-pixel " ++ vp; _isBackground = (vp == "Background"); Option_edit caption labels value = this.VirtualPixel labels?value; } VirtualPixel_widget = VirtualPixel "Edge"; VirtualPixelBack_widget = class { virtpix = Magick.VirtualPixel_widget; background = Magick.background_widget; _flag = (if virtpix._isBackground then (background._flag ++ " ") else "") ++ virtpix._flag; } Geometry_widget = class { _vislevel = 3; x = Expression "X" 0; y = Expression "Y" 0; hoffset = Expression "Horizontal offset" 0; voffset = Expression "Vertical offset" 0; _flag = concat [print x.expr, "x", print y.expr, format hoffset, format voffset] { // print an offset ... we want '+' in front of +ve strings format offset = concat ["+", print offset.expr], offset.expr >= 0 = print offset.expr; } }; AnnotGeometry_widget = class { _vislevel = 3; shearX = Expression "shear X (degrees)" 0; shearY = Expression "shear Y (degrees)" 0; hoffset = Expression "Horizontal offset" 0; voffset = Expression "Vertical offset" 0; _flag = concat [print shearX.expr, "x", print shearY.expr, format hoffset, format voffset] { // print an offset ... we want '+' in front of +ve strings format offset = concat ["+", print offset.expr], offset.expr >= 0 = print offset.expr; } }; OffsetGeometry_widget = class { _vislevel = 3; hoffset = Expression "Horizontal offset" 0; voffset = Expression "Vertical offset" 0; _flag = concat [format hoffset, format voffset] { // print an offset ... we want '+' in front of +ve strings format offset = concat ["+", print offset.expr], offset.expr >= 0 = print offset.expr; } }; WhxyGeometry_widget = class { _vislevel = 3; x = Expression "Width" 0; y = Expression "Height" 0; hoffset = Expression "Horizontal offset" 0; voffset = Expression "Vertical offset" 0; _flag = concat [print x.expr, "x", print y.expr, format hoffset, format voffset] { // print an offset ... we want '+' in front of +ve strings format offset = concat ["+", print offset.expr], offset.expr >= 0 = print offset.expr; } }; FrameGeometry_widget = class { _vislevel = 3; x = Expression "Width" 0; y = Expression "Height" 0; outbev = Expression "Outer bevel thickness" 0; inbev = Expression "Inner bevel thickness" 0; _flag = concat [print x.expr, "x", print y.expr, format outbev, format inbev] { // print an offset ... we want '+' in front of +ve strings format offset = concat ["+", print offset.expr], offset.expr >= 0 = print offset.expr; } }; Font_widget = class { _vislevel = 3; family = Option_string "Family" [ "Arial", "ArialBlack", "AvantGarde", "BitstreamCharter", "Bookman", "CenturySchoolbook", "ComicSansMS", "Courier", "CourierNew", "DejaVuSans", "DejaVuSansMono", "DejaVuSerif", "Dingbats", "FreeMono", "FreeSans", "FreeSerif", "Garuda", "Georgia", "Helvetica", "HelveticaNarrow", "Impact", "LiberationMono", "LiberationSans", "LiberationSerif", "NewCenturySchlbk", "Palatino", "Purisa", "Symbol", "Times", "TimesNewRoman", "Ubuntu", "Verdana", "Webdings" ] "Arial"; style = Option_string "Style" [ "Any", "Italic", "Normal", "Oblique" ] "Normal"; weight = Scale "Weight" 1 800 400; size = Scale "Point size" 1 100 12; stretch = Option_string "Stretch" [ "Any", "Condensed", "Expanded", "ExtraCondensed", "ExtraExpanded", "Normal", "SemiCondensed", "SemiExpanded", "UltraCondensed", "UltraExpanded" ] "Normal"; _flag = join_sep " " [ "-family", family.item, "-weight", print weight.value, "-pointsize", print size.value, "-style", style.item, "-stretch", stretch.item]; } }