Waste no mouse clicks making multiple crops on many image files. Through pygame interface with pan, zoom and next/previous image. Saves files at new resolution and serialized names in a seperate folder. the mainloop() and helper functions are easy to reuse, but I include a cruddy text based interface if needed. if not, comment out most of __main__().
Requires PIL (python imaging library) and pygame.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 | import os
import pygame, sys
from pygame.locals import K_a, K_s,K_w,K_d,K_LEFTBRACKET,K_RIGHTBRACKET, K_RIGHT, K_LEFT, QUIT
from PIL import Image
pygame.init()
'''
main() sets these (perhaps differently), so make changes down there.
If you cut/copy this code somewhere you need these variables globally,
or make it a class and make these attributes.
'''
resolution = 300 #the resolution (in dpi) the resulting cropped images should have.
infile_folder = '.' #path to folder images to process are in. '.' is the folder this script is in
infile_prefix = "album80-86_" #prefix common to all the images you'd like to access
start_page = 1 #which page to start on. 0 is the first page.
outfile_folder= "./cropped"
outfile_prefix = "photo80-86_"
outfile_extension = "jpg" #must be three character extension with no period. Why? Because I am lazy. So no "jpeg", okay?
BG_COLOR = (0,0,0)
def displayRect(screen, px, topleft, prior,pos,scale):
# ensure that the rect always has positive width, height
if topleft == None:
#func was called without a topleft, which means clear the previous rectangle
screen.fill(BG_COLOR)
rect = px.get_rect()
px = pygame.transform.scale(px,[int(rect.width/scale), int(rect.height/scale)])
screen.blit(px, (rect[0]-pos[0],rect[1]-pos[1]))
pygame.display.flip()
return None
#or, the usual situation, topleft is defined, so blit over the old rect and blit in the new.
topleft = [(val/scale-pos[i]) for i,val in enumerate(topleft)]
x, y = topleft
bottomright = pygame.mouse.get_pos()
width = bottomright[0] - topleft[0]
height = bottomright[1] - topleft[1]
if width < 0:
x += width
width = abs(width)
if height < 0:
y += height
height = abs(height)
# eliminate redundant drawing cycles (when mouse isn't moving)
current = x, y, width, height
if not (width and height):
return current
if current == prior:
return current
# draw transparent box and blit it onto canvas
rect = px.get_rect()
px = pygame.transform.scale(px,[int(rect.width/scale), int(rect.height/scale)])
screen.blit(px, (rect[0]-pos[0],rect[1]-pos[1]))
im = pygame.Surface((width, height))
im.fill((128, 128, 128))
pygame.draw.rect(im, (32, 32, 32), im.get_rect(), 1)
im.set_alpha(128)
screen.blit(im, (x, y))
pygame.display.flip()
# return current box extents
return (x, y, width, height)
def setup(px):
screen = pygame.display.set_mode( px.get_rect()[2:] )
screen.blit(px, px.get_rect())
pygame.display.flip()
return screen, px
def move(pos,scale,px,screen):
x,y = pos
#print pos,x
rect = px.get_rect()
screen.fill(BG_COLOR)
px = pygame.transform.scale(px,[int(rect.width/scale), int(rect.height/scale)])
screen.blit(px, (rect[0]-x,rect[1]-y))
pygame.display.flip()
#px.rect.topleft = pr.rect.topleft[0] - x,
def mainLoop():
topleft = bottomright = prior = None
n=0
scale = 1
pos = [0,0]
#create list of files matching prefix in folder, and sort it
# input_loc = first file #input_loc = 'album86-92_003.jpg'
infiles = []
len_prefix = len(infile_prefix)
for fname in os.listdir(infile_folder):
if fname[:len_prefix] == infile_prefix:
if fname[-3:] in ['jpg','png']:
infiles.append(fname)
infiles.sort()
file_idx = start_page
try:
infile = infiles[file_idx]
except IndexError:
print "the start page you requested is beyond the scope of the files loaded.\nYou have been taken to the last page instead."
file_idx = len(infiles)-1
infile = infiles[file_idx]
#get files begining with output prefix, grab suffixes and sort.
#but, if folder does not exist or is empty, just start at 0
outfiles = []
len_prefix = len(outfile_prefix)
try:
for fname in os.listdir(outfile_folder):
if fname[:len_prefix] == outfile_prefix:
outfiles.append(fname)
except OSError:
os.makedirs(outfile_folder)
out_idx = 0
else:
outfiles.sort()
try:
out_idx = int(outfiles[-1][len_prefix:-4])+1
except ValueError:
print "Egad! Not all files with the output prefix specified end with a number followed by a three character extension\nMaybe start a new output folder?"
print "...Quitting"
return 0
except IndexError:
#folder exisits but is empty
out_idx = 0
input_loc = os.path.join(infile_folder,infile)
screen, px = setup(px = pygame.image.load(input_loc))
outfilename = outfile_prefix+str(out_idx).zfill(3)+'.'+outfile_extension
output_loc = os.path.join(outfile_folder,outfilename)
while n!=1:
for event in pygame.event.get():
if event.type == QUIT:
sys.exit(0)
if event.type == pygame.MOUSEBUTTONUP:
if not topleft:
topleft = [(val+pos[i])*scale for i,val in enumerate(event.pos)]
#print "tr: ",topleft
else:
bottomright = [(val+pos[i])*scale for i,val in enumerate(event.pos)]
#print "br: ",bottomright
if event.type == pygame.KEYDOWN and event.key == K_a:
pos = [pos[0]-200,pos[1]]
move(pos,scale,px,screen)
if event.type == pygame.KEYDOWN and event.key == K_d:
pos = [pos[0]+200,pos[1]]
move(pos,scale,px,screen)
if event.type == pygame.KEYDOWN and event.key == K_w:
pos = [pos[0],pos[1]-200]
move(pos,scale,px,screen)
if event.type == pygame.KEYDOWN and event.key == K_s:
pos = [pos[0],pos[1]+200]
move(pos,scale,px,screen)
if event.type == pygame.KEYDOWN and event.key == K_RIGHTBRACKET:
scale = scale/1.25
move(pos,scale,px,screen)
if event.type == pygame.KEYDOWN and event.key == K_LEFTBRACKET:
scale = scale*1.25
move(pos,scale,px,screen)
if event.type == pygame.KEYDOWN and event.key == K_RIGHT:
file_idx += 1
try:
infile = infiles[file_idx]
#print "file_idx: ",file_idx
except IndexError:
file_idx -= 1
print "End of album"
#raise
else:
input_loc = os.path.join(infile_folder,infile)
px = pygame.image.load(input_loc)
pos = [0,0]
topleft = bottomright = prior = None
prior = displayRect(screen, px, topleft, prior,pos,scale)
if event.type == pygame.KEYDOWN and event.key == K_LEFT:
if file_idx == 0:
print "This is the begining of the album, cannot go back a page."
else:
#print "file_idx",file_idx
file_idx -= 1
infile = infiles[file_idx]
input_loc = os.path.join(infile_folder,infile)
px = pygame.image.load(input_loc)
pos = [0,0]
topleft = bottomright = prior = None
prior = displayRect(screen, px, topleft, prior,pos,scale)
if topleft:
#first corner has been selected
prior = displayRect(screen, px, topleft, prior,pos,scale)
if bottomright:
#selection has been made!
left, upper, right, lower = ( topleft + bottomright )
# ensure output rect always has positive width, height
if right < left:
left, right = right, left
if lower < upper:
lower, upper = upper, lower
im = Image.open(input_loc)
im = im.crop(( int(left), int(upper), int(right), int(lower)))
dpi = resolution
im.save(output_loc, dpi = (dpi,dpi))
out_idx += 1
outfilename = outfile_prefix+str(out_idx).zfill(3)+'.'+outfile_extension
output_loc = os.path.join(outfile_folder,outfilename)
topleft = bottomright = prior = None
prior = displayRect(screen, px, topleft, prior,pos,scale)
print "saved"
return
if __name__ == "__main__":
os.system( [ 'clear', 'cls' ][ os.name == 'nt' ] )
print'''
Hello!
This program exists to speed up cropping out many sections from larger images
while also changing the resolution of the cropped images.
The Zudell family photo album was scanned at 600 dpi resolution.
The default resolution for cropped images is 300 dpi.
'''
resolution = raw_input('enter new integer resolution, or nothing for default: ')
os.system( [ 'clear', 'cls' ][ os.name == 'nt' ] )
try: resolution = int(resolution)
except:
print '\nNo new resolution specified, using 300 dpi'
resolution = int(300)
dirs = []
for f in os.listdir('.'):
if os.path.isdir(f):
dirs.append(f)
print '''\n\n\n\n
now, enter the name of the directory you want to work on. here is a list of sub
directories within this current directory:\n'''
if dirs:
for dir in dirs: print dir
else:
print "oops, there are no sub-directories here"
print "\n\nenter nothing or nonsense to use the current directory"
path = raw_input("enter directory to use: ")
infile_folder = path.strip()
os.system( [ 'clear', 'cls' ][ os.name == 'nt' ] )
if os.path.isdir(infile_folder):
pass
elif os.path.isdir('./'+infile_folder):
infile_folder = './'+infile_folder
else:
print "no valid directory entered, using current"
infile_folder = '.'
for f in os.listdir(infile_folder):
print f
if not os.listdir(infile_folder):
print "oh... There aren't any files at all in here"
d = raw_input("press enter to quit")
pygame.display.quit()
print '''\n\n
You may choose a filename prefix so that only some of the images in this dir
are available for editing. all files in this directory are listed above. \n'''
infile_prefix = raw_input('input file prefix (or nothing to use all files): ')
os.system( [ 'clear', 'cls' ][ os.name == 'nt' ] )
print '''\n\n
You may choose a prefix for output files also. they will go in the ./cropped folder.\n'''
outfile_prefix = raw_input('output file prefix (or nothing for default): ')
if not outfile_prefix: outfile_prefix = "image_"
os.system( [ 'clear', 'cls' ][ os.name == 'nt' ] )
print '''
Use the left ard right arrows to change image.
'[' and ']' zoom out and in, respectively.
click and drag a box to crop.
too move around:
w
asd
And come back to this screen to see unnecessary messages.
'''
raw_input('\npress enter to begin')
mainLoop()
pygame.display.quit()
|
I haven't tested png's, but should work.
You may want to change the buttons.
I wrote this because we scanned 150+ pages of photo albums (at 600 dpi) and wanted to crop individual photos out at a lower resolution.