예제 #1
0
    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
예제 #2
0
    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
예제 #3
0
    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
예제 #4
0
    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
예제 #5
0
    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)
예제 #6
0
    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)