Image Wizard: Scale and Align an Image

From Scribus Wiki
Jump to navigation Jump to search
This article is part of the Scripts series.

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:

ImageWizard3.png

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

# License
# Copyright 2007, Jeremy Brown (TrnsltLife)
# This script 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.

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()