82 if not image_data: |
84 if not image_data: |
83 raise NotImageError("Empty image data.") |
85 raise NotImageError("Empty image data.") |
84 |
86 |
85 self._image_data = image_data |
87 self._image_data = image_data |
86 self._transforms = [] |
88 self._transforms = [] |
87 self._transform_map = {} |
|
88 self._width = None |
89 self._width = None |
89 self._height = None |
90 self._height = None |
90 |
91 |
91 def _check_transform_limits(self, transform): |
92 def _check_transform_limits(self): |
92 """Ensure some simple limits on the number of transforms allowed. |
93 """Ensure some simple limits on the number of transforms allowed. |
93 |
94 |
94 Args: |
95 Raises: |
95 transform: images_service_pb.ImagesServiceTransform, enum of the |
96 BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already been |
96 trasnform called. |
97 requested for this image |
97 |
98 """ |
98 Raises: |
99 if len(self._transforms) >= MAX_TRANSFORMS_PER_REQUEST: |
99 BadRequestError if the transform has already been requested for the image. |
100 raise BadRequestError("%d transforms have already been requested on this " |
100 """ |
101 "image." % MAX_TRANSFORMS_PER_REQUEST) |
101 if not images_service_pb.ImagesServiceTransform.Type_Name(transform): |
|
102 raise BadRequestError("'%s' is not a valid transform." % transform) |
|
103 |
|
104 if transform in self._transform_map: |
|
105 transform_name = images_service_pb.ImagesServiceTransform.Type_Name( |
|
106 transform) |
|
107 raise BadRequestError("A '%s' transform has already been " |
|
108 "requested on this image." % transform_name) |
|
109 self._transform_map[transform] = True |
|
110 |
102 |
111 def _update_dimensions(self): |
103 def _update_dimensions(self): |
112 """Updates the width and height fields of the image. |
104 """Updates the width and height fields of the image. |
113 |
105 |
114 Raises: |
106 Raises: |
170 while offset < size and ord(self._image_data[offset]) == 0xFF: |
162 while offset < size and ord(self._image_data[offset]) == 0xFF: |
171 offset += 1 |
163 offset += 1 |
172 if (offset < size and ord(self._image_data[offset]) & 0xF0 == 0xC0 and |
164 if (offset < size and ord(self._image_data[offset]) & 0xF0 == 0xC0 and |
173 ord(self._image_data[offset]) != 0xC4): |
165 ord(self._image_data[offset]) != 0xC4): |
174 offset += 4 |
166 offset += 4 |
175 if offset + 4 < size: |
167 if offset + 4 <= size: |
176 self._height, self._width = struct.unpack( |
168 self._height, self._width = struct.unpack( |
177 ">HH", |
169 ">HH", |
178 self._image_data[offset:offset + 4]) |
170 self._image_data[offset:offset + 4]) |
179 break |
171 break |
180 else: |
172 else: |
181 raise BadImageError("Corrupt JPEG format") |
173 raise BadImageError("Corrupt JPEG format") |
182 elif offset + 2 <= size: |
174 elif offset + 3 <= size: |
183 offset += 1 |
175 offset += 1 |
184 offset += struct.unpack(">H", self._image_data[offset:offset + 2])[0] |
176 offset += struct.unpack(">H", self._image_data[offset:offset + 2])[0] |
185 else: |
177 else: |
186 raise BadImageError("Corrupt JPEG format") |
178 raise BadImageError("Corrupt JPEG format") |
187 if self._height is None or self._width is None: |
179 if self._height is None or self._width is None: |
197 if self._image_data.startswith("II"): |
189 if self._image_data.startswith("II"): |
198 endianness = "<" |
190 endianness = "<" |
199 else: |
191 else: |
200 endianness = ">" |
192 endianness = ">" |
201 ifd_offset = struct.unpack(endianness + "I", self._image_data[4:8])[0] |
193 ifd_offset = struct.unpack(endianness + "I", self._image_data[4:8])[0] |
202 if ifd_offset < size + 14: |
194 if ifd_offset + 14 <= size: |
203 ifd_size = struct.unpack( |
195 ifd_size = struct.unpack( |
204 endianness + "H", |
196 endianness + "H", |
205 self._image_data[ifd_offset:ifd_offset + 2])[0] |
197 self._image_data[ifd_offset:ifd_offset + 2])[0] |
206 ifd_offset += 2 |
198 ifd_offset += 2 |
207 for unused_i in range(0, ifd_size): |
199 for unused_i in range(0, ifd_size): |
289 height: int, height (in pixels) to change the image height to. |
281 height: int, height (in pixels) to change the image height to. |
290 |
282 |
291 Raises: |
283 Raises: |
292 TypeError when width or height is not either 'int' or 'long' types. |
284 TypeError when width or height is not either 'int' or 'long' types. |
293 BadRequestError when there is something wrong with the given height or |
285 BadRequestError when there is something wrong with the given height or |
294 width or if a Resize has already been requested on this image. |
286 width or if MAX_TRANSFORMS_PER_REQUEST transforms have already been |
|
287 requested on this image. |
295 """ |
288 """ |
296 if (not isinstance(width, (int, long)) or |
289 if (not isinstance(width, (int, long)) or |
297 not isinstance(height, (int, long))): |
290 not isinstance(height, (int, long))): |
298 raise TypeError("Width and height must be integers.") |
291 raise TypeError("Width and height must be integers.") |
299 if width < 0 or height < 0: |
292 if width < 0 or height < 0: |
303 raise BadRequestError("At least one of width or height must be > 0.") |
296 raise BadRequestError("At least one of width or height must be > 0.") |
304 |
297 |
305 if width > 4000 or height > 4000: |
298 if width > 4000 or height > 4000: |
306 raise BadRequestError("Both width and height must be < 4000.") |
299 raise BadRequestError("Both width and height must be < 4000.") |
307 |
300 |
308 self._check_transform_limits( |
301 self._check_transform_limits() |
309 images_service_pb.ImagesServiceTransform.RESIZE) |
|
310 |
302 |
311 transform = images_service_pb.Transform() |
303 transform = images_service_pb.Transform() |
312 transform.set_width(width) |
304 transform.set_width(width) |
313 transform.set_height(height) |
305 transform.set_height(height) |
314 |
306 |
321 degrees: int, must be a multiple of 90. |
313 degrees: int, must be a multiple of 90. |
322 |
314 |
323 Raises: |
315 Raises: |
324 TypeError when degrees is not either 'int' or 'long' types. |
316 TypeError when degrees is not either 'int' or 'long' types. |
325 BadRequestError when there is something wrong with the given degrees or |
317 BadRequestError when there is something wrong with the given degrees or |
326 if a Rotate trasnform has already been requested. |
318 if MAX_TRANSFORMS_PER_REQUEST transforms have already been requested. |
327 """ |
319 """ |
328 if not isinstance(degrees, (int, long)): |
320 if not isinstance(degrees, (int, long)): |
329 raise TypeError("Degrees must be integers.") |
321 raise TypeError("Degrees must be integers.") |
330 |
322 |
331 if degrees % 90 != 0: |
323 if degrees % 90 != 0: |
332 raise BadRequestError("degrees argument must be multiple of 90.") |
324 raise BadRequestError("degrees argument must be multiple of 90.") |
333 |
325 |
334 degrees = degrees % 360 |
326 degrees = degrees % 360 |
335 |
327 |
336 self._check_transform_limits( |
328 self._check_transform_limits() |
337 images_service_pb.ImagesServiceTransform.ROTATE) |
|
338 |
329 |
339 transform = images_service_pb.Transform() |
330 transform = images_service_pb.Transform() |
340 transform.set_rotate(degrees) |
331 transform.set_rotate(degrees) |
341 |
332 |
342 self._transforms.append(transform) |
333 self._transforms.append(transform) |
343 |
334 |
344 def horizontal_flip(self): |
335 def horizontal_flip(self): |
345 """Flip the image horizontally. |
336 """Flip the image horizontally. |
346 |
337 |
347 Raises: |
338 Raises: |
348 BadRequestError if a HorizontalFlip has already been requested on the |
339 BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already been |
349 image. |
340 requested on the image. |
350 """ |
341 """ |
351 self._check_transform_limits( |
342 self._check_transform_limits() |
352 images_service_pb.ImagesServiceTransform.HORIZONTAL_FLIP) |
|
353 |
343 |
354 transform = images_service_pb.Transform() |
344 transform = images_service_pb.Transform() |
355 transform.set_horizontal_flip(True) |
345 transform.set_horizontal_flip(True) |
356 |
346 |
357 self._transforms.append(transform) |
347 self._transforms.append(transform) |
358 |
348 |
359 def vertical_flip(self): |
349 def vertical_flip(self): |
360 """Flip the image vertically. |
350 """Flip the image vertically. |
361 |
351 |
362 Raises: |
352 Raises: |
363 BadRequestError if a HorizontalFlip has already been requested on the |
353 BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already been |
364 image. |
354 requested on the image. |
365 """ |
355 """ |
366 self._check_transform_limits( |
356 self._check_transform_limits() |
367 images_service_pb.ImagesServiceTransform.VERTICAL_FLIP) |
|
368 transform = images_service_pb.Transform() |
357 transform = images_service_pb.Transform() |
369 transform.set_vertical_flip(True) |
358 transform.set_vertical_flip(True) |
370 |
359 |
371 self._transforms.append(transform) |
360 self._transforms.append(transform) |
372 |
361 |
403 bottom_y: float value between 0.0 and 1.0 (inclusive). |
392 bottom_y: float value between 0.0 and 1.0 (inclusive). |
404 |
393 |
405 Raises: |
394 Raises: |
406 TypeError if the args are not of type 'float'. |
395 TypeError if the args are not of type 'float'. |
407 BadRequestError when there is something wrong with the given bounding box |
396 BadRequestError when there is something wrong with the given bounding box |
408 or if there has already been a crop transform requested for this image. |
397 or if MAX_TRANSFORMS_PER_REQUEST transforms have already been requested |
|
398 for this image. |
409 """ |
399 """ |
410 self._validate_crop_arg(left_x, "left_x") |
400 self._validate_crop_arg(left_x, "left_x") |
411 self._validate_crop_arg(top_y, "top_y") |
401 self._validate_crop_arg(top_y, "top_y") |
412 self._validate_crop_arg(right_x, "right_x") |
402 self._validate_crop_arg(right_x, "right_x") |
413 self._validate_crop_arg(bottom_y, "bottom_y") |
403 self._validate_crop_arg(bottom_y, "bottom_y") |
415 if left_x >= right_x: |
405 if left_x >= right_x: |
416 raise BadRequestError("left_x must be less than right_x") |
406 raise BadRequestError("left_x must be less than right_x") |
417 if top_y >= bottom_y: |
407 if top_y >= bottom_y: |
418 raise BadRequestError("top_y must be less than bottom_y") |
408 raise BadRequestError("top_y must be less than bottom_y") |
419 |
409 |
420 self._check_transform_limits(images_service_pb.ImagesServiceTransform.CROP) |
410 self._check_transform_limits() |
421 |
411 |
422 transform = images_service_pb.Transform() |
412 transform = images_service_pb.Transform() |
423 transform.set_crop_left_x(left_x) |
413 transform.set_crop_left_x(left_x) |
424 transform.set_crop_top_y(top_y) |
414 transform.set_crop_top_y(top_y) |
425 transform.set_crop_right_x(right_x) |
415 transform.set_crop_right_x(right_x) |
431 """Automatically adjust image contrast and color levels. |
421 """Automatically adjust image contrast and color levels. |
432 |
422 |
433 This is similar to the "I'm Feeling Lucky" button in Picasa. |
423 This is similar to the "I'm Feeling Lucky" button in Picasa. |
434 |
424 |
435 Raises: |
425 Raises: |
436 BadRequestError if this transform has already been requested for this |
426 BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already |
437 image. |
427 been requested for this image. |
438 """ |
428 """ |
439 self._check_transform_limits( |
429 self._check_transform_limits() |
440 images_service_pb.ImagesServiceTransform.IM_FEELING_LUCKY) |
|
441 transform = images_service_pb.Transform() |
430 transform = images_service_pb.Transform() |
442 transform.set_autolevels(True) |
431 transform.set_autolevels(True) |
443 |
432 |
444 self._transforms.append(transform) |
433 self._transforms.append(transform) |
445 |
434 |
538 output_encoding: a value from OUTPUT_ENCODING_TYPES. |
526 output_encoding: a value from OUTPUT_ENCODING_TYPES. |
539 |
527 |
540 Raises: |
528 Raises: |
541 TypeError when width or height not either 'int' or 'long' types. |
529 TypeError when width or height not either 'int' or 'long' types. |
542 BadRequestError when there is something wrong with the given height or |
530 BadRequestError when there is something wrong with the given height or |
543 width or if a Resize has already been requested on this image. |
531 width. |
544 Error when something went wrong with the call. See Image.ExecuteTransforms |
532 Error when something went wrong with the call. See Image.ExecuteTransforms |
545 for more details. |
533 for more details. |
546 """ |
534 """ |
547 image = Image(image_data) |
535 image = Image(image_data) |
548 image.resize(width, height) |
536 image.resize(width, height) |
557 degrees: value from ROTATE_DEGREE_VALUES. |
545 degrees: value from ROTATE_DEGREE_VALUES. |
558 output_encoding: a value from OUTPUT_ENCODING_TYPES. |
546 output_encoding: a value from OUTPUT_ENCODING_TYPES. |
559 |
547 |
560 Raises: |
548 Raises: |
561 TypeError when degrees is not either 'int' or 'long' types. |
549 TypeError when degrees is not either 'int' or 'long' types. |
562 BadRequestError when there is something wrong with the given degrees or |
550 BadRequestError when there is something wrong with the given degrees. |
563 if a Rotate trasnform has already been requested. |
|
564 Error when something went wrong with the call. See Image.ExecuteTransforms |
551 Error when something went wrong with the call. See Image.ExecuteTransforms |
565 for more details. |
552 for more details. |
566 """ |
553 """ |
567 image = Image(image_data) |
554 image = Image(image_data) |
568 image.rotate(degrees) |
555 image.rotate(degrees) |
617 bottom_y: float value between 0.0 and 1.0 (inclusive). |
604 bottom_y: float value between 0.0 and 1.0 (inclusive). |
618 output_encoding: a value from OUTPUT_ENCODING_TYPES. |
605 output_encoding: a value from OUTPUT_ENCODING_TYPES. |
619 |
606 |
620 Raises: |
607 Raises: |
621 TypeError if the args are not of type 'float'. |
608 TypeError if the args are not of type 'float'. |
622 BadRequestError when there is something wrong with the given bounding box |
609 BadRequestError when there is something wrong with the given bounding box. |
623 or if there has already been a crop transform requested for this image. |
|
624 Error when something went wrong with the call. See Image.ExecuteTransforms |
610 Error when something went wrong with the call. See Image.ExecuteTransforms |
625 for more details. |
611 for more details. |
626 """ |
612 """ |
627 image = Image(image_data) |
613 image = Image(image_data) |
628 image.crop(left_x, top_y, right_x, bottom_y) |
614 image.crop(left_x, top_y, right_x, bottom_y) |