Align an Image in its Frame

From Scribus Wiki
Revision as of 02:51, 20 April 2014 by Gpittman (talk | contribs) (→‎TkInter Version 2)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
This article is part of the Scripts series.

Under Scribus' "Windows" menu, there is a very nice option called "Align and Distribute". This lets you align items on the page, for example centering them horizontally or vertically, aligning them to the left margin, etc. However, I have not found any way to align an image in its frame.

This script will give you a dialog window, allowing you to select on of 9 alignments:

  • Top Left, Top Center, Top Right
  • Middle Left, Middle Center, Middle Right
  • Bottom Left, Bottom Center, Bottom Right

When you press the "Align" button, it will align all the selected images in their frames, using the alignment option you selected. It has been tested in Scribus 1.3.3.9 and 1.3.4. It requires Tkinter to be properly installed.

Of course, if you need more fine-tuned adjustments than these 9 options, you should probably use the Image Properties toolbar to manually set the X and Y offsets of the image in its frame.

See also this script (Image Wizard: Scale and Align an Image) which can scale and align images.

Warning: You can leave the script dialog open, and continue to process more images. 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!

New Version Fixed for 1.4.3/1.5.0

Aside from fixing it so that it works, I have also changed the import syntax to the preferred:
import scribus
rather than
from scribus import *.

This script has been modified again. The original command, scaleImage() is deprecated and no longer works, so now use setImageScale().

Something to note is that when you click the Align button, the work is done, but you don't see the results until you click Done – this is because the document isn't redrawn until then.

How It Works

When you start the script it first calls the Tkinter function, which sets up the dialog to choose your position for the image in the frame. When you click the Align button, the AlignImage function is called.

AlignImage is rather ingenious in how it determines how far to move the origin of the image in the frame. First, it gets the image scale settings (saveScaleX and saveScaleY). We can't really know the dimensions of this image in page units since there is no Scripter command for that. It then resizes image to frame (with keep proportions off), and rechecks the image scale again (fullScaleX and fullScaleY). We know the dimensions of this resized image, since it's the dimensions of the frame. Once it gets this information, the script then returns the image back to its original scaling.

At this point we can calculate the actual dimensions the image had originally (imageW and imageH) using the relative scales and the frame width/height values. Next, the value sent from the Tkinter function is parsed to determine how to calculate the new X and Y offsets, using frameW, frameH, imageW and imageH. Because there is a loop for all selected objects, you then move on to the next selected object and go through this same process.

It's also worth mentioning that this script also works when the scaled image is larger than the frame.

A modified version, farther down this wiki page (TkInter Version 2), has been included with Scribus 1.4.4svn.

#!/usr/bin/python
# Image_align_frame.py
# This version 2014.01.25
"""
This script will align an image inside a frame to one of 9 possible positions: 
Top left, top center, top right; middle left, middle center, middle right; 
or bottom left, bottom center, bottom right.

USAGE
Select one or more image frames. Run the script, which asks for your alignment 
choice (all selected frames will need to have the same alignment). Choose the 
position in the dialog radio button grid, click Align, then Done.

Note
There is minimal error checking, in particular no checking for frame type.

"""

import scribus
 
try:
    from Tkinter import *
    from tkFont import Font
except ImportError:
    print "This script requires Python's Tkinter properly installed."
    scribus.messageBox('Script failed',
               'This script requires Python\'s Tkinter properly installed.',
               scribus.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.grid()
        self.master.resizable(0, 0)
        self.master.title('Scribus Image Alignment Wizard')
        #define widgets
        # alignment options
        self.alignLabel = Label(self, text='Select alignment:')
        self.alignVar = StringVar()
        self.alignRadio1 = Radiobutton(self, text='', variable=self.alignVar, value="TL")
        self.alignRadio2 = Radiobutton(self, text='', variable=self.alignVar, value="TC")
        self.alignRadio3 = Radiobutton(self, text='', variable=self.alignVar, value="TR")
        self.alignRadio4 = Radiobutton(self, text='', variable=self.alignVar, value="ML")
        self.alignRadio5 = Radiobutton(self, text='', variable=self.alignVar, value="MC")
        self.alignRadio6 = Radiobutton(self, text='', variable=self.alignVar, value="MR")
        self.alignRadio7 = Radiobutton(self, text='', variable=self.alignVar, value="BL")
        self.alignRadio8 = Radiobutton(self, text='', variable=self.alignVar, value="BC")
        self.alignRadio9 = Radiobutton(self, text='', variable=self.alignVar, value="BR")
        self.alignButton = Button(self, text='Align', command=self.alignImage)
        # closing/running
        self.doneButton = Button(self, text="Done", command=self.quit)
        # setup values
        self.alignRadio5.select()
        # make layout
        self.columnconfigure(0, pad=0)
        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)
 
    def alignImage(self):
        if scribus.haveDoc():
            nbrSelected = scribus.selectionCount()
            objList = []
            for i in range(nbrSelected):
                objList.append(scribus.getSelectedObject(i))
            for i in range(nbrSelected):
                try:
                    obj = objList[i]
                    frameW, frameH = scribus.getSize(obj)
                    saveScaleX, saveScaleY = scribus.getImageScale(obj)
                    scribus.setScaleImageToFrame(1, 0, obj)
                    fullScaleX, fullScaleY = scribus.getImageScale(obj)
                    scribus.setScaleImageToFrame(0, 0, obj)
                    scribus.setImageScale(saveScaleX, saveScaleY, obj) # corrected command
                    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
                    elif self.alignVar.get()[0] == "B":
                        imageY = (frameH - imageH)
                    if self.alignVar.get()[1] == "L":
                        imageX = 0.0
                    elif self.alignVar.get()[1] == "C":
                        imageX = (frameW - imageW) / 2.0
                    elif self.alignVar.get()[1] == "R":
                        imageX = (frameW - imageW)
 
                    scribus.setImageOffset(imageX, imageY, obj)
                    scribus.docChanged(1)
                    scribus.setRedraw(True)
                except:
                    nothing = "nothing"
 
    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 scribus.haveDoc():
            scribus.redrawAll()
 
if __name__ == '__main__':
    main()

TkInter Version 2

This version has been committed to 1.4.4svn. Compared with the previous version, this one has stripped out the Done button. When you click Align, the script carries out the operations and quits.

A late problem identified was that when more than one object was selected, the final scale of all objects ended up being the same. The problem seemed to be that when more than one object was selected, any usage of setImageScale is applied to all. Therefore, this was prevented by a deselectAll command after the selection list was obtained, then one-by-one selecting, then deselecting in the alignment for loop.

#!/usr/bin/python
# Align_image_in_frame.py
# This version 2014.04.19
"""
This script will align an image inside a frame to one of 9 possible positions: 
Top left, top center, top right; middle left, middle center, middle right; 
or bottom left, bottom center, bottom right.
 
USAGE
Select one or more image frames. Run the script, which asks for your alignment 
choice (all selected frames will need to have the same alignment). Choose the 
position in the dialog radio button grid, click Align. Image(s) are aligned, and script quits.
 
Note
There is minimal error checking, in particular no checking for frame type.

See the wiki page for further info:
wiki.scribus.net/canvas/Align_an_Image_in_its_Frame
 
"""
 
import scribus
 
try:
    from Tkinter import *
    from tkFont import Font
except ImportError:
    print "This script requires Python's Tkinter properly installed."
    scribus.messageBox('Script failed',
               'This script requires Python\'s Tkinter properly installed.',
               scribus.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.grid()
        self.master.geometry('120x120-80+40')
        self.master.title('Scribus Image Alignment Wizard')
        #define widgets
        # alignment options
        self.alignLabel = Label(self, text='Select alignment:')
        self.alignVar = StringVar()
        self.alignRadio1 = Radiobutton(self, text='', variable=self.alignVar, value="TL")
        self.alignRadio2 = Radiobutton(self, text='', variable=self.alignVar, value="TC")
        self.alignRadio3 = Radiobutton(self, text='', variable=self.alignVar, value="TR")
        self.alignRadio4 = Radiobutton(self, text='', variable=self.alignVar, value="ML")
        self.alignRadio5 = Radiobutton(self, text='', variable=self.alignVar, value="MC")
        self.alignRadio6 = Radiobutton(self, text='', variable=self.alignVar, value="MR")
        self.alignRadio7 = Radiobutton(self, text='', variable=self.alignVar, value="BL")
        self.alignRadio8 = Radiobutton(self, text='', variable=self.alignVar, value="BC")
        self.alignRadio9 = Radiobutton(self, text='', variable=self.alignVar, value="BR")
        self.alignButton = Button(self, text='Align', command=self.alignImage)

        # setup values
        self.alignRadio5.select()
        # make layout
        self.columnconfigure(0, pad=0)
        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)
 
    def alignImage(self):
        if scribus.haveDoc():
	    restore_units = scribus.getUnit()   # since there is an issue with units other than points,
	    scribus.setUnit(0)			# we switch to points then restore later.
            nbrSelected = scribus.selectionCount()
            objList = []
            for i in range(nbrSelected):
                objList.append(scribus.getSelectedObject(i))
            scribus.deselectAll()
            for i in range(nbrSelected):
                try:
                    obj = objList[i]
                    scribus.selectObject(obj)
                    frameW, frameH = scribus.getSize(obj)
                    saveScaleX, saveScaleY = scribus.getImageScale(obj)
                    scribus.setScaleImageToFrame(1, 0, obj)
                    fullScaleX, fullScaleY = scribus.getImageScale(obj)
                    scribus.setScaleImageToFrame(0, 0, obj)
                    scribus.setImageScale(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
                    elif self.alignVar.get()[0] == "B":
                        imageY = (frameH - imageH)
                    if self.alignVar.get()[1] == "L":
                        imageX = 0.0
                    elif self.alignVar.get()[1] == "C":
                        imageX = (frameW - imageW) / 2.0
                    elif self.alignVar.get()[1] == "R":
                        imageX = (frameW - imageW)
 
                    scribus.setImageOffset(imageX, imageY, obj)
                    scribus.docChanged(1)
                    scribus.setRedraw(True)
                    scribus.deselectAll()
                except:
                    nothing = "nothing"
	    scribus.setUnit(restore_units)
	    
	    self.master.destroy()
 
 
def main():
    """ Application/Dialog loop with Scribus sauce around """
    try:
        root = Tk()
        app = TkImageAlignmentWizard(root)
        root.mainloop()
    finally:
        if scribus.haveDoc():
            scribus.redrawAll()
 
if __name__ == '__main__':
    main()

New Version NOT Using Tkinter

Here I have completely eliminated all aspects of Tkinter.

Instead, you get a valueDialog and enter a 2-letter combination, the first being T, M, or B, and the second L, C, or R. The script allows upper or lower case entries, but if you don't follow this order, or use other letters (for example, either CM or XY), this defaults to top left positioning.

#!/usr/bin/python
# align_image_in_frame.py
# This version 2014.04.19
"""
This script will align an image inside a frame to one of 9 possible positions: 
Top left, top center, top right; middle left, middle center, middle right; 
or bottom left, bottom center, bottom right.
 
USAGE
Select one or more image frames. Run the script, which asks for your alignment 
choice. Possible legitimate entries are:
TL	TC	TR
ML	MC	MR
BL	BC	BR

and lowercase entries are also legitimate.
 
Note
There is minimal error checking, in particular no checking for frame type.
 
"""
 
import scribus
 

if scribus.haveDoc():
    restore_units = scribus.getUnit()   # since there is an issue with units other than points,
    scribus.setUnit(0)			# we switch to points then restore later.
    nbrSelected = scribus.selectionCount()
    if (nbrSelected == 0):
      scribus.messageBox('Error:', 'No frame selected')
      sys.exit(1)
    position = scribus.valueDialog('Image Position', '     T = Top,     M = Middle,     B = Bottom     \n     L = Left,     C = Center,     R = Right     \n     Example: ML = Middle Left', 'MC')
    objList = []
    for i in range(nbrSelected):
      objList.append(scribus.getSelectedObject(i))
      for i in range(nbrSelected):
        try:
          obj = objList[i]
          frameW, frameH = scribus.getSize(obj)
          saveScaleX, saveScaleY = scribus.getImageScale(obj)
          scribus.setScaleImageToFrame(1, 0, obj)
          fullScaleX, fullScaleY = scribus.getImageScale(obj)
          scribus.setScaleImageToFrame(0, 0, obj)
          scribus.setImageScale(saveScaleX, saveScaleY, obj)
          imageW = frameW * (saveScaleX / fullScaleX)
          imageH = frameH * (saveScaleY / fullScaleY)
          imageX = 0.0
          imageY = 0.0

          if (position[0] == "T") or (position[0] == "t"):
            imageY = 0.0
          elif (position[0] == "M") or (position[0] == "m"):
            imageY = (frameH - imageH) / 2.0
          elif (position[0] == "B") or (position[0] == "b"):
            imageY = (frameH - imageH)
          if (position[1] == "L") or (position[1] == "l"):
            imageX = 0.0
          elif (position[1] == "C") or (position[1] == "c"):
            imageX = (frameW - imageW) / 2.0
          elif (position[1] == "R") or (position[1] == "r"):
            imageX = (frameW - imageW)
 
          scribus.setImageOffset(imageX, imageY, obj)
          scribus.docChanged(1)
          scribus.setRedraw(True)
        except:
          nothing = "nothing"
    scribus.setUnit(restore_units)
else:
    scribus.messageBox('Error', 'No document open')
    sys.exit(1)

if scribus.haveDoc():
    scribus.redrawAll()

Original Version

I'm not sure how well this version worked originally, but at any rate it no longer worked in 1.5.0. I'm leaving it here for reference purposes, as well as acknowledging the original work done.

Save this script with a filename of Image_AlignInFrame.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:
    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.grid()
        self.master.resizable(0, 0)
        self.master.title('Scribus Image Alignment Wizard')
        #define widgets
        # alignment options
        self.alignLabel = Label(self, text='Select alignment:')
        self.alignVar = StringVar()
        self.alignRadio1 = Radiobutton(self, text='', variable=self.alignVar, value="TL")
        self.alignRadio2 = Radiobutton(self, text='', variable=self.alignVar, value="TC")
        self.alignRadio3 = Radiobutton(self, text='', variable=self.alignVar, value="TR")
        self.alignRadio4 = Radiobutton(self, text='', variable=self.alignVar, value="ML")
        self.alignRadio5 = Radiobutton(self, text='', variable=self.alignVar, value="MC")
        self.alignRadio6 = Radiobutton(self, text='', variable=self.alignVar, value="MR")
        self.alignRadio7 = Radiobutton(self, text='', variable=self.alignVar, value="BL")
        self.alignRadio8 = Radiobutton(self, text='', variable=self.alignVar, value="BC")
        self.alignRadio9 = Radiobutton(self, text='', variable=self.alignVar, value="BR")
        self.alignButton = Button(self, text='Align', command=self.alignImage)
        # closing/running
        self.doneButton = Button(self, text="Done", command=self.quit)
        # setup values
        self.alignRadio5.select()
        # make layout
        self.columnconfigure(0, pad=0)
        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)

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