Image Wizard: Scale and Align an Image

You may have seen these scripts that Scale an Image to Fill a Frame Proportionally and Align an Image in its Frame. The following script brings up a dialog that allows you to perform both functions, as well as two other scaling functions, and flipping and manual alignment functions.


 * Scale to Fill Frame (proportional scaling with cropping)
 * Scale to Fit Frame (standard Scribus "Scale to Frame Size: Proportional" - no cropping)
 * Scale Image to: x % (proportional scaling by the given percentage)
 * Automatic alignment (9 possible alignments)
 * Click arrow buttons to align the image in the frame
 * Click arrow buttons to align the frame on the canvas
 * Flip the image horizontally or vertically

Each button click performs a separate action on all the currently selected images. So if you need to scale the image and then align it, you should first click the scaling button, then select your alignment and press the Align button.

I hope you'll find this script useful. This script has been immensely helpful to me in laying out the pictures for a photo album. More functions may be forthcoming if I find some that are useful. Maybe this additional alignment and scaling functionality could be incorporated into Scribus at some point.

The script has been tested in Scribus 1.3.3.9 and 1.3.4. It requires Tkinter to be properly installed.

'''Warning: You can leave the script dialog open, and continue to scale and align more images that you select. However, due to a multi-threading bug with Python scripts in Scribus, if you attempt to launch another script while this one is running, Scribus will probably crash and you'll lose your document. So be careful, and save often! Also, when the script is running, it may interfere with other Scribus actions such as using the Properties toolbar.'''

Here is a screenshot of the script:



Save this script with a filename of Image_Wizard.py, for example.

from scribus import *

try: # I wish PyQt installed everywhere :-/ from Tkinter import * from tkFont import Font except ImportError: print "This script requires Python's Tkinter properly installed." messageBox('Script failed',              'This script requires Python\'s Tkinter properly installed.',               ICON_CRITICAL) sys.exit(1)

class TkImageAlignmentWizard(Frame): """ GUI interface for aligning an image in a frame"""

def __init__(self, master=None): """ Setup the dialog """ # refernce to the localization dictionary self.key = 'English' Frame.__init__(self, master) self.master.resizable(0, 0) self.master.title('Image Wizard') #define widgets #define framed areas self.grid self.align_frame = Frame(self, bd=1, relief=RIDGE) self.scale_frame = Frame(self, bd=1, relief=RIDGE) self.flip_frame = Frame(self, bd=1, relief=RIDGE) self.move_frame = Frame(self, bd=1, relief=RIDGE) # alignment options self.alignLabel = Label(self.align_frame, text='Align Image') self.alignVar = StringVar self.alignRadio1 = Radiobutton(self.align_frame, text='', variable=self.alignVar, value="TL") self.alignRadio2 = Radiobutton(self.align_frame, text='', variable=self.alignVar, value="TC") self.alignRadio3 = Radiobutton(self.align_frame, text='', variable=self.alignVar, value="TR") self.alignRadio4 = Radiobutton(self.align_frame, text='', variable=self.alignVar, value="ML") self.alignRadio5 = Radiobutton(self.align_frame, text='', variable=self.alignVar, value="MC") self.alignRadio6 = Radiobutton(self.align_frame, text='', variable=self.alignVar, value="MR") self.alignRadio7 = Radiobutton(self.align_frame, text='', variable=self.alignVar, value="BL") self.alignRadio8 = Radiobutton(self.align_frame, text='', variable=self.alignVar, value="BC") self.alignRadio9 = Radiobutton(self.align_frame, text='', variable=self.alignVar, value="BR") self.alignButton = Button(self.align_frame, text='Align', command=self.alignImage) self.alignRadio5.select # scaling options self.scaleLabel = Label(self.scale_frame, text='Scale Image:') self.scaleToFillButton = Button(self.scale_frame, text='Scale to Fill Frame', command=self.scaleImageToFill) self.scaleToFitButton = Button(self.scale_frame, text='Scale to Fit Frame', command=self.scaleImageToFit) self.scalePercentVar = StringVar self.scalePercentLabel1 = Label(self.scale_frame, text='Scale Image to:') self.scalePercentLabel2 = Label(self.scale_frame, text='%') self.scalePercentEntry = Entry(self.scale_frame, textvariable=self.scalePercentVar, width=8) self.scalePercentButton = Button(self.scale_frame, text='Scale', command=self.scaleImageToPercent) #flip options self.flipLabel = Label(self.flip_frame, text='Flip Image:') self.flipHorizontallyButton = Button(self.flip_frame, text='< Flip Horizontally >', command=self.flipHorizontally) self.flipVerticallyButton = Button(self.flip_frame, text='^\nFlip Vertically\nv', command=self.flipVertically) self.flipUndoButton = Button(self.flip_frame, text='No Flipping', command=self.flipUndo) #move options self.moveVar = StringVar self.moveRadio1 = Radiobutton(self.move_frame, variable=self.moveVar, value="Image", text='Move Image') self.moveRadio2 = Radiobutton(self.move_frame, variable=self.moveVar, value="Frame", text='Move Frame') self.moveUpButton = Button(self.move_frame, text='^', command=self.moveUp) self.moveUpLotsButton = Button(self.move_frame, text='^^', command=self.moveUpLots) self.moveDownButton = Button(self.move_frame, text='v', command=self.moveDown) self.moveDownLotsButton = Button(self.move_frame, text='vv', command=self.moveDownLots) self.moveLeftButton = Button(self.move_frame, text='<', command=self.moveLeft) self.moveLeftLotsButton = Button(self.move_frame, text='<<', command=self.moveLeftLots) self.moveRightButton = Button(self.move_frame, text='>', command=self.moveRight) self.moveRightLotsButton = Button(self.move_frame, text='>>', command=self.moveRightLots) self.moveRadio1.select # closing/running self.doneButton = Button(self, text="Done", command=self.quit) #status label self.statusVar = StringVar self.statusLabel = Label(self, textvariable=self.statusVar) self.statusVar.set('Status:') # make align layout currRow = 0 self.alignLabel.grid(column=0, row=currRow, columnspan=3) currRow += 1 self.alignRadio1.grid(column=0, row=currRow) self.alignRadio2.grid(column=1, row=currRow) self.alignRadio3.grid(column=2, row=currRow) currRow += 1 self.alignRadio4.grid(column=0, row=currRow) self.alignRadio5.grid(column=1, row=currRow) self.alignRadio6.grid(column=2, row=currRow) currRow += 1 self.alignRadio7.grid(column=0, row=currRow) self.alignRadio8.grid(column=1, row=currRow) self.alignRadio9.grid(column=2, row=currRow) currRow += 1 self.alignButton.grid(column=0, row=currRow, columnspan=3) self.doneButton.grid(column=3, row=currRow, columnspan=3) #make scale layout currRow = 0 self.scaleLabel.grid(column=0, row=currRow, columnspan=3) currRow += 1 self.scaleToFillButton.grid(column=0, row=currRow, columnspan=3) currRow += 1 self.scaleToFitButton.grid(column=0, row=currRow, columnspan=3) currRow += 1 self.scalePercentLabel1.grid(column=0, row=currRow, columnspan=3) currRow += 1 self.scalePercentEntry.grid(column=0, row=currRow) self.scalePercentLabel2.grid(column=1, row=currRow) self.scalePercentButton.grid(column=2, row=currRow) #make flip layout currRow = 0 self.flipLabel.grid(column=0, row=currRow) currRow += 1 self.flipHorizontallyButton.grid(column=0, row=currRow) currRow += 1 self.flipVerticallyButton.grid(column=0, row=currRow) currRow += 1 self.flipUndoButton.grid(column=0, row=currRow) #make move layout currRow = 0 self.moveRadio1.grid(column=0, row=currRow, columnspan=5) currRow += 1 self.moveRadio2.grid(column=0, row=currRow, columnspan=5) currRow += 1 self.moveUpLotsButton.grid(column=2, row=currRow) currRow += 1 self.moveUpButton.grid(column=2, row=currRow) currRow += 1 self.moveLeftLotsButton.grid(column=0, row=currRow) self.moveLeftButton.grid(column=1, row=currRow) self.moveRightButton.grid(column=3, row=currRow) self.moveRightLotsButton.grid(column=4, row=currRow) currRow += 1 self.moveDownButton.grid(column=2, row=currRow) currRow += 1 self.moveDownLotsButton.grid(column=2, row=currRow) #make overall layout self.grid self.scale_frame.grid(row=0, column=0, sticky=NSEW) self.flip_frame.grid(row=0, column=1, sticky=NSEW) self.align_frame.grid(row=1, column=0, sticky=NSEW) self.move_frame.grid(row=1, column=1, sticky=NSEW) self.doneButton.grid(row=2, column=0, columnspan=2) #self.statusLabel.grid(row=2, column=0, columnspan=3, stick=W)

def alignImage(self): if haveDoc: nbrSelected = selectionCount objList = [] for i in range(nbrSelected): objList.append(getSelectedObject(i)) for i in range(nbrSelected): try: obj = objList[i] if getProperty(obj, "itemType") == 2: frameW = getProperty(obj, "width") frameH = getProperty(obj, "height") saveScaleX = getProperty(obj, "imageXScale") saveScaleY = getProperty(obj, "imageYScale") setScaleImageToFrame(True, False, obj) fullScaleX = getProperty(obj, "imageXScale") fullScaleY = getProperty(obj, "imageYScale") setScaleImageToFrame(False, False, obj) scaleImage(saveScaleX, saveScaleY, obj) imageW = frameW * (saveScaleX / fullScaleX) imageH = frameH * (saveScaleY / fullScaleY) imageX = 0.0 imageY = 0.0 if self.alignVar.get[0] == "T": imageY = 0.0 elif self.alignVar.get[0] == "M": imageY = (frameH - imageH) / 2.0 / saveScaleY elif self.alignVar.get[0] == "B": imageY = (frameH - imageH) / saveScaleY if self.alignVar.get[1] == "L": imageX = 0.0 elif self.alignVar.get[1] == "C": imageX = (frameW - imageW) / 2.0 / saveScaleX elif self.alignVar.get[1] == "R": imageX = (frameW - imageW) / saveScaleX

setProperty(obj, "imageXOffset", imageX) setProperty(obj, "imageYOffset", imageY) docChanged(1) setRedraw(True) except: nothing = "nothing"

def scaleImageToFill(self): if haveDoc: nbrSelected = selectionCount objList = [] for i in range(nbrSelected): objList.append(getSelectedObject(i)) for i in range(nbrSelected): try: obj = objList[i] if getProperty(obj, "itemType") == 2: setScaleImageToFrame(True, False, obj) scaleX, scaleY = getImageScale(obj) setScaleImageToFrame(False, False, obj) if scaleX > scaleY: scale = scaleX scaleImage(scale, scale, obj) elif scaleY > scaleX: scale = scaleY scaleImage(scale, scale, obj) docChanged(1) setRedraw(True) except: nothing = "nothing"

def scaleImageToFit(self): if haveDoc: nbrSelected = selectionCount objList = [] for i in range(nbrSelected): objList.append(getSelectedObject(i)) for i in range(nbrSelected): try: obj = objList[i] if getProperty(obj, "itemType") == 2: setScaleImageToFrame(True, True, obj) docChanged(1) setRedraw(True) except: nothing = "nothing" def scaleImageToPercent(self): if haveDoc: nbrSelected = selectionCount objList = [] for i in range(nbrSelected): objList.append(getSelectedObject(i)) for i in range(nbrSelected): try: tempScale = float(self.scalePercentVar.get) tempScale = tempScale / 100.0 obj = objList[i] scaleImage(tempScale, tempScale, obj) setRedraw(True) docChanged(1) except: nothing = "nothing"

def flipHorizontally(self): if haveDoc: nbrSelected = selectionCount objList = [] for i in range(nbrSelected): objList.append(getSelectedObject(i)) for i in range(nbrSelected): try: obj = objList[i] if getProperty(obj, "itemType") == 2: setProperty(obj, "m_ImageIsFlippedH", not getProperty(obj, "m_ImageIsFlippedH")) self.forceRefresh(obj) docChanged(1) setRedraw(True) except: nothing = "nothing" def flipVertically(self): if haveDoc: nbrSelected = selectionCount objList = [] for i in range(nbrSelected): objList.append(getSelectedObject(i)) for i in range(nbrSelected): try: obj = objList[i] if getProperty(obj, "itemType") == 2: setProperty(obj, "m_ImageIsFlippedV", not getProperty(obj, "m_ImageIsFlippedV")) self.forceRefresh(obj) docChanged(1) setRedraw(True) except: nothing = "nothing" def flipUndo(self): if haveDoc: nbrSelected = selectionCount objList = [] for i in range(nbrSelected): objList.append(getSelectedObject(i)) for i in range(nbrSelected): try: obj = objList[i] if getProperty(obj, "itemType") == 2: setProperty(obj, "m_ImageIsFlippedH", False) setProperty(obj, "m_ImageIsFlippedV", False) self.forceRefresh(obj) docChanged(1) setRedraw(True) except: nothing = "nothing" def moveUp(self): self.moveImage(0, -10.0) def moveUpLots(self): self.moveImage(0, -100.0) def moveDown(self): self.moveImage(0, 10.0) def moveDownLots(self): self.moveImage(0, 100.0) def moveLeft(self): self.moveImage(-10.0, 0) def moveLeftLots(self): self.moveImage(-100.0, 0) def moveRight(self): self.moveImage(10.0, 0) def moveRightLots(self): self.moveImage(100.0, 0) def moveImage(self, dx, dy): if haveDoc: nbrSelected = selectionCount objList = [] for i in range(nbrSelected): objList.append(getSelectedObject(i)) for i in range(nbrSelected): try: obj = objList[i] if getProperty(obj, "itemType") == 2: #based on which radio button is selected, we either move the image frame on the canvas, or move the image in its frame if str(self.moveVar.get) == "Frame": #moveObject(dx, dy, obj) setProperty(obj, "xPos", getProperty(obj, "xPos") + dx) setProperty(obj, "yPos", getProperty(obj, "yPos") + dy) self.forceRefresh(obj) else: #if the image is flipped, we need to reverse the delta-X or delta-Y in order to keep moving #the image in the direction that the user expects if getProperty(obj, "m_ImageIsFlippedH"): dx = dx * -1 if getProperty(obj, "m_ImageIsFlippedV"): dy = dy * -1 setProperty(obj, "imageXOffset", getProperty(obj, "imageXOffset") + dx) setProperty(obj, "imageYOffset", getProperty(obj, "imageYOffset") + dy) self.forceRefresh(obj) docChanged(1) setRedraw(True) except: nothing = "nothing" def forceRefresh(self, obj): #changing the certain properties doesn't actually redraw the image until the script is terminated #to fix this, we move the image and then move it back to the same spot, thus triggering a refresh of the image moveObject(1, 0, obj) moveObject(-1, 0, obj) docChanged(1) setRedraw(True)

def quit(self): self.master.destroy

def main: """ Application/Dialog loop with Scribus sauce around """ try: root = Tk app = TkImageAlignmentWizard(root) root.mainloop finally: if haveDoc: redrawAll

if __name__ == '__main__': main