Beispiel #1
0
class ThresholdProcessor(Processor):
    """ Abstract base class for a processor using a uint8_t threshold matrix """
    # This is used in instakit.processors.halftone
    __slots__ = tuplize('threshold_matrix')

    LO_TUP = tuplize(0)
    HI_TUP = tuplize(255)

    def __init__(self, threshold=128.0):
        """ Initialize with a threshold value between 0 and 255 """
        self.threshold_matrix = int(threshold)  * self.LO_TUP + \
                           (256-int(threshold)) * self.HI_TUP
Beispiel #2
0
class Posterize(Processor):
    """ Reduce the number of bits (1 to 8) per channel """
    __slots__ = tuplize('bits')

    def __init__(self, bits=4):
        self.bits = min(max(1, bits), 8)

    def process(self, image):
        return ImageOps.posterize(image, bits=self.bits)
Beispiel #3
0
class EnhanceNop(ABC, metaclass=Slotted):

    __slots__ = tuplize('image')

    def __init__(self, image=None):
        self.image = image

    def adjust(self, *args, **kwargs):
        return self.image
Beispiel #4
0
class Solarize(Processor):
    """ Invert all pixel values above an 8-bit threshold """
    __slots__ = tuplize('threshold')

    def __init__(self, threshold=128):
        self.threshold = min(max(1, threshold), 255)

    def process(self, image):
        return ImageOps.solarize(image, threshold=self.threshold)
Beispiel #5
0
class Equalize(Processor):
    """ Apply a non-linear mapping to the image, via histogram """
    __slots__ = tuplize('mask')

    def __init__(self, mask=None):
        self.mask = hasattr(mask, 'copy') and mask.copy() or mask

    def process(self, image):
        return ImageOps.equalize(image, mask=self.mask)
Beispiel #6
0
class AutoContrast(Processor):
    """ Normalize contrast throughout the image, via histogram """
    __slots__ = tuplize('cutoff', 'ignore')

    def __init__(self, cutoff=0, ignore=None):
        self.cutoff, self.ignore = cutoff, ignore

    def process(self, image):
        return ImageOps.autocontrast(image,
                                     cutoff=self.cutoff,
                                     ignore=self.ignore)
Beispiel #7
0
class Pipe(Sequence):
    """ A static linear pipeline of processors to be applied en masse.
        Derived from a `pilkit` class:
            `pilkit.processors.base.ProcessorPipeline`
    """
    __slots__ = tuplize('tuple')

    @classmethod
    def base_type(cls):
        return tuple

    @wraps(tuple.__init__)
    def __init__(self, *args):
        self.tuple = tuplize(*args)

    def iterate(self):
        yield from self.tuple

    @wraps(tuple.__len__)
    def __len__(self):
        return len(self.tuple)

    @wraps(tuple.__contains__)
    def __contains__(self, value):
        return value in self.tuple

    @wraps(tuple.__getitem__)
    def __getitem__(self, idx):
        return self.tuple[idx]

    @wraps(tuple.index)
    def index(self, value):
        return self.tuple.index(value)

    def last(self):
        if not bool(self):
            raise IndexError("pipe is empty")
        return self.tuple[-1]

    def process(self, image):
        for processor in self.iterate():
            image = processor.process(image)
        return image

    def __eq__(self, other):
        if not isinstance(other, (type(self), type(self).base_type())):
            return NotImplemented
        return super(Pipe, self).__eq__(other)
Beispiel #8
0
class Adjustment(Processor):
    """ Base type for image adjustment processors """
    __slots__ = tuplize('value')

    def __init__(self, value=1.0):
        """ Initialize the adjustment with a float value """
        self.value = value

    @abstract
    def adjust(self, image):
        """ Adjust the image, using the float value with which
            the adjustment was first initialized
        """
        ...

    def process(self, image):
        return (self.value == 1.0) and image or self.adjust(image)
Beispiel #9
0
class Pipeline(MutableSequence):
    """ A mutable linear pipeline of processors to be applied en masse.
        Derived from a `pilkit` class:
            `pilkit.processors.base.ProcessorPipeline`
    """
    __slots__ = tuplize('list')

    @classmethod
    def base_type(cls):
        return list

    @wraps(list.__init__)
    def __init__(self, *args):
        base_type = type(self).base_type()
        if len(args) == 0:
            self.list = base_type()
        if len(args) == 1:
            target = args[0]
            if type(target) is type(self):
                self.list = copy(target.list)
            elif type(target) is base_type:
                self.list = copy(target)
            elif type(target) in (tuple, set, frozenset):
                self.list = base_type([*target])
            elif type(target) in (dict, defaultdict, OrderedDict):
                self.list = base_type([*sorted(target).values()])
            elif hasattr(target, 'iterate'):
                self.list = base_type([*target.iterate()])
            elif hasattr(target, '__iter__'):
                self.list = base_type([*target])
            else:
                self.list = base_type([target])
        else:
            self.list = base_type([*args])

    def iterate(self):
        yield from self.list

    @wraps(list.__len__)
    def __len__(self):
        return len(self.list)

    @wraps(list.__contains__)
    def __contains__(self, value):
        return value in self.list

    @wraps(list.__getitem__)
    def __getitem__(self, idx):
        return self.list[idx]

    @wraps(list.__setitem__)
    def __setitem__(self, idx, value):
        if value in (None, NOOp):
            value = NOOp()
        self.list[idx] = value

    @wraps(list.__delitem__)
    def __delitem__(self, idx):
        del self.list[idx]

    @wraps(list.index)
    def index(self, value):
        return self.list.index(value)

    @wraps(list.append)
    def append(self, value):
        self.list.append(value)

    @wraps(list.extend)
    def extend(self, iterable):
        self.list.extend(iterable)

    def pop(self, idx=-1):
        """ Remove and return item at `idx` (default last).
            Raises IndexError if list is empty or `idx` is out of range.
            See list.pop(…) for details.
        """
        self.list.pop(idx)

    def last(self):
        if not bool(self):
            raise IndexError("pipe is empty")
        return self.list[-1]

    def process(self, image):
        for processor in self.iterate():
            image = processor.process(image)
        return image

    def __eq__(self, other):
        if not isinstance(other, (type(self), type(self).base_type())):
            return NotImplemented
        return super(Pipe, self).__eq__(other)
Beispiel #10
0
 def __init__(self, *args):
     self.tuple = tuplize(*args)
Beispiel #11
0
class BandFork(Fork):
    """ BandFork is a processor container -- a processor that applies other
        processors. BandFork acts selectively on the individual bands of
        input image data, either:
        - applying a band-specific processor instance, or
        - applying a default processor factory successively across all bands.
        
        BandFork’s interface is closely aligned with Python’s mutable-mapping
        API‡ -- with which most programmers are no doubt quite familiar:
        
        • Ex. 1: apply Atkinson dithering to each of an RGB images’ bands:
        >>> from instakit.utils.pipeline import BandFork
        >>> from instakit.processors.halftone import Atkinson
        >>> BandFork(Atkinson).process(my_image)
        
        • Ex. 2: apply Atkinson dithering to only the green band:
        >>> from instakit.utils.pipeline import BandFork
        >>> from instakit.processors.halftone import Atkinson
        >>> bfork = BandFork(None)
        >>> bfork['G'] = Atkinson()
        >>> bfork.process(my_image)
        
        BandFork inherits from `instakit.abc.Fork`, which itself is not just
        an Instakit Processor. The Fork ABC implements the required methods
        of an Instakit Processor Container†, through which it furnishes an
        interface to individual bands -- also generally known as channels,
        per the language of the relevant Photoshop UI elements -- of image
        data. 
        
        † q.v. the `instakit.abc` module source code supra.
        ‡ q.v. the `collections.abc` module, and the `MutableMapping`
                    abstract base class within, supra.
    """
    __slots__ = tuplize('mode_t')

    def __init__(self, processor_factory, *args, **kwargs):
        """ Initialize a BandFork instance, using the given callable value
            for `processor_factory` and any band-appropriate keyword-arguments,
            e.g. `(R=MyProcessor, G=MyOtherProcessor, B=None)`
        """
        # Call `super(…)`, passing `processor_factory`:
        super(BandFork, self).__init__(processor_factory, *args, **kwargs)

        # Reset `self.mode_t` if a new mode was specified --
        # N.B. we can’t use the “self.mode” property during “__init__(…)”:
        self.mode_t = kwargs.pop('mode', Mode.RGB)

    @property
    def mode(self):
        return self.mode_t

    @mode.setter
    def mode(self, value):
        if value is None:
            return
        if type(value) in string_types:
            value = Mode.for_string(value)
        if Mode.is_mode(value):
            # if value is not self.mode_t:
            self.set_mode_t(value)
        else:
            raise TypeError("invalid mode type: %s (%s)" %
                            (type(value), value))

    def set_mode_t(self, value):
        self.mode_t = value  # DOUBLE SHADOW!!

    @property
    def band_labels(self):
        return self.mode.bands

    def iterate(self):
        yield from (self[band_label] for band_label in self.band_labels)

    def split(self, image):
        return self.mode.process(image).split()

    def compose(self, *bands):
        return self.mode.merge(*bands)

    def process(self, image):
        processed = []
        for processor, band in zip(self.iterate(), self.split(image)):
            processed.append(processor.process(band))
        return self.compose(*processed)