home *** CD-ROM | disk | FTP | other *** search
/ PC Extra 07 & 08 / pca1507.iso / Software / psp8 / Data1.cab / AutoTuber.PspScript < prev    next >
Encoding:
Text File  |  2003-04-22  |  18.8 KB  |  425 lines

  1. from JascApp import *
  2. from Tkinter import *
  3. import tkMessageBox
  4. import JascUtils
  5.  
  6. def ScriptProperties():
  7.     return {
  8.         'Author': 'Joe Fromm',
  9.         'Copyright': 'Copyright (C) 2002-2003, Jasc Software Inc., All Rights Reserved. Permission to create derivate works of this script is granted provided this copyright notice is included',
  10.         'Description': 'Combine all open images into a single image and export it as a tube',
  11.         'Host': 'Paint Shop Pro',
  12.         'Host Version': '8.00',
  13.         'ForceTextEditor': App.Constants.Boolean.true
  14.         }
  15.  
  16. # AutoTubing (as described by a private beta tester):
  17. # 1. Iterate open images and verify that each meets the requirement of being
  18. #    a single raster layer with transparency.  If not, give the user the choice
  19. #    of fixing the image, skipping the image, or canceling the export entirely.
  20. #    During this phase we find the largest source image, since we need to know
  21. #    how big the tube cells are.
  22. # 2. Next compute the best fit for tube layout - best fit is described as closest
  23. #    to square, but the user can override these choices in a dialog.  They number
  24. #    of cells they create must be sufficient for the number of images that will be
  25. #    tubed.
  26. # 3. Create a new image big enough to hold all of the images.
  27. # 4. For possible future editing, define the grid on the image to match the number of cells
  28. # 5. Iterate all the images that will be in the tube, copy them to the clipboard,
  29. #    then paste them into the tube image as a new selection.  We set the offset on the
  30. #    paste operation so that each lands in the right spot.
  31. # 6. Invoke the tube exporter, with cells across, cells down and total cells set properly.
  32. #    Step size is initialized to the cell width.
  33.  
  34. class CellCountDlg(Frame):
  35.     ''' define the dialog used to prompt the user for the number of cells'''
  36.     def __init__( self, parent, title, NumCols, NumRows, TotalCells ):
  37.         Frame.__init__(self)    # init our parent
  38.  
  39.         # if we exit with OK this will be set to 1.  A zero means we pressed cancel
  40.         self.OKPressed = 0
  41.         self.ImageCount = TotalCells
  42.         
  43.         # define all the variables attached to the controls
  44.         self.GridLinesX = IntVar()
  45.         self.GridLinesX.set( NumCols )
  46.  
  47.         self.GridLinesY = IntVar()
  48.         self.GridLinesY.set( NumRows )
  49.  
  50.         self.PadAmount = IntVar()
  51.         self.PadAmount.set( 2 )
  52.         
  53.         # define the basics of the window
  54.         self.pack(expand=YES, fill=BOTH)
  55.         self.master.title('Define Cells')
  56.        
  57.         # put some explanatory text on the window
  58.         Label( self, text = 'Enter the cell arrangement of the tube:',
  59.                justify=LEFT ).pack(expand=YES, fill=BOTH, side=TOP)
  60.  
  61.         # make a subframe to hold the cells across controls
  62.         XFrame = Frame( self )
  63.         XLabel = Label( XFrame, text='Cells across:', width=30 )
  64.         XLabel.pack( expand=YES, fill=BOTH, side=LEFT )
  65.         self.XEntry = Entry( XFrame, textvariable=self.GridLinesX )
  66.         self.XEntry.pack( expand=YES, fill=BOTH, side=RIGHT )
  67.         XFrame.pack(side=TOP)
  68.         
  69.         # do the same thing for the cells down controls
  70.         YFrame = Frame( self )
  71.         YLabel = Label( YFrame, text='Cells down:', width=30 )
  72.         YLabel.pack( expand=YES, fill=BOTH, side=LEFT )
  73.         self.YEntry = Entry( YFrame, textvariable=self.GridLinesY )
  74.         self.YEntry.pack( expand=YES, fill=BOTH, side=RIGHT )
  75.         YFrame.pack(side=TOP)
  76.         
  77.         # and finally for the cell padding
  78.         PadFrame = Frame( self )
  79.         PadLabel = Label( PadFrame, text='Cell padding:', width=30 )
  80.         PadLabel.pack( expand=YES, fill=BOTH, side=LEFT )
  81.         self.PadEntry = Entry( PadFrame, textvariable=self.PadAmount )
  82.         self.PadEntry.pack( expand=YES, fill=BOTH, side=RIGHT )
  83.         PadFrame.pack(side=TOP)
  84.         
  85.         # put OK/Cancel buttons on the dialog - parts of this lifted from TkSimpleDialog
  86.         ButtonFrame = Frame(self)
  87.         OKButton = Button( ButtonFrame, text="OK", width=10,
  88.                            command=self.OnOK, default=ACTIVE )
  89.         OKButton.pack(side=LEFT, padx=5, pady=5)
  90.         CancelButton = Button( ButtonFrame, text="Cancel", width=10,
  91.                                command=self.OnCancel )
  92.         CancelButton.pack(side=LEFT, padx=5, pady=5)
  93.         ButtonFrame.pack()
  94.         
  95.         self.bind("<Return>", self.OnOK)
  96.         self.bind("<Escape>", self.OnCancel)
  97.        
  98.  
  99.     def OnOK(self, event=None):
  100.         ''' called by pressing the OK button - validates data, and if no error
  101.             sets a good return code and dismisses the dialog by calling OnCancel
  102.         '''
  103.         try:
  104.             X = self.GridLinesX.get()
  105.         except ValueError:
  106.             X = 0
  107.  
  108.         try:            
  109.             Y = self.GridLinesY.get()
  110.         except ValueError:
  111.             Y = 0
  112.         
  113.         try:
  114.             Pad = self.PadAmount.get()
  115.         except ValueError:
  116.             Pad = -1
  117.             
  118.         if X < 1 or X > self.ImageCount:
  119.             tkMessageBox.showerror( 'Cells across invalid',
  120.                                     'Cells across must be less than %d' % self.ImageCount )
  121.             return 
  122.         if Y < 1 or Y > self.ImageCount:
  123.             tkMessageBox.showerror( 'Cells down invalid',
  124.                                     'Cells down must be less than %d' % self.ImageCount )
  125.             return
  126.         if X * Y < self.ImageCount:
  127.             tkMessageBox.showerror( 'Not enough cells',
  128.                                     'Cells across x cells down must be at least %d' % self.ImageCount )
  129.             return
  130.         if Pad < 0 or Pad > 25:
  131.             tkMessageBox.showerror( 'Pad amount invalid',
  132.                                     'The per cell pad amount must be between 0 and 25 pixels' )
  133.             return
  134.         
  135.         # if we got here we passed validation
  136.         self.OKPressed = 1
  137.         
  138.         # finish by pressing the Cancel button
  139.         self.OnCancel()
  140.  
  141.     def OnCancel(self, event=None):
  142.         # on cancel we simply terminate the message loop
  143.         self.quit()
  144.  
  145. def Do(Environment):
  146.     # we need at least two images for this to work
  147.     if len(App.Documents) < 2:
  148.         App.Do(Environment,  'MsgBox', {
  149.                 'Buttons': App.Constants.MsgButtons.OK, 
  150.                 'Icon': App.Constants.MsgIcons.Stop, 
  151.                 'Text': 'This script requires at least two open images.'
  152.                 })
  153.         return  
  154.     
  155.     # 1. Iterate open images and verify that each meets the requirement of being
  156.     #    a single raster layer with transparency.  If not, give the user the choice
  157.     #    of fixing the image, skipping the image, or canceling the export entirely.
  158.     #    During this phase we find the largest source image, since we need to know
  159.     #    how big the tube cells are.
  160.         
  161.     CellHeight = 0
  162.     CellWidth = 0
  163.     IncludedDocs = []
  164.     
  165.     # all of these formats have palettes, and tubes have to be true color
  166.     PalettedFormats = [ App.Constants.PixelFormat.Index1, App.Constants.PixelFormat.Index4,
  167.                         App.Constants.PixelFormat.Index8, App.Constants.PixelFormat.Grey,
  168.                         App.Constants.PixelFormat.GreyA ]
  169.     
  170.     for Doc in App.Documents:
  171.         DocInfo = App.Do( Environment, 'ReturnImageInfo', {}, Doc )
  172.         LayerInfo = App.Do( Environment, 'ReturnLayerProperties', {}, Doc )
  173.         ColorsInDoc = App.Do( Environment, 'CountImageColors', {
  174.                                 'GeneralSettings': {
  175.                                     'ExecutionMode': App.Constants.ExecutionMode.Silent, 
  176.                                     'AutoActionMode': App.Constants.AutoActionMode.Match
  177.                                     }
  178.                                 })
  179.  
  180.  
  181.         LayerCount = DocInfo[ 'LayerNum' ]
  182.         LayerType = LayerInfo[ 'LayerType' ]
  183.         PixelFormat = DocInfo[ 'PixelFormat' ]
  184.         IncludeDoc = 0
  185.         if ColorsInDoc == 0:    # an empty image will fail on paste
  186.             MsgText = 'Image %s is empty.\n' \
  187.                       'Press OK to skip the image and continue\n' \
  188.                       'Press CANCEL to cancel the script entirely\n' % Doc.Title
  189.             result = App.Do(Environment,  'MsgBox', {
  190.                         'Buttons': App.Constants.MsgButtons.OKCancel, 
  191.                         'Icon': App.Constants.MsgIcons.Question, 
  192.                         'Text': MsgText
  193.                         })
  194.             if result == 0: # cancel script
  195.                 return  # give up
  196.         elif LayerCount != 1:
  197.             MsgText = 'Image %s has more than one layer.\n' \
  198.                       'Press OK to merge the visible layers.\n' \
  199.                       'Press CANCEL to leave this image out of the tube.\n' % Doc.Title
  200.             result = App.Do(Environment,  'MsgBox', {
  201.                         'Buttons': App.Constants.MsgButtons.OKCancel, 
  202.                         'Icon': App.Constants.MsgIcons.Question, 
  203.                         'Text': MsgText
  204.                         })
  205.             if result == 1:  # OK button
  206.                 App.Do( Environment, 'LayerMergeVisible', {}, Doc )
  207.                 IncludeDoc = 1
  208.             elif result == 0: # Cancel button
  209.                 result = App.Do(Environment,  'MsgBox', {
  210.                             'Buttons': App.Constants.MsgButtons.YesNo, 
  211.                             'Icon': App.Constants.MsgIcons.Question, 
  212.                             'Text': 'Do you wish to cancel the entire script?'
  213.                             })
  214.                 if result == 1:
  215.                     return  # give up
  216.                 
  217.         elif LayerType == App.Constants.LayerType.Vector:
  218.             MsgText = 'Image %s is vector.\n' \
  219.                       'Press OK to to convert to a raster layer.\n' \
  220.                       'Press CANCEL to leave this image out of the tube.\n' % Doc.Title
  221.             result = App.Do(Environment,  'MsgBox', {
  222.                         'Buttons': App.Constants.MsgButtons.OKCancel, 
  223.                         'Icon': App.Constants.MsgIcons.Question, 
  224.                         'Text': MsgText
  225.                         })
  226.             if result == 1:  # OK button
  227.                 App.Do( Environment, 'LayerConvertToRaster', {}, Doc )
  228.                 IncludeDoc = 1
  229.             elif result == 0: # cancel button
  230.                 result = App.Do(Environment,  'MsgBox', {
  231.                             'Buttons': App.Constants.MsgButtons.YesNo, 
  232.                             'Icon': App.Constants.MsgIcons.Question, 
  233.                             'Text': 'Do you wish to cancel the entire script?'
  234.                             })
  235.                 if result == 1:
  236.                     return  # give up
  237.                 
  238.         elif PixelFormat != App.Constants.PixelFormat.BGRA and \
  239.              PixelFormat != App.Constants.PixelFormat.GreyA:
  240.             MsgText = 'Image %s is a background layer without transparency.\n' \
  241.                       'Press OK to to use the image anyway.\n' \
  242.                       'Press CANCEL to leave this image out of the tube.\n' % Doc.Title
  243.             result = App.Do(Environment,  'MsgBox', {
  244.                         'Buttons': App.Constants.MsgButtons.OKCancel, 
  245.                         'Icon': App.Constants.MsgIcons.Question, 
  246.                         'Text': MsgText
  247.                         })
  248.             if result == 1:  # OK button
  249.                 if PixelFormat in PalettedFormats:    # increase color depth if needed
  250.                     App.Do(Environment, 'IncreaseColorsTo16Million', {}, Doc )
  251.                 IncludeDoc = 1  # use the doc anyway
  252.             elif result == 0: # no button
  253.                 result = App.Do(Environment,  'MsgBox', {
  254.                             'Buttons': App.Constants.MsgButtons.YesNo, 
  255.                             'Icon': App.Constants.MsgIcons.Question, 
  256.                             'Text': 'Do you wish to cancel the entire script?'
  257.                             })
  258.                 if result == 1:
  259.                     return  # give up
  260.         else:
  261.             IncludeDoc = 1  # image was fine just the way it is
  262.  
  263.         # if we are going to use this document add it to our list and
  264.         # update the needed cell size
  265.         if IncludeDoc:
  266.             IncludedDocs.append( Doc )
  267.             CellHeight = max( CellHeight, Doc.Height )
  268.             CellWidth = max( CellWidth, Doc.Width )
  269.  
  270.     if len(IncludedDocs) < 2:
  271.         App.Do(Environment,  'MsgBox', {
  272.                 'Buttons': App.Constants.MsgButtons.OK, 
  273.                 'Icon': App.Constants.MsgIcons.Stop, 
  274.                 'Text': 'Not enough images left to tube.'
  275.                 })
  276.         return  # not enough images left
  277.  
  278.     # 2. Next compute the best fit for tube layout - best fit is described as closest
  279.     #    to square.  We do this by computing the size of various layouts going from
  280.     #    1 x n, 2 x n/2, 3 x n/3, all the way up to n x 1.  We watch the difference between
  281.     #    height and width - when it starts increasing we know we found the best fit.
  282.     OldDifference = 9999999 # init this high our initial test works
  283.     for i in range( 1, len(IncludedDocs)+1 ):
  284.         NumRows = i
  285.         NumCols = (len(IncludedDocs) + i-1) / i
  286.  
  287.         Width = NumCols * CellWidth
  288.         Height = NumRows * CellHeight
  289.         Difference = abs( Width - Height )
  290.  
  291.         if Difference >= OldDifference:
  292.             NumRows = i - 1
  293.             NumCols = (len(IncludedDocs) + NumRows - 1) / NumRows
  294.             break
  295.         else:
  296.             OldDifference = Difference
  297.  
  298.     # 2. Seed a dialog with the best fit choices, but let the user override. The number
  299.     #    of cells they create must be sufficient for the number of images that will be
  300.     #    tubed.
  301.     # create the root TK window    
  302.     root = Tk()
  303.     
  304.     # create the dialog and show the dialog
  305.     Dlg = CellCountDlg( root, 'Cell Layout', NumCols, NumRows, len(IncludedDocs) )
  306.     
  307.     # tell PSP that a foreign dialog is running.  This causes PSP to do some additional
  308.     # work to keep the UI updating properly and to prevent the script window from going
  309.     # behind PSP.
  310.     App.Do( Environment, 'StartForeignWindow', { 'WindowHandle': int(Dlg.winfo_id()) } )
  311.     
  312.     root.mainloop()
  313.     root.destroy()
  314.     App.Do( Environment, 'StartForeignWindow', { 'WindowHandle': 0 } )
  315.     
  316.     # if the user pressed cancel in the dialog just return
  317.     if not Dlg.OKPressed:
  318.         print 'Cancel pressed - aborting'
  319.         return
  320.  
  321.     # get the number of cells to use    
  322.     PadFactor = Dlg.PadAmount.get()
  323.     CellWidth += PadFactor
  324.     CellHeight += PadFactor
  325.     
  326.     NumCols = Dlg.GridLinesX.get()
  327.     NumRows = Dlg.GridLinesY.get()
  328.     
  329.     # 3. Create a new image big enough to hold all of the images.
  330.     App.Do( Environment, 'NewFile', {
  331.             'Width': NumCols * CellWidth, 
  332.             'Height': NumRows * CellHeight, 
  333.             'ColorDepth': App.Constants.Colordepth.SixteenMillionColor, 
  334.             'DimensionUnits': App.Constants.DimensionType.Pixels, 
  335.             'Transparent': App.Constants.Boolean.true, 
  336.             'VectorBackground': App.Constants.Boolean.false, 
  337.             'GeneralSettings': {
  338.                 'ExecutionMode': App.Constants.ExecutionMode.Silent, 
  339.                 }
  340.             })
  341.     TubeDoc = App.ActiveDocument
  342.     
  343.     # 4. For possible future editing, define the grid on the image to match the cell layout
  344.     #    We actually make the grid half the size, so that it we can snap to the center of the cell
  345.     GridSpacingX = TubeDoc.Width / NumCols / 2
  346.     GridSpacingY = TubeDoc.Height / NumRows / 2
  347.         
  348.     App.Do( Environment, 'GridGuideSnapProperties', {
  349.             'CurrentHorzGridSpacing': GridSpacingX, 
  350.             'CurrentVertGridSpacing': GridSpacingY, 
  351.             'GeneralSettings': {
  352.                 'ExecutionMode': App.Constants.ExecutionMode.Silent, 
  353.                 'AutoActionMode': App.Constants.AutoActionMode.Match
  354.                 }
  355.             }, TubeDoc )
  356.  
  357.  
  358.     # 5. Iterate all the images that will be in the tube, copy them to the clipboard,
  359.     #    then paste them into the tube image as a new selection.  We then use the mover tool
  360.     #    to center each image in its cell.
  361.     Row = 0
  362.     Col = 0
  363.     for Doc in IncludedDocs:
  364.         if Doc.Title == TubeDoc.Title:
  365.             continue    # skip the one we just created
  366.  
  367.         print 'Working on document %s' % Doc.Title
  368.  
  369.         # temporarily save any selection we have
  370.         SelSaver = JascUtils.SaveSelection(Environment, Doc)
  371.  
  372.         # copy the source image to the clipboard
  373.         App.Do( Environment, 'Copy', {
  374.                 'LayerNumber': 0, 
  375.                 'CopyToClipboard': App.Constants.Boolean.false, 
  376.                 'GeneralSettings': {
  377.                     'ExecutionMode': App.Constants.ExecutionMode.Silent, 
  378.                     'AutoActionMode': App.Constants.AutoActionMode.Match
  379.                     }
  380.                 }, Doc)
  381.  
  382.         # put back any selection we saved        
  383.         SelSaver.RestoreSelection()
  384.         
  385.         # now paste as a new selection, moving it to the proper spot
  386.         # the paste will default to the center of the image, so we just
  387.         # compute the difference between the center of the image and center
  388.         # of the cell
  389.         OffsetX = (Col * CellWidth + CellWidth / 2) - (TubeDoc.Width / 2)
  390.         OffsetY = (Row * CellHeight + CellHeight / 2) - (TubeDoc.Height / 2)
  391.         App.Do( Environment, 'PasteAsNewSelection', {
  392.                 'Offset': (OffsetX, OffsetY), 
  393.                 'GeneralSettings': {
  394.                     'ExecutionMode': App.Constants.ExecutionMode.Default, 
  395.                     'AutoActionMode': App.Constants.AutoActionMode.Match
  396.                     }
  397.                 }, TubeDoc)
  398.  
  399.         # remove the selection
  400.         App.Do( Environment, 'SelectNone', {}, TubeDoc )        
  401.  
  402.         # advance to the next column, and possibly the next row
  403.         Col += 1
  404.         if Col == NumCols:
  405.             Col = 0
  406.             Row += 1
  407.             
  408.  
  409.     # 6. Invoke the tube exporter, with cells across, cells down and total cells set properly.
  410.     #    Step size is initialized to the cell width.
  411.     App.Do( Environment, 'ExportTube', {
  412.             'FileName': 'AutoTube', 
  413.             'NumberOfCellsAcross': NumCols, 
  414.             'NumberOfCellsDown': NumRows, 
  415.             'TotalNumberOfCells': len(IncludedDocs), 
  416.             'PlacementMode': App.Constants.TubePlacementMode.Random, 
  417.             'SelectionMode': App.Constants.TubeSelectionMode.Random, 
  418.             'StepSize': min( CellWidth, 500 ), 
  419.             'OverwriteIfExists': App.Constants.Boolean.true, 
  420.             'GeneralSettings': {
  421.                 'ExecutionMode': App.Constants.ExecutionMode.Interactive, 
  422.                 'AutoActionMode': App.Constants.AutoActionMode.Match
  423.                 }
  424.             })
  425.