def save(self, **kwargs): """Returns a buffer to the image for saving, supports the following optional keyword arguments: format - The format to save as: see Image.FORMATS optimize - The image file size should be optimized preserve_exif - Preserve the Exif information in JPEGs progressive - The output should be progressive JPEG quality - The quality used to save JPEGs: integer from 1 - 100 """ opts = Image._normalize_options(kwargs) outfile = BytesIO() if opts["pil"]["format"]: fmt = opts["pil"]["format"] else: fmt = self._orig_format save_kwargs = dict() if Image._isint(opts["quality"]): save_kwargs["quality"] = int(opts["quality"]) if int(opts["optimize"]): save_kwargs["optimize"] = True if int(opts["progressive"]): save_kwargs["progressive"] = True if int(opts["preserve_exif"]): save_kwargs["exif"] = self._exif color = color_hex_to_dec_tuple(opts["background"]) if self.img.mode == "RGBA": self._background(fmt, color) if fmt == "JPEG": if self.img.mode == "P": # Converting old GIF and PNG files to JPEG can raise # IOError: cannot write mode P as JPEG # https://mail.python.org/pipermail/python-list/2000-May/036017.html self.img = self.img.convert("RGB") elif self.img.mode == "RGBA": # JPEG does not have an alpha channel so cannot be # saved as RGBA. It must be converted to RGB. self.img = self.img.convert("RGB") if self._orig_format == "JPEG": self.img.format = self._orig_format save_kwargs["subsampling"] = "keep" if opts["quality"] == "keep": save_kwargs["quality"] = "keep" try: self.img.save(outfile, fmt, **save_kwargs) except IOError as e: raise errors.ImageSaveError(str(e)) self.img.format = fmt outfile.seek(0) return outfile
def resize(self, width, height, **kwargs): """Resizes the image to the supplied width/height. Returns the instance. Supports the following optional keyword arguments: mode - The resizing mode to use, see Image.MODES filter - The filter to use: see Image.FILTERS background - The hexadecimal background fill color, RGB or ARGB position - The position used to crop: see Image.POSITIONS for pre-defined positions or a custom position ratio retain - The minimum percentage of the original image to retain when cropping """ opts = Image._normalize_options(kwargs) size = self._get_size(width, height) if opts["mode"] == "adapt": self._adapt(size, opts) elif opts["mode"] == "clip": self._clip(size, opts) elif opts["mode"] == "fill": self._fill(size, opts) elif opts["mode"] == "scale": self._scale(size, opts) else: self._crop(size, opts) return self
def _remove_alpha_channel(self, **kwargs): opts = Image._normalize_options(kwargs) if self.img.mode != "RGB" and len(self.img.split()) > 3: # logger.debug("removing alpha channel") background = PIL.Image.new("RGB", self.img.size, color_hex_to_dec_tuple(opts["background"])) background.paste(self.img, mask=self.img.split()[3]) # 3 is the alpha channel self.img = background
def save(self, **kwargs): """Returns a buffer to the image for saving, supports the following optional keyword arguments: format - The format to save as: see Image.FORMATS optimize - The image file size should be optimized quality - The quality used to save JPEGs: integer from 1 - 100 """ opts = Image._normalize_options(kwargs) outfile = BytesIO() if opts["pil"]["format"]: fmt = opts["pil"]["format"] else: fmt = self._orig_format save_kwargs = dict(quality=int(opts["quality"])) if int(opts["optimize"]): save_kwargs["optimize"] = True try: self.img.save(outfile, fmt, **save_kwargs) except IOError as e: raise errors.ImageSaveError(str(e)) self.img.format = fmt outfile.seek(0) return outfile
def validate_options(opts): opts = Image._normalize_options(opts) if opts["mode"] not in Image.MODES: raise errors.ModeError("Invalid mode: %s" % opts["mode"]) elif opts["filter"] not in Image.FILTERS: raise errors.FilterError("Invalid filter: %s" % opts["filter"]) elif opts["format"] and opts["format"] not in Image.FORMATS: raise errors.FormatError("Invalid format: %s" % opts["format"]) elif opts["position"] not in Image.POSITIONS \ and not opts["pil"]["position"]: raise errors.PositionError( "Invalid position: %s" % opts["position"]) elif not Image._isint(opts["background"], 16) \ or len(opts["background"]) not in [3, 4, 6, 8]: raise errors.BackgroundError( "Invalid background: %s" % opts["background"]) elif opts["optimize"] and not Image._isint(opts["optimize"]): raise errors.OptimizeError( "Invalid optimize: %s", str(opts["optimize"])) elif opts["quality"] != "keep" \ and (not Image._isint(opts["quality"]) or int(opts["quality"]) > 100 or int(opts["quality"]) < 0): raise errors.QualityError( "Invalid quality: %s", str(opts["quality"])) elif opts["progressive"] and not Image._isint(opts["progressive"]): raise errors.ProgressiveError( "Invalid progressive: %s", str(opts["progressive"]))
def _adpat_with_padding(self,w,h,padding,**kwargs): opts = Image._normalize_options(kwargs) pad=0 width=int(w); height=int(h); if padding: pad=float(padding) paddingH=int(width*pad) paddingV=int(height*pad) width=int(width-paddingH) height=int(height-paddingV) logger.debug("New Size : "+str([width,height])) self._clip((width,height), opts) if self.img.size == (width,height) and not padding: return # No need to fill x= ((width+paddingH)-self.img.size[0])/2 y= ((height+paddingV)-self.img.size[1])/2 color = color_hex_to_dec_tuple(opts["background"]) mode = "RGBA" if len(color) == 4 else "RGB" logger.debug("New Size with Padding: "+str((width+paddingH, height+paddingV))) img = PIL.Image.new(mode=mode, size=(width+paddingH, height+paddingV), color=(255, 255, 255)) # If the image has an alpha channel, use it as a mask when # pasting onto the background. channels = self.img.split() mask = channels[3] if len(channels) == 4 else None img.paste(self.img, (int(x),int(y)), mask=mask) self._skip_background = True self.img = img
def rotate(self, deg, **kwargs): """ Rotates the image clockwise around its center. Returns the instance. Supports the following optional keyword arguments: expand - Expand the output image to fit rotation """ opts = Image._normalize_options(kwargs) if deg == "auto": if self._orig_format == "JPEG": try: exif = self.img._getexif() or dict() deg = _orientation_to_rotation.get(exif.get(274, 0), 0) except Exception: logger.warn('unable to parse exif') deg = 0 else: deg = 0 deg = 360 - (int(deg) % 360) if deg % 90 == 0: if deg == 90: self.img = self.img.transpose(PIL.Image.ROTATE_90) elif deg == 180: self.img = self.img.transpose(PIL.Image.ROTATE_180) elif deg == 270: self.img = self.img.transpose(PIL.Image.ROTATE_270) else: self.img = self.img.rotate(deg, expand=bool(int(opts["expand"]))) return self
def validate_options(opts): opts = Image._normalize_options(opts) if opts["mode"] not in Image.MODES: raise errors.ModeError("Invalid mode: %s" % opts["mode"]) elif opts["filter"] not in Image.FILTERS: raise errors.FilterError("Invalid filter: %s" % opts["filter"]) elif opts["format"] and opts["format"] not in Image.FORMATS: raise errors.FormatError("Invalid format: %s" % opts["format"]) elif opts["position"] not in Image.POSITIONS \ and not opts["pil"]["position"]: raise errors.PositionError("Invalid position: %s" % opts["position"]) elif not Image._isint(opts["background"], 16) \ or len(opts["background"]) not in [3, 4, 6, 8]: raise errors.BackgroundError("Invalid background: %s" % opts["background"]) elif opts["optimize"] and not Image._isint(opts["optimize"]): raise errors.OptimizeError("Invalid optimize: %s", str(opts["optimize"])) elif opts["quality"] != "keep" and \ (not Image._isint(opts["quality"]) or int(opts["quality"]) > 100 or int(opts["quality"]) < 0): raise errors.QualityError("Invalid quality: %s", str(opts["quality"])) elif opts["preserve_exif"] and not Image._isint(opts["preserve_exif"]): raise errors.PreserveExifError("Invalid preserve_exif: %s" % str(opts["preserve_exif"])) elif opts["progressive"] and not Image._isint(opts["progressive"]): raise errors.ProgressiveError("Invalid progressive: %s", str(opts["progressive"])) elif (not Image._isint(opts["retain"]) or int(opts["retain"]) > 100 or int(opts["retain"]) < 0): raise errors.RetainError("Invalid retain: %s" % str(opts["retain"]))
def save(self, **kwargs): opts = Image._normalize_options(kwargs) outfile = BytesIO() if opts["pil"]["format"]: fmt = opts["pil"]["format"] else: fmt = self._orig_format save_kwargs = dict() if Image._isint(opts["quality"]): save_kwargs["quality"] = int(opts["quality"]) if int(opts["optimize"]): save_kwargs["optimize"] = True if int(opts["progressive"]): save_kwargs["progressive"] = True if int(opts["preserve_exif"]): save_kwargs["exif"] = self._exif if self._orig_format == "JPEG": self.img.format = self._orig_format save_kwargs["subsampling"] = "keep" if opts["quality"] == "keep": save_kwargs["quality"] = "keep" try: self.img.save(outfile, fmt, **save_kwargs) except IOError as e: raise errors.ImageSaveError(str(e)) self.img.format = fmt outfile.seek(0) return outfile
def rotate(self, deg, **kwargs): """ Rotates the image clockwise around its center. Returns the instance. Supports the following optional keyword arguments: expand - Expand the output image to fit rotation """ opts = Image._normalize_options(kwargs) expand = False if int(deg) % 90 == 0 else bool(int(opts["expand"])) self.img = self.img.rotate(360 - int(deg), expand=expand) return self
def watermark(self, position, **kwargs): pos = make_tuple(position) opts = Image._normalize_options(kwargs) watermark_txt = opts[ "watermark_txt"] if "watermark_txt" in opts else None watermark_img = opts[ "watermark_img"] if "watermark_img" in opts else None if watermark_txt: watermark_txt_size = int(opts["watermark_txt_size"] if "watermark_txt_size" in opts else 30) watermark_txt_color = opts[ "watermark_txt_color"] if "watermark_txt_color" in opts else "black" # make the image editable drawing = ImageDraw.Draw(self.img) if watermark_txt_color == "white": color = (255, 255, 255) else: color = (3, 8, 12) #font = ImageFont.truetype("pilbox/arial.ttf", watermark_txt_size) font = ImageFont.truetype( "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", watermark_txt_size) drawing.text(pos, watermark_txt, fill=color, font=font) elif watermark_img: # Get watermark image ratio watermark_img_ratio = int( opts["watermark_img_ratio"] if "watermark_img_ratio" in opts else 5) / 100 # Get watermark image watermark = PIL.Image.open( BytesIO(Image.get_watermark_img(watermark_img))) # Get size of target image width, height = self.img.size # Resize watermark to a percentage of the target image's height targetHeight = int(height * watermark_img_ratio) hpercent = (watermark.size[0] / float(watermark.size[1])) wsize = int(targetHeight * float(hpercent)) watermark = watermark.resize((wsize, targetHeight)) # Create new watermarked images transparent = PIL.Image.new('RGBA', (width, height), (0, 0, 0, 0)) transparent.paste(self.img, (0, 0)) transparent.paste(watermark, box=pos, mask=watermark) self.img = transparent
def validate_options(opts): opts = Image._normalize_options(opts) if opts["mode"] not in Image.MODES: raise ModeError("Invalid mode: %s" % opts["mode"]) elif opts["filter"] not in Image.FILTERS: raise FilterError("Invalid filter: %s" % opts["filter"]) elif opts["position"] not in Image.POSITIONS: raise PositionError("Invalid position: %s" % opts["position"]) elif not Image._isint(opts["background"], 16) \ or len(opts["background"]) not in [3, 4, 6, 8]: raise BackgroundError("Invalid background: %s" % opts["background"]) elif not Image._isint(opts["quality"]) \ or int(opts["quality"]) > 100 or int(opts["quality"]) < 0: raise QualityError("Invalid quality: %s", str(opts["quality"]))
def region(self, rect, width=False, height=False, **kwargs): """ Selects a sub-region of the image using the supplied rectangle, x, y, width, height. """ box = (int(rect[0]), int(rect[1]), int(rect[0]) + int(rect[2]), int(rect[1]) + int(rect[3])) if box[2] > self.img.size[0] or box[3] > self.img.size[1]: raise errors.RectangleError("Region out-of-bounds") self.img = self.img.crop(box) if width or height: opts = Image._normalize_options(kwargs) size = self._get_size(width, height) self._scale(size, opts) return self
def save(self, **kwargs): """Returns a buffer to the image for saving, supports the following optional keyword arguments: format - The format to save as: see Image.FORMATS optimize - The image file size should be optimized preserve_exif - Preserve the Exif information in JPEGs progressive - The output should be progressive JPEG quality - The quality used to save JPEGs: integer from 1 - 100 """ opts = Image._normalize_options(kwargs) outfile = BytesIO() if opts["pil"]["format"]: fmt = opts["pil"]["format"] else: fmt = self._orig_format save_kwargs = dict() if Image._isint(opts["quality"]): save_kwargs["quality"] = int(opts["quality"]) if int(opts["optimize"]): save_kwargs["optimize"] = True if int(opts["progressive"]): save_kwargs["progressive"] = True if int(opts["preserve_exif"]): save_kwargs["exif"] = self._exif color = color_hex_to_dec_tuple(opts["background"]) if self.img.mode == "RGBA": self._background(fmt, color) if self._orig_format == "JPEG": self.img.format = self._orig_format save_kwargs["subsampling"] = "keep" if opts["quality"] == "keep": save_kwargs["quality"] = "keep" try: self.img.save(outfile, fmt, **save_kwargs) except IOError as e: raise errors.ImageSaveError(str(e)) self.img.format = fmt outfile.seek(0) return outfile
def save(self, **kwargs): """Returns a buffer to the image for saving, supports the following optional keyword arguments: format - The format to save as: see Image.FORMATS quality - The quality used to save JPEGs: integer from 1 - 100 """ opts = Image._normalize_options(kwargs) outfile = BytesIO() if opts["pil"]["format"]: fmt = opts["pil"]["format"] else: fmt = self._orig_format self.img.save(outfile, fmt, quality=int(opts["quality"])) outfile.seek(0) return outfile
def rotate(self, deg, **kwargs): """ Rotates the image clockwise around its center. Returns the instance. Supports the following optional keyword arguments: expand - Expand the output image to fit rotation """ opts = Image._normalize_options(kwargs) if deg == "auto": if self._orig_format == "JPEG": exif = self.img._getexif() or dict() deg = _orientation_to_rotation.get(exif.get(274, 0), 0) else: deg = 0 expand = False if int(deg) % 90 == 0 else bool(int(opts["expand"])) self.img = self.img.rotate(360 - int(deg), expand=expand) return self
def resize(self, width, height, **kwargs): """Returns a buffer to the resized image for saving, supports the following optional keyword arguments: mode - The resizing mode to use, see Image.MODES filter - The filter to use: see Image.FILTERS background - The hexadecimal background fill color, RGB or ARGB position - The position used to crop: see Image.POSITIONS quality - The quality used to save JPEGs: integer from 1 - 100 """ img = PIL.Image.open(self.stream) if img.format.lower() not in self.FORMATS: raise FormatError("Unknown format: %s" % img.format) opts = Image._normalize_options(kwargs, self.defaults) resized = self._resize(img, self._get_size(img, width, height), opts) outfile = BytesIO() resized.save(outfile, img.format, quality=int(opts["quality"])) outfile.seek(0) return outfile
def validate_options(opts): opts = Image._normalize_options(opts) if opts["mode"] not in Image.MODES: raise errors.ModeError("Invalid mode: %s" % opts["mode"]) elif opts["filter"] not in Image.FILTERS: raise errors.FilterError("Invalid filter: %s" % opts["filter"]) elif opts["format"] and opts["format"] not in Image.FORMATS: raise errors.FormatError("Invalid format: %s" % opts["format"]) elif opts["position"] not in Image.POSITIONS \ and not opts["pil"]["position"]: raise errors.PositionError("Invalid position: %s" % opts["position"]) elif not Image._isint(opts["background"], 16) \ or len(opts["background"]) not in [3, 4, 6, 8]: raise errors.BackgroundError("Invalid background: %s" % opts["background"]) elif not Image._isint(opts["quality"]) \ or int(opts["quality"]) > 100 or int(opts["quality"]) < 0: raise errors.QualityError("Invalid quality: %s", str(opts["quality"]))
def rotate(self, deg, **kwargs): """ Rotates the image clockwise around its center. Returns the instance. Supports the following optional keyword arguments: expand - Expand the output image to fit rotation """ opts = Image._normalize_options(kwargs) if deg == "auto": if self._orig_format == "JPEG": try: exif = self.img._getexif() or dict() deg = _orientation_to_rotation.get(exif.get(274, 0), 0) except: logger.warn('unable to parse exif') deg = 0 else: deg = 0 expand = False if int(deg) % 90 == 0 else bool(int(opts["expand"])) self.img = self.img.rotate(360 - int(deg), expand=expand) return self
def validate_options(opts): opts = Image._normalize_options(opts) if opts["mode"] not in Image.MODES: raise errors.ModeError("Invalid mode: {}".format(opts["mode"])) elif opts["filter"] not in Image.FILTERS: raise errors.FilterError("Invalid filter: {}".format(opts["filter"])) elif opts["format"] and opts["format"] not in Image.FORMATS: raise errors.FormatError("Invalid format: {}".format(opts["format"])) elif opts["position"] not in Image.POSITIONS and not opts["pil"]["position"]: raise errors.PositionError("Invalid position: {}".format(opts["position"])) elif not Image._isint(opts["background"], 16) or len(opts["background"]) not in [3, 4, 6, 8]: raise errors.BackgroundError("Invalid background: {}".format(opts["background"])) elif opts["optimize"] and not Image._isint(opts["optimize"]): raise errors.OptimizeError("Invalid optimize: {}".format(str(opts["optimize"]))) elif opts["quality"] != "keep" and (not Image._isint(opts["quality"]) or int(opts["quality"]) > 100 or int(opts["quality"]) < 0): raise errors.QualityError("Invalid quality: {}".format(str(opts["quality"]))) elif opts["preserve_exif"] and not Image._isint(opts["preserve_exif"]): raise errors.PreserveExifError("Invalid preserve_exif: {}".format(str(opts["preserve_exif"]))) elif opts["progressive"] and not Image._isint(opts["progressive"]): raise errors.ProgressiveError("Invalid progressive: {}".format(str(opts["progressive"]))) elif (not Image._isint(opts["retain"]) or int(opts["retain"]) > 100 or int(opts["retain"]) < 0): raise errors.RetainError("Invalid retain: {}".format(str(opts["retain"])))
def scaleToAspectRatio(self,ar,padding,**kwargs): opts = Image._normalize_options(kwargs) tokens=ar.split(":") ratio=float(tokens[0])/float(tokens[1]) width=self.img.size[0] height=self.img.size[1] pad=0.05 if padding: pad=float(padding) if width==height: width=width*ratio elif height>width: width=height*ratio else: height=height/ratio paddingH=int(width*pad) paddingV=int(height*pad) width=int(width) height=int(height) logger.debug("New Size : "+str([width,height])) self._clip((width,height), opts) if self.img.size == (width,height) and not padding: return # No need to fill x= ((width+paddingH)-self.img.size[0])/2 y= ((height+paddingV)-self.img.size[1])/2 color = color_hex_to_dec_tuple(opts["background"]) mode = "RGBA" if len(color) == 4 else "RGB" logger.debug("New Size with Padding: "+str((width+paddingH, height+paddingV))) img = PIL.Image.new(mode=mode, size=(width+paddingH, height+paddingV), color=(255, 255, 255)) # If the image has an alpha channel, use it as a mask when # pasting onto the background. channels = self.img.split() mask = channels[3] if len(channels) == 4 else None img.paste(self.img, (int(x),int(y)), mask=mask) self._skip_background = True self.img = img
def __init__(self, stream, defaults=dict()): self.stream = stream self.defaults = Image._normalize_options(defaults)