def original(self): """(:class:`Image`) The original image. It could be ``None`` if there are no stored images yet. """ if Session.object_session(self.instance) is None: for image, store in self._stored_images: if image.original: return image state = instance_state(self.instance) try: added = state.committed_state[self.attr.key].added_items except KeyError: pass else: for image in added: if image.original: return image if self.session: for image in self.session.new: if image.original: return image return query = self.filter_by(original=True) try: return query.one() except NoResultFound: pass
def _original_images(self, **kwargs): """A list of the original images. :returns: A list of the original images. :rtype: :class:`collections.Image` """ def test(image): if not image.original: return False for filter, value in kwargs.items(): if getattr(image, filter) != value: return False return True if Session.object_session(self.instance) is None: images = [] for image, store in self._stored_images: if test(image): images.append(image) state = instance_state(self.instance) try: added = state.committed_state[self.attr.key].added_items except KeyError: pass else: for image in added: if test(image): images.append(image) if self.session: for image in self.session.new: if test(image): images.append(image) else: query = self.filter_by(original=True, **kwargs) images = query.all() return images
def _original_images(self, **kwargs): """A list of the original images. :returns: A list of the original images. :rtype: :class:`typing.Sequence`\ [:class:`Image`] """ def test(image): if not image.original: return False for filter, value in kwargs.items(): if getattr(image, filter) != value: return False return True if Session.object_session(self.instance) is None: images = [] for image, store in self._stored_images: if test(image): images.append(image) state = instance_state(self.instance) try: added = state.committed_state[self.attr.key].added_items except KeyError: pass else: for image in added: if test(image): images.append(image) if self.session: for image in self.session.new: if test(image): images.append(image) else: query = self.filter_by(original=True, **kwargs) images = query.all() return images
def generate_thumbnail(self, ratio=None, width=None, height=None, filter='undefined', store=current_store, _preprocess_image=None, _postprocess_image=None): """Resizes the :attr:`original` (scales up or down) and then store the resized thumbnail into the ``store``. :param ratio: resize by its ratio. if it's greater than 1 it scales up, and if it's less than 1 it scales down. exclusive for ``width`` and ``height`` parameters :type ratio: :class:`numbers.Real` :param width: resize by its width. exclusive for ``ratio`` and ``height`` parameters :type width: :class:`numbers.Integral` :param height: resize by its height. exclusive for ``ratio`` and ``width`` parameters :type height: :class:`numbers.Integral` :param filter: a filter type to use for resizing. choose one in :const:`wand.image.FILTER_TYPES`. default is ``'undefined'`` which means ImageMagick will try to guess best one to use :type filter: :class:`basestring`, :class:`numbers.Integral` :param store: the storage to store the resized image file. :data:`~sqlalchemy_imageattach.context.current_store` by default :type store: :class:`~sqlalchemy_imageattach.store.Store` :param _preprocess_image: internal-use only option for preprocessing original image before resizing. it has to be callable which takes a :class:`wand.image.Image` object and returns a new :class:`wand.image.Image` object :type _preprocess_image: :class:`collections.Callable` :param _postprocess_image: internal-use only option for preprocessing original image before resizing. it has to be callable which takes a :class:`wand.image.Image` object and returns a new :class:`wand.image.Image` object :type _postprocess_image: :class:`collections.Callable` :returns: the resized thumbnail image. it might be an already existing image if the same size already exists :rtype: :class:`Image` :raises exceptions.IOError: when there's no :attr:`original` image yet """ params = ratio, width, height param_count = sum(p is not None for p in params) if not param_count: raise TypeError('pass an argument ratio, width, or height') elif param_count > 1: raise TypeError('pass only one argument in ratio, width, or ' 'height; these parameters are exclusive for ' 'each other') query = self.query transient = Session.object_session(query.instance) is None state = instance_state(query.instance) try: added = state.committed_state[query.attr.key].added_items except KeyError: added = [] if width is not None: if not isinstance(width, numbers.Integral): raise TypeError('width must be integer, not ' + repr(width)) elif width < 1: raise ValueError('width must be natural number, not ' + repr(width)) # find the same-but-already-generated thumbnail for image in added: if image.width == width: return image if not transient: q = query.filter_by(width=width) try: return q.one() except NoResultFound: pass def height(sz): return sz[1] * (width / sz[0]) elif height is not None: if not isinstance(height, numbers.Integral): raise TypeError('height must be integer, not ' + repr(height)) elif height < 1: raise ValueError('height must be natural number, not ' + repr(height)) # find the same-but-already-generated thumbnail for image in added: if image.height == height: return image if not transient: q = query.filter_by(height=height) try: return q.one() except NoResultFound: pass def width(sz): return sz[0] * (height / sz[1]) elif ratio is not None: if not isinstance(ratio, numbers.Real): raise TypeError('ratio must be an instance of numbers.Real, ' 'not ' + repr(ratio)) def width(sz): return sz[0] * ratio def height(sz): return sz[1] * ratio data = io.BytesIO() image = self.require_original() with image.open_file(store=store) as f: if _preprocess_image is None: img = WandImage(file=f) else: with WandImage(file=f) as img: img = _preprocess_image(img) with img: if img.mimetype in VECTOR_TYPES: img.format = 'png' original_size = img.size if callable(width): width = width(original_size) if callable(height): height = height(original_size) width = int(width) height = int(height) # find the same-but-already-generated thumbnail for image in added: if image.width == width and image.height == height: return image if not transient: q = query.filter_by(width=width, height=height) try: return q.one() except NoResultFound: pass if len(img.sequence) > 1: img_ctx = img.sequence[0].clone() img_ctx.resize(width, height, filter=filter) else: img_ctx = NoopContext(img) with img_ctx as single_img: single_img.resize(width, height, filter=filter) if _postprocess_image is None: mimetype = img.mimetype single_img.save(file=data) else: with _postprocess_image(img) as img: mimetype = img.mimetype single_img.save(file=data) return self.from_raw_file(data, store, size=(width, height), mimetype=mimetype, original=False)