Skip to content

Commit 0df1090

Browse files
committed
More explicit image lifecycle management
1 parent 260ffd2 commit 0df1090

11 files changed

Lines changed: 168 additions & 166 deletions

server/aicon.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ def calculate_crop_ratio(ratio_tuple):
6666
# Scale and crop the generated image.
6767
generated_image = response.generated_images[0]
6868
with BytesIO(generated_image.image.image_bytes) as image_data:
69-
with Image.open(image_data).convert('RGB') as image:
69+
with Image.open(image_data) as image:
70+
image = image.convert('RGB')
71+
7072
# Scale to fill.
7173
scale = max(width / image.width, height / image.height)
7274
scaled_width = int(image.width * scale)

server/artwork.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ def image(self, user, width, height, variant):
2525
filename = choice(paths)
2626
info('Using artwork file: %s' % filename)
2727

28-
with Image.open(filename).convert('RGB') as image:
28+
with Image.open(filename) as image:
29+
image = image.convert('RGB')
30+
2931
# Crop the image to a random display-sized area.
3032
x = randint(0, max(0, image.width - width))
3133
y = randint(0, max(0, image.height - height))
3234
image = image.crop((x, y, x + width, y + height))
3335

3436
# The source artwork is already quantized (no dithering).
35-
image = image.convert('P', dither=None, palette=Image.ADAPTIVE)
36-
37-
return image
37+
return image.convert('P', dither=None, palette=Image.ADAPTIVE)

server/city.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,7 +1020,8 @@ def _draw_layers(self, image, layers, user, width, height):
10201020

10211021
# Draw the layer.
10221022
path = path_join(ASSETS_DIR, layer['file'])
1023-
with Image.open(path).convert('RGBA') as bitmap:
1023+
with Image.open(path) as bitmap:
1024+
bitmap = bitmap.convert('RGBA')
10241025
image.paste(bitmap, (x, y), bitmap)
10251026

10261027
# Remember the drawn file for the else condition.
@@ -1029,13 +1030,11 @@ def _draw_layers(self, image, layers, user, width, height):
10291030
def image(self, user, width, height, variant):
10301031
"""Generates the current city image."""
10311032

1032-
image = Image.new(mode='RGB', size=(width, height))
1033-
try:
1034-
self._draw_layers(image, self._layers(), user, width, height)
1035-
except DataError as e:
1036-
raise ContentError(e)
1037-
1038-
# The city image is already quantized (no dithering).
1039-
image = image.convert('P', dither=None, palette=Image.ADAPTIVE)
1033+
with Image.new(mode='RGB', size=(width, height)) as image:
1034+
try:
1035+
self._draw_layers(image, self._layers(), user, width, height)
1036+
except DataError as e:
1037+
raise ContentError(e)
10401038

1041-
return image
1039+
# The city image is already quantized (no dithering).
1040+
return image.convert('P', dither=None, palette=Image.ADAPTIVE)

server/client_image.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -89,24 +89,26 @@ def main(_):
8989
output.write('#ifndef %s\n' % include_guard)
9090
output.write('#define %s\n\n' % include_guard)
9191

92-
image = Image.open(FLAGS.input).convert('RGB')
93-
assert image.width % 8 == 0, 'Image width must be a multiple of 8'
94-
95-
output.write('// Generated from "%s" using "%s".\n' % (
96-
source_filename, script_filename))
97-
output.write('const uint16_t %s = 0x%02X; // %s\n' % (
98-
background_variable_name, COLOR_LUT[FLAGS.background],
99-
FLAGS.background))
100-
output.write('const uint16_t %s = %d;\n' % (width_variable_name,
101-
image.width))
102-
output.write('const uint16_t %s = %d;\n' % (height_variable_name,
103-
image.height))
104-
105-
black_bytes = encode(image, 'black')
106-
write_bytes(black_image_variable_name, black_bytes, output)
107-
108-
red_bytes = encode(image, 'red')
109-
write_bytes(red_image_variable_name, red_bytes, output)
92+
with Image.open(FLAGS.input) as image:
93+
image = image.convert('RGB')
94+
95+
assert image.width % 8 == 0, 'Image width must be a multiple of 8'
96+
97+
output.write('// Generated from "%s" using "%s".\n' % (
98+
source_filename, script_filename))
99+
output.write('const uint16_t %s = 0x%02X; // %s\n' % (
100+
background_variable_name, COLOR_LUT[FLAGS.background],
101+
FLAGS.background))
102+
output.write('const uint16_t %s = %d;\n' % (width_variable_name,
103+
image.width))
104+
output.write('const uint16_t %s = %d;\n' % (height_variable_name,
105+
image.height))
106+
107+
black_bytes = encode(image, 'black')
108+
write_bytes(black_image_variable_name, black_bytes, output)
109+
110+
red_bytes = encode(image, 'red')
111+
write_bytes(red_image_variable_name, red_bytes, output)
110112

111113
output.write('\n#endif // %s\n' % include_guard)
112114

server/commute.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,4 @@ def image(self, user, width, height, variant):
7777
image=image)
7878

7979
# The map looks better without dithering.
80-
image = image.convert('P', dither=None, palette=Image.ADAPTIVE)
81-
82-
return image
80+
return image.convert('P', dither=None, palette=Image.ADAPTIVE)

server/everyone.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ def image(self, user, width, height, variant):
5656
marker_icon=MARKER_ICON_URL)
5757

5858
# The map looks better without dithering.
59-
image = image.convert('P', dither=None, palette=Image.ADAPTIVE)
60-
61-
return image
59+
return image.convert('P', dither=None, palette=Image.ADAPTIVE)
6260
except DataError as e:
6361
raise ContentError(e)

server/google_calendar.py

Lines changed: 63 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -153,65 +153,66 @@ def image(self, user, width, height, variant):
153153
event_counts = self._event_counts(time, user)
154154

155155
# Create a blank image.
156-
image = Image.new(mode='RGB', size=(width, height),
157-
color=BACKGROUND_COLOR)
158-
draw = Draw(image)
159-
160-
# Get this month's calendar.
161-
calendar = Calendar(firstweekday=SUNDAY)
162-
weeks = calendar.monthdayscalendar(time.year, time.month)
163-
164-
# Determine the spacing of the days in the image.
165-
x_stride = width // (DAYS_IN_WEEK + 1)
166-
y_stride = height // (len(weeks) + 1)
167-
168-
# Draw each week in a row.
169-
for week_index in range(len(weeks)):
170-
week = weeks[week_index]
171-
172-
# Draw each day in a column.
173-
for day_index in range(len(week)):
174-
day = week[day_index]
175-
176-
# Ignore days from other months.
177-
if day == 0:
178-
continue
179-
180-
# Determine the position of this day in the image.
181-
x = (day_index + 1) * x_stride
182-
y = (week_index + 1) * y_stride
183-
184-
# Mark the current day with a squircle.
185-
if day == time.day:
186-
with Image.open(SQUIRCLE_FILE).convert(mode='RGBA') as squircle:
187-
squircle_xy = (x - squircle.width // 2,
188-
y - squircle.height // 2)
189-
draw.bitmap(squircle_xy, squircle, HIGHLIGHT_COLOR)
190-
number_color = TODAY_COLOR
191-
event_color = TODAY_COLOR
192-
else:
193-
number_color = NUMBER_COLOR
194-
event_color = HIGHLIGHT_COLOR
195-
196-
# Draw the day of the month number.
197-
number = str(day)
198-
draw_text(number, SUBVARIO_CONDENSED_MEDIUM, number_color,
199-
xy=(x, y - NUMBER_Y_OFFSET), image=image)
200-
201-
# Draw a dot for each event.
202-
num_events = min(MAX_EVENTS, event_counts[day])
203-
if num_events > 0:
204-
with Image.open(DOT_FILE).convert(mode='RGBA') as dot:
205-
events_width = (num_events * dot.width +
206-
(num_events - 1) * DOT_MARGIN)
207-
for event_index in range(num_events):
208-
event_offset = (event_index * (dot.width +
209-
DOT_MARGIN) - events_width // 2)
210-
dot_xy = [x + event_offset,
211-
y + DOT_OFFSET - dot.width // 2]
212-
draw.bitmap(dot_xy, dot, event_color)
213-
214-
# The calendar image is already quantized (no dithering).
215-
image = image.convert('P', dither=None, palette=Image.ADAPTIVE)
216-
217-
return image
156+
with Image.new(mode='RGB',
157+
size=(width, height),
158+
color=BACKGROUND_COLOR) as image:
159+
draw = Draw(image)
160+
161+
# Get this month's calendar.
162+
calendar = Calendar(firstweekday=SUNDAY)
163+
weeks = calendar.monthdayscalendar(time.year, time.month)
164+
165+
# Determine the spacing of the days in the image.
166+
x_stride = width // (DAYS_IN_WEEK + 1)
167+
y_stride = height // (len(weeks) + 1)
168+
169+
# Draw each week in a row.
170+
for week_index in range(len(weeks)):
171+
week = weeks[week_index]
172+
173+
# Draw each day in a column.
174+
for day_index in range(len(week)):
175+
day = week[day_index]
176+
177+
# Ignore days from other months.
178+
if day == 0:
179+
continue
180+
181+
# Determine the position of this day in the image.
182+
x = (day_index + 1) * x_stride
183+
y = (week_index + 1) * y_stride
184+
185+
# Mark the current day with a squircle.
186+
if day == time.day:
187+
with Image.open(SQUIRCLE_FILE) as squircle:
188+
squircle = squircle.convert(mode='RGBA')
189+
squircle_xy = (x - squircle.width // 2,
190+
y - squircle.height // 2)
191+
draw.bitmap(squircle_xy, squircle, HIGHLIGHT_COLOR)
192+
number_color = TODAY_COLOR
193+
event_color = TODAY_COLOR
194+
else:
195+
number_color = NUMBER_COLOR
196+
event_color = HIGHLIGHT_COLOR
197+
198+
# Draw the day of the month number.
199+
number = str(day)
200+
draw_text(number, SUBVARIO_CONDENSED_MEDIUM, number_color,
201+
xy=(x, y - NUMBER_Y_OFFSET), image=image)
202+
203+
# Draw a dot for each event.
204+
num_events = min(MAX_EVENTS, event_counts[day])
205+
if num_events > 0:
206+
with Image.open(DOT_FILE) as dot:
207+
dot = dot.convert(mode='RGBA')
208+
events_width = (num_events * dot.width +
209+
(num_events - 1) * DOT_MARGIN)
210+
for event_index in range(num_events):
211+
event_offset = (event_index * (dot.width +
212+
DOT_MARGIN) - events_width // 2)
213+
dot_xy = [x + event_offset,
214+
y + DOT_OFFSET - dot.width // 2]
215+
draw.bitmap(dot_xy, dot, event_color)
216+
217+
# The calendar image is already quantized (no dithering).
218+
return image.convert('P', dither=None, palette=Image.ADAPTIVE)

server/google_maps.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,9 @@ def map_image(self, width, height, variant, polyline=None, markers=None,
145145
markers=markers,
146146
marker_icon=marker_icon)
147147
with BytesIO(image_data) as buffer:
148-
with Image.open(buffer).convert('RGB') as image:
148+
with Image.open(buffer) as image:
149+
image = image.convert('RGB')
150+
149151
# Catch map size restrictions.
150152
if image.width != width or image.height != height:
151153
raise DataError(

server/response.py

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -94,45 +94,46 @@ def settings_response(key, image_func, width, height, variant):
9494
"""Creates an image response to start the new user flow."""
9595

9696
# Draw the image with the link text and a computer.
97-
image = Image.new(mode='RGB', size=(width, height), color=BACKGROUND_COLOR)
98-
draw_text(settings_url(key),
99-
font_spec=SUBVARIO_CONDENSED_MEDIUM,
100-
text_color=TEXT_COLOR,
101-
xy=adjust_xy(*LINK_TEXT_XY, width, height),
102-
anchor='center_x',
103-
image=image)
104-
with Image.open(COMPUTER_FILE).convert(mode='RGBA') as computer:
105-
image.paste(computer,
106-
box=adjust_xy(*COMPUTER_XY, width, height),
107-
mask=computer)
108-
109-
return image_func(image, variant)
97+
with Image.new(mode='RGB',
98+
size=(width, height),
99+
color=BACKGROUND_COLOR) as image:
100+
draw_text(settings_url(key),
101+
font_spec=SUBVARIO_CONDENSED_MEDIUM,
102+
text_color=TEXT_COLOR,
103+
xy=adjust_xy(*LINK_TEXT_XY, width, height),
104+
anchor='center_x',
105+
image=image)
106+
with Image.open(COMPUTER_FILE) as computer:
107+
computer = computer.convert(mode='RGBA')
108+
image.paste(computer,
109+
box=adjust_xy(*COMPUTER_XY, width, height),
110+
mask=computer)
111+
112+
return image_func(image, variant)
110113

111114

112115
def content_response(content, image_response, user, width, height, variant):
113116
"""Creates an image response and handles the error case flow."""
114117

115-
try:
116-
# Get the user's rotation setting.
117-
if user:
118-
try:
119-
rotation = user.get('rotation')
120-
except KeyError:
121-
rotation = 0
122-
else:
118+
# Get the user's rotation setting.
119+
if user:
120+
try:
121+
rotation = user.get('rotation')
122+
except KeyError:
123123
rotation = 0
124+
else:
125+
rotation = 0
124126

125-
# Apply the rotation to the dimensions for content generation.
126-
rotated_width, rotated_height = rotate_dimensions(width, height,
127-
rotation)
127+
# Apply the rotation to the dimensions for content generation.
128+
rotated_width, rotated_height = rotate_dimensions(width, height, rotation)
128129

130+
try:
129131
# Generate the image with the rotated dimensions.
130-
image = content.image(user, rotated_width, rotated_height, variant)
131-
132-
# Correct the rotation of the image content itself.
133-
image = rotate_image(image, rotation)
132+
with content.image(user, rotated_width, rotated_height, variant) as image:
133+
# Correct the rotation of the image content itself.
134+
image = rotate_image(image, rotation)
134135

135-
return image_response(image, variant)
136+
return image_response(image, variant)
136137
except ContentError as e:
137138
exception('Failed to create %s content: %s' % (
138139
content.__class__.__name__, e))

0 commit comments

Comments
 (0)