Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 321 lines
  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.