#!/usr/bin/env python """software_license # This program 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. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """ # Based on Batch resize code by Carol Spears # Based on contactsheet by R. Gilham and E. Sullock Enzlin # Photoprint by Elmar Sullock Enzlin at moroquendo@gmail.com # See for details my website at www.sullockenzlin.demon.nl # pdb.python_fu_photo_print(.......) #to be changed import os import os.path import gimp from gimpfu import * from math import ceil, floor #============================================================================== #================= localization with "photoprint.mo" ========================== #============================================================================== #no photoprint.mo yet gettext.install("photoprint", gimp.locale_directory, unicode=True) #============================================================================== #=========== function only used for testpurposes and error logging ============ #============================================================================== def Log(text): filename = ("c:/tmp/gimp.log") f=file(filename, "a+") f.write(text+"\n") f.close() return #============================================================================== #=============== Makes an overview of the images and directorys =============== #============================================================================== # input: text: text to save # contact_location: directory to save # output: txt file with imagename and dirname were the image is located #============================================================================== def LogFileName(text, contact_location, FirstRun): Filename = (contact_location + '/' + 'photoprint' + '.txt') if (FirstRun == True): if (os.path.isfile(Filename) == True): os.remove(Filename) f=file(Filename, "a+") f.write(text + "\n") f.close() return #============================================================================== #================= get images from eventually all dirs ======================== #============================================================================== # input: FileType: array contains one or more extensions # original_location: directory to start # all_subdirs: bool to include subdirectory's too # DirFileList: bool to give a txt list of images with their dir # output: images: array with images #============================================================================== def get_images(FileType, original_location, all_subdirs): images = [] if (FileType == 5): FileType = '.jpg .jpeg .JPG .JPEG .png .PNG .tiff .tif .TIF .TIFF .pcx .PCX .xcf .XCF' elif (FileType == 4): FileType = '.xcf .XCF' elif (FileType == 3): FileType = '.pcx .PCX' elif (FileType == 2): FileType = '.tiff .tif .TIF .TIFF' elif (FileType == 1): FileType = '.png .PNG' elif(FileType == 0): FileType = '.jpg .jpeg .JPG .JPEG' else: FileType = '' Log("error in file type") if (all_subdirs == True): #include all subdirectory's for dirpath, dirnames, filenames in os.walk(original_location, topdown=True): for filename in filenames: basename, ext = os.path.splitext(filename) if ((len(ext)>2) and (ext in FileType)): imagefile = os.path.join(dirpath, filename) original_image = {'extension':ext,'base_name':basename,'image_file':imagefile} if os.path.isfile(imagefile): images.append(original_image) else: #only the choosen directory for filename in os.listdir(original_location): basename, ext = os.path.splitext(filename) if ((len(ext)>2) and (ext in FileType)): imagefile = os.path.join(original_location, filename) original_image = {'extension':ext,'base_name':basename,'image_file':imagefile} if os.path.isfile(imagefile): images.append(original_image) # Log(str(original_image)) return images #============================================================================== #================= save photoprint as a png file ============================== #============================================================================== def save_png(image, drawable, new_filelocation, use_comment): compression = 9 interlace, bkgd = False, False gama, offs, phys = False, False, False time, svtrans = True, False pdb.file_png_save2(image, drawable, new_filelocation, new_filelocation, interlace, compression, bkgd, gama, offs, phys, time, use_comment, svtrans) #============================================================================== #================= save photoprint as a jpg file ============================== #============================================================================== #save in highest quality def save_jpeg(image, name, comment=""): jpeg_save_defaults = (1.0, 0.0, 1, 0, "", 1, 0, 0, 0) args = list(jpeg_save_defaults) args[4] = comment pdb.file_jpeg_save(image, image.active_layer, name, name, *args) #============================================================================== #========= resize and eventually rotate and crop photo to allowed size ======== #============================================================================== # input: filename: photo to resize # Photo_width: maximum photo width # Photo_height: maximum photo height # crop: crop yes or no # aspect_ratio photo has a fixed size or not # output: img: resized photo # new[0] x-size of the photo # new[1] y-size of the photo #============================================================================== def generate_foto(filename, Photo_width, Photo_height, crop, aspect_ratio): # Log('Entering generate_foto routine') if (aspect_ratio < 7): #sizes depends on settings fixed_size = False if (aspect_ratio == 0): AR = 4/float(3) elif (aspect_ratio == 1): AR = 16/float(9) elif (aspect_ratio == 2): AR = 3/float(2) elif (aspect_ratio == 3): AR = 5/float(4) elif (aspect_ratio == 4): AR = 6/float(7) elif (aspect_ratio == 5): AR = 1/float(1) elif (aspect_ratio == 6): #none AR = 4/float(3) else: #fixed sizes crop = True fixed_size = True AR = 0 img = pdb.gimp_file_load(filename,filename) #load Photo #first rotate the photo 90 degrees if the longest side is vertical if (img.height>img.width): pdb.gimp_image_rotate(img,0) crop_x = False crop_y = False ratio = img.width/float(img.height) #aspect of the image # Log('AR: ' + str(AR) + ' ratio: ' + str(ratio)) if (fixed_size == False): if (aspect_ratio == 6): #none, ratio depends on photo #Log('aspect ratio = none and fixed size = False') if (crop == False): new_Photo_size = (int(Photo_height*ratio),Photo_height) #Log(str(new_Photo_size)) if (new_Photo_size[0]>Photo_width): new_Photo_size = (Photo_width, int(Photo_width/ratio)) #exactly fits else: scale_y = Photo_height/float(img.height) #image should be scaled lineair scale_x = Photo_width/float(img.width) if (scale_x < scale_y): #scale lineair at maximum and crop width new_Photo_size = (Photo_height*ratio, Photo_height) #height is correct crop_x = True else: #scale lineair at maximum and crop height new_Photo_size = (Photo_width, Photo_width/ratio) #width is correct crop_y = True else: #given aspect ratio #Log('aspect ratio <> none') if (crop ==False): #scale minimum it's the same as none, aspect of photo will be kept new_Photo_size = (int(Photo_height*ratio),Photo_height) #Log(str(new_Photo_size)) if (new_Photo_size[0]>Photo_width): new_Photo_size = (Photo_width, int(Photo_width/ratio)) #exactly fits else: #scale maximum and crop photo to given aspect scale_y = Photo_height/float(img.height)#image should be scaled lineair first scale_x = Photo_width/float(img.width) if (scale_x < scale_y): #scale lineair at maximum and crop width new_Photo_size = (Photo_height*ratio, Photo_height) #height is correct crop_x = True else: #scale lineair at maximum and crop height new_Photo_size = (Photo_width, Photo_width/ratio) #width is correct crop_y = True else: #predefined fixed sizes scale_y = Photo_height/float(img.height) #image should be scaled lineair scale_x = Photo_width/float(img.width) if (scale_x < scale_y): #scale lineair at maximum and crop width new_Photo_size = (Photo_height*ratio, Photo_height) #height is correct crop_x = True else: #scale lineair at maximum and crop height new_Photo_size = (Photo_width, Photo_width/ratio) #width is correct crop_y = True #Log('crop_x: '+ str(crop_x) + ' crop_y: '+ str(crop_y)) #Log('size_x: '+ str(new_Photo_size[0])+ ' size_y: '+ str(new_Photo_size[1])) #Log('max. photosize_x: ' + str(Photo_width) + ' photosize_y: '+ str(Photo_height)) if (crop == True): #scale maximum and crop pdb.gimp_image_scale(img,new_Photo_size[0],new_Photo_size[1]) #scale first #and now crop to fixed size or given aspect ratio if (crop_x == True): if (aspect_ratio < 7): #Log('crop_x is True en aspect ratio <7') #crop met aspect offx = (new_Photo_size[0] - Photo_width)/float(2) offy = 0 else: #Log('crop_x is True en aspect ratio >6') offx = (new_Photo_size[0] - Photo_width)/float(2) offy = 0 new_Photo_size = (Photo_width, Photo_height) #Log('offx: ' + str(offx) + ' offy: ' + str(offy)) pdb.gimp_image_crop(img, Photo_width, new_Photo_size[1], offx , offy) else: if (aspect_ratio < 7): #Log('crop_x is False en aspect ratio <7') #crop met aspect offx = 0 offy = (new_Photo_size[1] - Photo_height)/float(2) else: #Log('crop_x is False en aspect ratio >6') offx = 0 offy = (new_Photo_size[1] - Photo_height)/float(2) new_Photo_size = (Photo_width, Photo_height) #Log('offx: ' + str(offx) + ' offy: ' + str(offy)) pdb.gimp_image_crop(img, new_Photo_size[0], Photo_height, offx, offy) else: #scale minimum and no cropping #or is gimp_image_resize better ?????? pdb.gimp_image_scale(img,new_Photo_size[0],new_Photo_size[1]) #scale photo return img,new_Photo_size[0],new_Photo_size[1] #============================================================================== #================= modify fontsize so it fits Photo width ===================== #============================================================================== def CalcFontSize(text, Font, Size, CalcTextHeight, max_width): #this procedure calculates the text size to fit within the #width param, the text is reduced until the width is small enough txtw,txtH,txte,txtd = pdb.gimp_text_get_extents_fontname(text,Size,PIXELS,Font) if (txtw<=max_width): return Size,txtw while ((txtw>max_width) and (Size>0)): Size = Size -1 txtw,txtH,txte,txtd = pdb.gimp_text_get_extents_fontname(text,Size,PIXELS,Font) return Size,txtw #============================================================================== #===================== calculate papersize in pixels ========================== #============================================================================== #input: Contactsize papersize # dpi desired resolution #output:width width in pixels of the papersize # height height in pixels of the papersize #============================================================================== def CalcPaperSize(ContactSize, dpi, orient): if (ContactSize == 0): #Jumbo width,height = (102,152) #sizes are in mm (bxh) elif (ContactSize == 1): #6x8 width,height = (152,203) elif (ContactSize == 2): #8x10 width,height = (203,254) elif (ContactSize == 3): #A4 width,height = (210,297) elif (ContactSize == 4): #A3 width,height = (297,420) elif (ContactSize == 5): #Letter width,height = (216,279) elif (ContactSize == 6): #Legal width,height = (216,356) elif (ContactSize == 7): #Tabloid width,height = (279,432) else: width,height = (210,297) #Default Log("error in pagesize, pagesize doesnot exist") if (orient == "land"): Height = int(width * dpi / 25.4) # calculate width in px Width = int(height * dpi / 25.4) # calculate height in px #Log('Landscape papiergrootte in px (bxh): ' + str(Width) + ' , ' + str(Height)) else: Width = int(width * dpi / 25.4) # calculate width in px Height = int(height * dpi / 25.4) # calculate height in px #Log('Portret papiergrootte in px (bxh): ' + str(Width) + ' , ' + str(Height)) return Width, Height #size in pixels (integer) #============================================================================== #================== calculate maximum photosize in pixels ===================== #============================================================================== #input: CanvasWidth canvaswidth in px # CanvasHeight canvasheight in px # numcols int number of columns # numrows int number of rows # AspectRatio int number # PhotoMargin margin in pixels #output:MaxPhotoWidth maximum width in pixels # MaxPhotoHeight maximum height in pixels #============================================================================== ## most common aspect ratio's (bxh) ## 4:3 -> TV ## 16:9 -> movie ## 3:2 -> SLR digital camera ## 5:4 ## 6:7 ## 1:1 ## none -> maximum size depends on paper, rows and columns ## ## otherwise predefined papersizes (bxh) ## 7x10 cm -> ## 9x12 cm -> ## 10x15 cm -> ## 13x18 cm -> ## 18x24 cm -> ## else?? #============================================================================== def CalcMaxPhotoSize(CanvasWidth, CanvasHeight, numcols, numrows, AspectRatio, PhotoMargin, dpi): fixed_size = False #------------------------size declarations-------------------------------------- ## Don't add new aspect ratios, unless you know what you are doing. Several lines ## should be new coded ## ## ("Aspect ratio: "), 0 ,["4:3", "16:9", "3:2", "5:4","6:7", "1:1", "none", ..]) ## or predefined photosizes if (AspectRatio == 0): #4:3 (bxh) PhotoSize_x,PhotoSize_y = (4,3) elif (AspectRatio == 1): #16:9 PhotoSize_x,PhotoSize_y = (16,9) elif (AspectRatio == 2): #3:2 PhotoSize_x,PhotoSize_y = (3,2) elif (AspectRatio == 3): #5:4 PhotoSize_x,PhotoSize_y = (5,4) elif (AspectRatio == 4): #6:7 PhotoSize_x,PhotoSize_y = (6,7) elif (AspectRatio == 5): #1:1 PhotoSize_x,PhotoSize_y = (1,1) elif (AspectRatio == 6): #none PhotoSize_x,PhotoSize_y = (4,3) else: ## this section contains the fixed photosizes in mm (bxh) ## You can add additional sizes here and to the register without modifying a ## lot of the code. The tallest size should be noted first (=width). fixed_size = True if (AspectRatio == 7): #7x10 cm predefined section PhotoSize_x,PhotoSize_y = (100,70) elif (AspectRatio == 8): #9x13 cm PhotoSize_x,PhotoSize_y = (127,89) elif (AspectRatio == 9): #10x15 cm PhotoSize_x,PhotoSize_y = (152,102) elif (AspectRatio == 10): #13x18 cm PhotoSize_x,PhotoSize_y = (178,127) elif (AspectRatio == 11): #18x24 cm PhotoSize_x,PhotoSize_y = (240,180) else: fixed_size = False PhotoSize_x,PhotoSize_y = (4,3) #none is the default #Log("Error in routine CalcMaxPhotoSize (AR), size doesn't exist.") AR = PhotoSize_x/float(PhotoSize_y) #------------------------------size calculations------------------------------- if (fixed_size == False): PhotoSize_x = int((CanvasWidth-(numcols-1)*PhotoMargin)/(numcols)) PhotoSize_y = int((CanvasHeight-(numrows-1)*PhotoMargin)/(numrows)) #Log('CalcMaxPhotoSize voor(bxh): ' + str(PhotoSize_x) + ' , ' + str(PhotoSize_y)) #Log('Aspect ratio: ' + str(AspectRatio)) # correct for aspect ratio if necessary if (AspectRatio < 6): #given aspect ratio else none #AR = PhotoSize_x/float(PhotoSize_y) #floating(aspect-ratio) #Log('Routine aspect ratio: ' + str(AR)) if (PhotoSize_y > int(PhotoSize_x / AR)): #aspect ratio is wrong PhotoSize_y = int(PhotoSize_x / AR) #resize y, x is oke #Log('Photosize_y aanpassen') else: PhotoSize_x = int(AR * PhotoSize_y) #resize x, y is oke #Log('Photosize_x aanpassen') MaxPhotoWidth = PhotoSize_x #sizes are now conform the aspect ratio MaxPhotoHeight = PhotoSize_y else: #fixed_size is true MaxPhotoWidth = int(PhotoSize_x * dpi / 25.4) MaxPhotoHeight = int(PhotoSize_y * dpi / 25.4) #Log('CalcMaxPhotoSize na(bxh): ' + str(MaxPhotoWidth) + ' , ' + str(MaxPhotoHeight)) return MaxPhotoWidth, MaxPhotoHeight #sizes are in px (integers) #============================================================================== #================= main routine generate photo print ========================= #============================================================================== def Photo_Print(file_type, location, all_subdirs, contact_name, contact_type, contact_location, aspect_ratio, crop_photos, contact_size, dpi, orient, num_col, num_rows, PageBorderL, PageBorderR, PageBorderT, PageBorderB, mmFOTO_MARGIN, print_direct): #collect 'all' images in the choosen directory and subdirs images = get_images(file_type, location, all_subdirs) num_images = len(images) #calculate number of images #make a new drawing canvas of the correct size width,height = CalcPaperSize(contact_size, dpi, orient) #papersize dimensions in px #Log('papiergrootte in px (bxh): ' + str(width) + ' , ' + str(height)) #calculate the maximum size for the fotos based on the number of images #per row and number of rows. #Sizes are in px LEFT_PAGE_BORDER = int(PageBorderL * dpi / 25.4) RIGHT_PAGE_BORDER = int(PageBorderR * dpi / 25.4) BOTTOM_PAGE_BORDER = int(PageBorderB * dpi / 25.4) TOP_PAGE_BORDER = int(PageBorderT * dpi / 25.4) PHOTO_MARGIN = int(mmFOTO_MARGIN * dpi / 25.4) #calculate canvas size in px (the printable sheet dimensions) CanvasWidth = width - LEFT_PAGE_BORDER - RIGHT_PAGE_BORDER CanvasHeight = height - BOTTOM_PAGE_BORDER - TOP_PAGE_BORDER #Log('canvasgrootte in px (bxh): ' + str(CanvasWidth) + ' , ' + str(CanvasHeight)) #calculate max photo size in px Photo_width, Photo_height = CalcMaxPhotoSize(CanvasWidth, CanvasHeight, num_col, num_rows, aspect_ratio, PHOTO_MARGIN, dpi) #Log('foto grootte in px (bxh): ' + str(Photo_width) + ' , ' + str(Photo_height)) PhotosPerSheet = int(num_col*num_rows) img_no = 1 for sheetcount in range(int(ceil(num_images/float(PhotosPerSheet)))): sheetimg = gimp.Image(width,height,RGB) #make sheet of the desired size bklayer = gimp.Layer(sheetimg,"Background",width,height, RGB_IMAGE,100,NORMAL_MODE) sheetimg.disable_undo() sheetimg.add_layer(bklayer,0) sheetimg.resolution = (float(dpi), float(dpi)) bklayer.fill(WHITE_FILL) bklayer.flush() sheetdsp = gimp.Display(sheetimg) gimp.displays_flush() CalcTextHeight =0 txtw,txth,txte,txtd = (0,0,0,0) files = images[sheetcount*PhotosPerSheet:(sheetcount+1)*PhotosPerSheet] #now for each of the image files generate a photo rcount = 0 ccount = 0 #generate photo for file in files: thumbimg,x_size,y_size = generate_foto(file['image_file'],Photo_width,Photo_height, crop_photos, aspect_ratio) cpy = pdb.gimp_edit_copy(thumbimg.active_layer) #center image within its minipage if (x_size>y_size): #landscape image, center vertical y_offset = (Photo_width - y_size)/2 x_offset = 0 else: #portrait image, center horizontal x_offset = (Photo_height - x_size)/2 y_offset = 0 gimp.delete(thumbimg) #now paste the new thumb into contact sheet newselect = pdb.gimp_edit_paste(sheetimg.active_layer,True) #positition in top left corner newselect.translate(-newselect.offsets[0],-newselect.offsets[1]) #now position in correct position, modified with x- and y-offset xpos = LEFT_PAGE_BORDER + ccount * (Photo_width + PHOTO_MARGIN) ypos = TOP_PAGE_BORDER + rcount * (Photo_height + PHOTO_MARGIN ) newselect.translate(xpos,ypos) pdb.gimp_floating_sel_anchor(newselect) ccount = ccount + 1 if (ccount>= num_col): ccount = 0 rcount = rcount + 1 gimp.displays_flush() #save photoprint contact_filename = contact_name + "_%03d" % (sheetcount) + contact_type contact_full_filename = os.path.join(contact_location, contact_filename) #print "File to save " + contact_full_filename if (contact_type == ".jpg"): save_jpeg(sheetimg,contact_full_filename,"") else: save_png(sheetimg,pdb.gimp_image_get_active_drawable(sheetimg), contact_full_filename,False) if (print_direct == True): pdb.file_print_gtk(sheetimg) gimp.delete(sheetimg) pdb.gimp_display_delete(sheetdsp) register( "python_fu_photo_print", _("Generates photoprint(s) for a directory of images. If you find this script useful or any bugs I would love to hear from you at moroquendo@gmail.com\n"), _("Generates photoprint(s) and print with a configurable number of photos for all photofiles located in a directory and subdirectory"), "E. Sullock Enzlin", "Licensed under the GPL v2", "2009", "/Xtns/Batch/Photo Print", "", [ (PF_OPTION, "file_type" ,_("File type: "), 0 ,[".jpg", ".png", ".tif", ".pcx", ".xcf", _("all registered formats")]), (PF_DIRNAME, "location", _('Generate photo print of\n all files in this directory: '), ""), (PF_BOOL, "all_subdirs", _("Include all subdirs? "), False), (PF_STRING, "contact_name", _('Photoprint base name: '), _('photoprint')), (PF_RADIO, "contact_type", _('Photo print image type: '), ".jpg", (("jpg", ".jpg"), ("png", ".png"))), (PF_DIRNAME, "contact_location", _('Where the photo print should be saved in: '), ""), (PF_OPTION, "aspect_ratio" ,_("Aspect ratio or size: "), 0 , ["4:3", "16:9", "3:2", "5:4","6:7", "1:1", "none", "7x10", "9x13", "10x15", "13x18", "18x24"]), (PF_BOOL, "crop_photos" ,_("Crop photos? "), False), (PF_OPTION, "contact_size", _("Photo page size: "), 3, ["Jumbo (10.2x15.2 cm)", "6x8 (15.2x20.3 cm)", "8x10 (20.3x25.4 cm)", "A4 (20.9x29.7 cm)", "A3 (29.7x42.0 cm)", "Letter (8.5x11 in)", "Legal (8.5x14 in)", "Tabloid (11x17 in)"]), (PF_SPINNER, "dpi", _("Photo page resolution"), 300,(150,1200,50)), (PF_RADIO, "orient", _("Photo page orientation:"), "port", ((_("portrait"), "port"), (_("landscape"),"land"))), (PF_SPINNER, "num_col", _("Number of photos per row"), 2, (1,8,1)), (PF_SPINNER, "num_rows", _("Number of rows"), 2, (1,8,1)), (PF_SPINNER, "PageBorderL", _("Left Page border [mm]"), 10, (1,32,1)), (PF_SPINNER, "PageBorderR", _("Right Page border [mm]"), 10, (1,32,1)), (PF_SPINNER, "PageBorderT", _("Top Page border [mm]"), 10, (1,32,1)), (PF_SPINNER, "PageBorderB", _("Bottom Page border [mm]"), 10, (1,32,1)), (PF_SPINNER, "mmFOTO_Margin", _("Distance between photos [mm]"), 1, (0,10,1)), (PF_BOOL, "print_direct", _("Directly printing? "), False) ], [], Photo_Print, menu="", domain=("photoprint", gimp.locale_directory) ) main()