home *** CD-ROM | disk | FTP | other *** search
Wrap
from JascApp import * from Tkinter import * import tkMessageBox import JascUtils def ScriptProperties(): return { 'Author': 'Joe Fromm', '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', 'Description': 'Combine all open images into a single image and export it as a tube', 'Host': 'Paint Shop Pro', 'Host Version': '8.00', 'ForceTextEditor': App.Constants.Boolean.true } # AutoTubing (as described by a private beta tester): # 1. Iterate open images and verify that each meets the requirement of being # a single raster layer with transparency. If not, give the user the choice # of fixing the image, skipping the image, or canceling the export entirely. # During this phase we find the largest source image, since we need to know # how big the tube cells are. # 2. Next compute the best fit for tube layout - best fit is described as closest # to square, but the user can override these choices in a dialog. They number # of cells they create must be sufficient for the number of images that will be # tubed. # 3. Create a new image big enough to hold all of the images. # 4. For possible future editing, define the grid on the image to match the number of cells # 5. Iterate all the images that will be in the tube, copy them to the clipboard, # then paste them into the tube image as a new selection. We set the offset on the # paste operation so that each lands in the right spot. # 6. Invoke the tube exporter, with cells across, cells down and total cells set properly. # Step size is initialized to the cell width. class CellCountDlg(Frame): ''' define the dialog used to prompt the user for the number of cells''' def __init__( self, parent, title, NumCols, NumRows, TotalCells ): Frame.__init__(self) # init our parent # if we exit with OK this will be set to 1. A zero means we pressed cancel self.OKPressed = 0 self.ImageCount = TotalCells # define all the variables attached to the controls self.GridLinesX = IntVar() self.GridLinesX.set( NumCols ) self.GridLinesY = IntVar() self.GridLinesY.set( NumRows ) self.PadAmount = IntVar() self.PadAmount.set( 2 ) # define the basics of the window self.pack(expand=YES, fill=BOTH) self.master.title('Define Cells') # put some explanatory text on the window Label( self, text = 'Enter the cell arrangement of the tube:', justify=LEFT ).pack(expand=YES, fill=BOTH, side=TOP) # make a subframe to hold the cells across controls XFrame = Frame( self ) XLabel = Label( XFrame, text='Cells across:', width=30 ) XLabel.pack( expand=YES, fill=BOTH, side=LEFT ) self.XEntry = Entry( XFrame, textvariable=self.GridLinesX ) self.XEntry.pack( expand=YES, fill=BOTH, side=RIGHT ) XFrame.pack(side=TOP) # do the same thing for the cells down controls YFrame = Frame( self ) YLabel = Label( YFrame, text='Cells down:', width=30 ) YLabel.pack( expand=YES, fill=BOTH, side=LEFT ) self.YEntry = Entry( YFrame, textvariable=self.GridLinesY ) self.YEntry.pack( expand=YES, fill=BOTH, side=RIGHT ) YFrame.pack(side=TOP) # and finally for the cell padding PadFrame = Frame( self ) PadLabel = Label( PadFrame, text='Cell padding:', width=30 ) PadLabel.pack( expand=YES, fill=BOTH, side=LEFT ) self.PadEntry = Entry( PadFrame, textvariable=self.PadAmount ) self.PadEntry.pack( expand=YES, fill=BOTH, side=RIGHT ) PadFrame.pack(side=TOP) # put OK/Cancel buttons on the dialog - parts of this lifted from TkSimpleDialog ButtonFrame = Frame(self) OKButton = Button( ButtonFrame, text="OK", width=10, command=self.OnOK, default=ACTIVE ) OKButton.pack(side=LEFT, padx=5, pady=5) CancelButton = Button( ButtonFrame, text="Cancel", width=10, command=self.OnCancel ) CancelButton.pack(side=LEFT, padx=5, pady=5) ButtonFrame.pack() self.bind("<Return>", self.OnOK) self.bind("<Escape>", self.OnCancel) def OnOK(self, event=None): ''' called by pressing the OK button - validates data, and if no error sets a good return code and dismisses the dialog by calling OnCancel ''' try: X = self.GridLinesX.get() except ValueError: X = 0 try: Y = self.GridLinesY.get() except ValueError: Y = 0 try: Pad = self.PadAmount.get() except ValueError: Pad = -1 if X < 1 or X > self.ImageCount: tkMessageBox.showerror( 'Cells across invalid', 'Cells across must be less than %d' % self.ImageCount ) return if Y < 1 or Y > self.ImageCount: tkMessageBox.showerror( 'Cells down invalid', 'Cells down must be less than %d' % self.ImageCount ) return if X * Y < self.ImageCount: tkMessageBox.showerror( 'Not enough cells', 'Cells across x cells down must be at least %d' % self.ImageCount ) return if Pad < 0 or Pad > 25: tkMessageBox.showerror( 'Pad amount invalid', 'The per cell pad amount must be between 0 and 25 pixels' ) return # if we got here we passed validation self.OKPressed = 1 # finish by pressing the Cancel button self.OnCancel() def OnCancel(self, event=None): # on cancel we simply terminate the message loop self.quit() def Do(Environment): # we need at least two images for this to work if len(App.Documents) < 2: App.Do(Environment, 'MsgBox', { 'Buttons': App.Constants.MsgButtons.OK, 'Icon': App.Constants.MsgIcons.Stop, 'Text': 'This script requires at least two open images.' }) return # 1. Iterate open images and verify that each meets the requirement of being # a single raster layer with transparency. If not, give the user the choice # of fixing the image, skipping the image, or canceling the export entirely. # During this phase we find the largest source image, since we need to know # how big the tube cells are. CellHeight = 0 CellWidth = 0 IncludedDocs = [] # all of these formats have palettes, and tubes have to be true color PalettedFormats = [ App.Constants.PixelFormat.Index1, App.Constants.PixelFormat.Index4, App.Constants.PixelFormat.Index8, App.Constants.PixelFormat.Grey, App.Constants.PixelFormat.GreyA ] for Doc in App.Documents: DocInfo = App.Do( Environment, 'ReturnImageInfo', {}, Doc ) LayerInfo = App.Do( Environment, 'ReturnLayerProperties', {}, Doc ) ColorsInDoc = App.Do( Environment, 'CountImageColors', { 'GeneralSettings': { 'ExecutionMode': App.Constants.ExecutionMode.Silent, 'AutoActionMode': App.Constants.AutoActionMode.Match } }) LayerCount = DocInfo[ 'LayerNum' ] LayerType = LayerInfo[ 'LayerType' ] PixelFormat = DocInfo[ 'PixelFormat' ] IncludeDoc = 0 if ColorsInDoc == 0: # an empty image will fail on paste MsgText = 'Image %s is empty.\n' \ 'Press OK to skip the image and continue\n' \ 'Press CANCEL to cancel the script entirely\n' % Doc.Title result = App.Do(Environment, 'MsgBox', { 'Buttons': App.Constants.MsgButtons.OKCancel, 'Icon': App.Constants.MsgIcons.Question, 'Text': MsgText }) if result == 0: # cancel script return # give up elif LayerCount != 1: MsgText = 'Image %s has more than one layer.\n' \ 'Press OK to merge the visible layers.\n' \ 'Press CANCEL to leave this image out of the tube.\n' % Doc.Title result = App.Do(Environment, 'MsgBox', { 'Buttons': App.Constants.MsgButtons.OKCancel, 'Icon': App.Constants.MsgIcons.Question, 'Text': MsgText }) if result == 1: # OK button App.Do( Environment, 'LayerMergeVisible', {}, Doc ) IncludeDoc = 1 elif result == 0: # Cancel button result = App.Do(Environment, 'MsgBox', { 'Buttons': App.Constants.MsgButtons.YesNo, 'Icon': App.Constants.MsgIcons.Question, 'Text': 'Do you wish to cancel the entire script?' }) if result == 1: return # give up elif LayerType == App.Constants.LayerType.Vector: MsgText = 'Image %s is vector.\n' \ 'Press OK to to convert to a raster layer.\n' \ 'Press CANCEL to leave this image out of the tube.\n' % Doc.Title result = App.Do(Environment, 'MsgBox', { 'Buttons': App.Constants.MsgButtons.OKCancel, 'Icon': App.Constants.MsgIcons.Question, 'Text': MsgText }) if result == 1: # OK button App.Do( Environment, 'LayerConvertToRaster', {}, Doc ) IncludeDoc = 1 elif result == 0: # cancel button result = App.Do(Environment, 'MsgBox', { 'Buttons': App.Constants.MsgButtons.YesNo, 'Icon': App.Constants.MsgIcons.Question, 'Text': 'Do you wish to cancel the entire script?' }) if result == 1: return # give up elif PixelFormat != App.Constants.PixelFormat.BGRA and \ PixelFormat != App.Constants.PixelFormat.GreyA: MsgText = 'Image %s is a background layer without transparency.\n' \ 'Press OK to to use the image anyway.\n' \ 'Press CANCEL to leave this image out of the tube.\n' % Doc.Title result = App.Do(Environment, 'MsgBox', { 'Buttons': App.Constants.MsgButtons.OKCancel, 'Icon': App.Constants.MsgIcons.Question, 'Text': MsgText }) if result == 1: # OK button if PixelFormat in PalettedFormats: # increase color depth if needed App.Do(Environment, 'IncreaseColorsTo16Million', {}, Doc ) IncludeDoc = 1 # use the doc anyway elif result == 0: # no button result = App.Do(Environment, 'MsgBox', { 'Buttons': App.Constants.MsgButtons.YesNo, 'Icon': App.Constants.MsgIcons.Question, 'Text': 'Do you wish to cancel the entire script?' }) if result == 1: return # give up else: IncludeDoc = 1 # image was fine just the way it is # if we are going to use this document add it to our list and # update the needed cell size if IncludeDoc: IncludedDocs.append( Doc ) CellHeight = max( CellHeight, Doc.Height ) CellWidth = max( CellWidth, Doc.Width ) if len(IncludedDocs) < 2: App.Do(Environment, 'MsgBox', { 'Buttons': App.Constants.MsgButtons.OK, 'Icon': App.Constants.MsgIcons.Stop, 'Text': 'Not enough images left to tube.' }) return # not enough images left # 2. Next compute the best fit for tube layout - best fit is described as closest # to square. We do this by computing the size of various layouts going from # 1 x n, 2 x n/2, 3 x n/3, all the way up to n x 1. We watch the difference between # height and width - when it starts increasing we know we found the best fit. OldDifference = 9999999 # init this high our initial test works for i in range( 1, len(IncludedDocs)+1 ): NumRows = i NumCols = (len(IncludedDocs) + i-1) / i Width = NumCols * CellWidth Height = NumRows * CellHeight Difference = abs( Width - Height ) if Difference >= OldDifference: NumRows = i - 1 NumCols = (len(IncludedDocs) + NumRows - 1) / NumRows break else: OldDifference = Difference # 2. Seed a dialog with the best fit choices, but let the user override. The number # of cells they create must be sufficient for the number of images that will be # tubed. # create the root TK window root = Tk() # create the dialog and show the dialog Dlg = CellCountDlg( root, 'Cell Layout', NumCols, NumRows, len(IncludedDocs) ) # tell PSP that a foreign dialog is running. This causes PSP to do some additional # work to keep the UI updating properly and to prevent the script window from going # behind PSP. App.Do( Environment, 'StartForeignWindow', { 'WindowHandle': int(Dlg.winfo_id()) } ) root.mainloop() root.destroy() App.Do( Environment, 'StartForeignWindow', { 'WindowHandle': 0 } ) # if the user pressed cancel in the dialog just return if not Dlg.OKPressed: print 'Cancel pressed - aborting' return # get the number of cells to use PadFactor = Dlg.PadAmount.get() CellWidth += PadFactor CellHeight += PadFactor NumCols = Dlg.GridLinesX.get() NumRows = Dlg.GridLinesY.get() # 3. Create a new image big enough to hold all of the images. App.Do( Environment, 'NewFile', { 'Width': NumCols * CellWidth, 'Height': NumRows * CellHeight, 'ColorDepth': App.Constants.Colordepth.SixteenMillionColor, 'DimensionUnits': App.Constants.DimensionType.Pixels, 'Transparent': App.Constants.Boolean.true, 'VectorBackground': App.Constants.Boolean.false, 'GeneralSettings': { 'ExecutionMode': App.Constants.ExecutionMode.Silent, } }) TubeDoc = App.ActiveDocument # 4. For possible future editing, define the grid on the image to match the cell layout # We actually make the grid half the size, so that it we can snap to the center of the cell GridSpacingX = TubeDoc.Width / NumCols / 2 GridSpacingY = TubeDoc.Height / NumRows / 2 App.Do( Environment, 'GridGuideSnapProperties', { 'CurrentHorzGridSpacing': GridSpacingX, 'CurrentVertGridSpacing': GridSpacingY, 'GeneralSettings': { 'ExecutionMode': App.Constants.ExecutionMode.Silent, 'AutoActionMode': App.Constants.AutoActionMode.Match } }, TubeDoc ) # 5. Iterate all the images that will be in the tube, copy them to the clipboard, # then paste them into the tube image as a new selection. We then use the mover tool # to center each image in its cell. Row = 0 Col = 0 for Doc in IncludedDocs: if Doc.Title == TubeDoc.Title: continue # skip the one we just created print 'Working on document %s' % Doc.Title # temporarily save any selection we have SelSaver = JascUtils.SaveSelection(Environment, Doc) # copy the source image to the clipboard App.Do( Environment, 'Copy', { 'LayerNumber': 0, 'CopyToClipboard': App.Constants.Boolean.false, 'GeneralSettings': { 'ExecutionMode': App.Constants.ExecutionMode.Silent, 'AutoActionMode': App.Constants.AutoActionMode.Match } }, Doc) # put back any selection we saved SelSaver.RestoreSelection() # now paste as a new selection, moving it to the proper spot # the paste will default to the center of the image, so we just # compute the difference between the center of the image and center # of the cell OffsetX = (Col * CellWidth + CellWidth / 2) - (TubeDoc.Width / 2) OffsetY = (Row * CellHeight + CellHeight / 2) - (TubeDoc.Height / 2) App.Do( Environment, 'PasteAsNewSelection', { 'Offset': (OffsetX, OffsetY), 'GeneralSettings': { 'ExecutionMode': App.Constants.ExecutionMode.Default, 'AutoActionMode': App.Constants.AutoActionMode.Match } }, TubeDoc) # remove the selection App.Do( Environment, 'SelectNone', {}, TubeDoc ) # advance to the next column, and possibly the next row Col += 1 if Col == NumCols: Col = 0 Row += 1 # 6. Invoke the tube exporter, with cells across, cells down and total cells set properly. # Step size is initialized to the cell width. App.Do( Environment, 'ExportTube', { 'FileName': 'AutoTube', 'NumberOfCellsAcross': NumCols, 'NumberOfCellsDown': NumRows, 'TotalNumberOfCells': len(IncludedDocs), 'PlacementMode': App.Constants.TubePlacementMode.Random, 'SelectionMode': App.Constants.TubeSelectionMode.Random, 'StepSize': min( CellWidth, 500 ), 'OverwriteIfExists': App.Constants.Boolean.true, 'GeneralSettings': { 'ExecutionMode': App.Constants.ExecutionMode.Interactive, 'AutoActionMode': App.Constants.AutoActionMode.Match } })