def matrix(self) -> list: """ a 3x3 or 5x5 convolution matrix useful for: convolve """ ret = [] matrix = self._getProperty('matrix', '[[0,0,0],[0,1,0],[0,0,0]]').replace( ' ', '') if matrix.startswith('[['): matrix = matrix[1:-1] else: matrix = matrix matrix = matrix.split('[')[1:] size = None for row in matrix: row = row.split(']', 1)[0].split(',') if (size is None and len(row) not in [5, 3]) or size != len(row): size = len(row) info = (matrix, len(row)) raise SmartimageError( self, 'Convolution matrix "%s" size %d not 3x3 or 5x5!' % info) ret.append([float(v) for v in row]) if len(ret) != size: info = (matrix, len(ret)) raise SmartimageError( self, 'Convolution matrix "%s" size %d not 3x3 or 5x5!' % info) return ret
def image(self) -> PilPlusImage: text = self.text # create a fake image, ImageDraw to use for size calculation img = Image.new('L', (1, 1)) d = ImageDraw.Draw(img) # some sanity checking if not text: info = (self.name, self.root.filename, self.xml.sourceline) print('WARN: Layer "%s" has no text specified - %s line %d' % info) return None if self.w == 0: size = d.textsize(text, font=self.font, spacing=self.lineSpacing) w, h = size else: # determine the text wrapping and final size w = self.w textWrapper = textwrap.TextWrapper() charSize = d.textsize('l', font=self.font, spacing=self.lineSpacing)[0] textWrapper.width = int(w / len(text) * charSize) size = d.textsize(text, font=self.font, spacing=self.lineSpacing) while size[0] > w and textWrapper.width > 0: textWrapper.width -= 1 text = textWrapper.fill(self.text) size = d.textsize(text, font=self.font, spacing=self.lineSpacing) h = max(size[1], self.h) # determine vertical alignment position x = self.x y = self.y align = self.align verticalAlign = self.verticalAlign if verticalAlign == 'top': pass elif verticalAlign == 'bottom': y += h - size[1] elif verticalAlign in ['center', 'middle']: y += h / 2 - size[1] / 2 else: raise SmartimageError( self, 'ERR: Unknown text vertical align mode "%s"' % verticalAlign) if align == 'left': pass elif align == 'right': x += w - size[0] elif align in ['center', 'middle']: x += w / 2 - size[0] / 2 else: raise SmartimageError(self, 'Unknown text alignment mode "%s"' % align) # draw the stuff img = Image.new('RGBA', (int(w), int(h)), (128, 128, 128, 0)) d = ImageDraw.Draw(img) d.multiline_text((0, 0), text, tuple(self.color), self.font, self.anchor, self.lineSpacing, self.align) return img
def _createChild(self,xml:str)->FormElement: """ create a child form element """ child=None if xml==self.xml or xml.__class__.__name__ in ['_Comment']: pass elif xml.tag=='text': child=Text(self,xml) elif xml.tag=='points': child=Points(self,xml) elif xml.tag=='image': child=Image(self,xml) elif xml.tag=='point': child=Point(self,xml) elif xml.tag=='select': child=Select(self,xml) elif xml.tag=='color': child=Color(self,xml) elif xml.tag=='preview': child=Preview(self,xml) elif xml.tag=='numeric': child=Numeric(self,xml) else: raise SmartimageError(self,'Unknown element, "%s"'%xml.tag) return child
def _createChild(self,parent:'FormElement',xml:str)->'FormElement': """ create a child form element """ child=None if xml==self.xml or xml.__class__.__name__ in ['_Comment']: pass else: raise SmartimageError(self,'Unknown element, "%s"'%xml.tag) return child
def target(self) -> Layer: """ the target to link to """ if self._target is None: layerId = self.ref.split('.', 1)[0] self._target = self.getLayer(layerId) if self._target is None: raise SmartimageError(self, 'ERR: broken link to layer %s' % layerId) return self._target
def roi(self) -> Union[PilPlusImage, None]: """ "region of interest" used for smart resizing and possibly other things """ ref = self._getProperty('roi') try: img = self.root.imageByRef(ref) except FileNotFoundError as e: raise SmartimageError(self, 'Missing roi resource "%s"' % e.filename) return img
def renderImage(self, layer: 'Layer') -> Union[PIL.Image.Image, None]: """ render an image from the layer image and all of its children WARNING: Do not modify the image without doing a .copy() first! """ image = None # loop prevention if layer.elementId in self.visitedLayers: info = (layer.elementId, layer.name) raise SmartimageError(layer, 'Link loop with layer %s "%s"' % info) self.visitedLayers.add(layer.elementId) # push a new variable context # do we need to do anything? opacity = layer.opacity if opacity <= 0.0 or not layer.visible: self.log('skipping', layer.name) else: self.log('creating new %s layer named "%s"' % (layer.__class__.__name__, layer.name)) image = layer.image # NOTE: base image can be None for childLayer in layer.children: childImage = childLayer.renderImage(self) image = composite(childImage, image, opacity=childLayer.opacity, blendMode=childLayer.blendMode, mask=childLayer.mask, position=childLayer.location, resize=True) self.log('adding child layer "%s" at %s' % (childLayer.name, childLayer.location)) if layer.cropping is not None: image = image.crop(layer.cropping) if layer.rotation % 360 != 0: self.log('rotating', layer.name) bounds = Bounds(0, 0, image.width, image.height) bounds.rotateFit(layer.rotation) image = extendImageCanvas(image, bounds) image = image.rotate(layer.rotation) # logging if image is None: self.log('info', 'for "%s":' % layer.name) self.log('info', ' NULL IMAGE') else: self.log('info', 'for "%s":' % layer.name) self.log('info', ' mode=' + image.mode) self.log('info', ' bounds=(0,0,%d,%d)' % (image.width, image.height)) self.log('finished', layer.name) # pop off tracking info for this layer self.visitedLayers.pop() return image
def _createChild(self,parent:FormElement,xml:str): """ the child elements are all points (duh) """ child=None if xml==self.xml or xml.__class__.__name__ in ['_Comment']: pass elif xml.tag=='point': child=Point(parent,xml) else: raise SmartimageError(self,'Unknown element, "%s"'%xml.tag) return child
def _createChild(self,parent:FormElement,xml:str): """ the only child elements allowed are option form elements """ child=None if xml==self.xml or xml.__class__.__name__ in ['_Comment']: pass elif xml.tag=='option': child=Option(parent,xml) else: raise SmartimageError(self,'Unknown element, "%s"'%xml.tag) return child
def normalMap(self) -> Union[PilPlusImage, None]: """ A 3d normal map, wherein red=X, green=Y, blue=Z (facing directly out from the screen) """ ref = self._getProperty('normalMap', None) if ref is None: ref = normalMapFromImage(self.image) try: img = self.root.imageByRef(ref) except FileNotFoundError as e: raise SmartimageError( self, 'Missing normalMap resource "%s"' % e.filename) return img
def bumpMap(self) -> Union[PilPlusImage, None]: """ a grayscale bump map or heightmap. """ ref = self._getProperty('bumpMap', None) if ref is None: ref = heightMapFromNormalMap(self.normalMap) try: img = self.root.imageByRef(ref) except FileNotFoundError as e: raise SmartimageError(self, 'Missing bumpMap resource "%s"' % e.filename) return img
def _createChild(self, parent, xml) -> 'Layer': """ create a child layer """ child = None if xml == self.xml or xml.__class__.__name__ in ['_Comment']: pass elif xml.tag in ('form', 'color', 'select', 'preview', 'numeric'): # form elements do not belong to an image pass elif xml.tag == 'text': from . import text child = text.TextLayer(parent, xml) elif xml.tag == 'link': from . import link child = link.Link(parent, xml) elif xml.tag == 'image': from . import image child = image.ImageLayer(parent, xml) elif xml.tag == 'group': child = Layer(parent, xml) elif xml.tag == 'modifier': from . import modifier child = modifier.Modifier(parent, xml) elif xml.tag == 'solid': from . import solid child = solid.Solid(parent, xml) elif xml.tag == 'texture': from . import texture child = texture.Texture(parent, xml) elif xml.tag == 'pattern': from . import pattern child = pattern.Pattern(parent, xml) elif xml.tag == 'particles': from . import particles child = particles.Particles(parent, xml) elif xml.tag == 'numberspace': from . import numberspace child = numberspace.NumberSpace(parent, xml) elif xml.tag == 'ext': from . import extension child = extension.ExtensionLayer(parent, xml) else: raise SmartimageError( self, 'ERR: unexpected child element "%s"' % xml.tag) return child
def font(self) -> str: """ change to any TrueType or OpenType font TODO: check inside the zipfile for embedded fonts! if the font is installed on your system, will attempt to install it from fonts.google.com returns PIL font object """ if self.fontName is not None: fontName = self.fontName else: fontName = DEFAULT_FONT if self._font is None: # first look in the zipped file for name in self.root.componentNames: name = name.rsplit('.', 1) if name[0] == fontName: if len(name) == 1 or name[1] in [ 'ttf', 'otf', 'otc', 'ttc' ]: name = '.'.join(name) component = self.root.getComponent(name) self._font = ImageFont.truetype( component, self.fontSize, self.typeFace) component.close() break if self._font is None: # try the os try: self._font = ImageFont.truetype(fontName, self.fontSize, self.typeFace) except IOError: self._font = None if self._font is None: fontdata = downloadFont(fontName) if fontdata is not None: self._font = ImageFont.truetype(fontdata, self.fontSize, self.typeFace) if self._font is None: raise SmartimageError(self, 'Cannot find font anywhere: "%s"' % fontName) return self._font
def h(self) -> float: """ the current height """ h = self._getProperty('h', 'auto') if h in ('', '0', 'auto', 'None'): if self.children: h = [child.y + child.h for child in self.children] h = max(h) - self.y else: h = 0 else: try: h = float(h) except ValueError: raise SmartimageError(self, 'Unable to convert h="%s" to float' % h) return h
def w(self) -> float: """ the current width """ w = self._getProperty('w', 'auto') if w in ('', '0', 'auto', 'None'): if self.children: w = [child.x + child.w for child in self.children] w = max(w) - self.x else: w = 0 else: try: w = float(w) except ValueError: raise SmartimageError(self, 'Unable to convert w="%s" to float' % w) return w
def roi(self) -> Union[PilPlusImage, None]: """ the region of interest of a graphical image if there is none, figure it out """ ref = self._getProperty('roi') if ref is not None: try: img = self.root.imageByRef(ref) except FileNotFoundError as e: raise SmartimageError(self, 'Missing roi resource "%s"' % e.filename) else: img = interest(self.image) if img.size != self.image.size: img.resize(self.image.size) return img
def image(self): import time start = time.time() if self.type == 'voronoi': img = voronoi(self.size, self.numPoints, 'simple', self.invert, seed=self.seed) elif self.type == 'random': img = smoothNoise(self.size, 1.0 - self.noiseSoften, seed=self.seed) elif self.type == 'clouds': img = turbulence(self.size, seed=self.seed) elif self.type == 'waveform': img = waveformTexture(self.size, self.waveform, self.frequency, self.noise, self.noiseBasis, self.noiseOctaves, self.noiseSoften, self.direction, self.invert, seed=self.seed) elif self.type == 'clock': img = clock2(self.size, self.waveform, self.frequency, self.noise, self.noiseBasis, self.noiseOctaves, self.noiseSoften, self.direction, self.invert, seed=self.seed) else: raise SmartimageError( self, 'texture type "%s" not implemented' % self.type) img = pilImage(img) img.immutable = True # mark this image so that compositor will not alter it end = time.time() return img
def x(self) -> float: """ if value=="auto", then take on the child value """ x = self._getProperty('x', 'auto') if x in ('', 'auto', 'None'): x = [child.x for child in self.children] if x: x = min(x) else: x = 0 else: try: x = float(x) except ValueError: raise SmartimageError(self, 'Unable to convert x="%s" to float' % x) if x < 0: x = self.w + x return x
def y(self) -> float: """ if value=="auto", then take on the child value """ y = self._getProperty('y', 'auto') if y in ('', 'auto', 'None'): y = [child.y for child in self.children] if y: y = min(y) else: y = 0 else: try: y = float(y) except ValueError: raise SmartimageError(self, 'Unable to convert y="%s" to float' % y) if y < 0: y = self.h + y return y
def image(self, image): """ Can assign any PIL image or a filename that can be loaded as one """ if image is None: raise SmartimageError(self, 'Attempt to assign None as an image') if isinstance(image, str): filename = image image = PilPlusImage(filename) imageFormat = image.format elif image.format is not None: filename = self.name + '.' + image.format.lower() imageFormat = image.format else: filename = self.name + '.png' imageFormat = 'PNG' data = io.BytesIO() image.save(data, imageFormat) data = data.getvalue() filename = self.root.addComponent(filename, data, overwrite=False) self.src = filename
def image(self) -> Union[PilPlusImage, None]: """ the image for this layer """ try: img = self.root.imageByRef(self.src) except FileNotFoundError as e: raise SmartimageError( self, 'Missing image src resource "%s"' % e.filename) if img is None: return img w = self._getProperty('w', 'auto') h = self._getProperty('h', 'auto') if (w not in ['0', 'auto']) and (h not in ['0', 'auto']): if w in ['0', 'auto']: w = img.width * (img.height / h) elif h in ['0', 'auto']: h = img.height * (img.width / w) img = img.resize((int(w), int(h)), img.ANTIALIAS) if img is not None: img.immutable = True # mark this image so that compositor will not alter it return img
def mask(self) -> Union[PilPlusImage, None]: """ grayscale image to be used as layer mask it can also be an image with alpha component which will be INVERTED to use as mask! NOTE: the mask can either be a link to another file, TODO: this could be made smarter with imageTools """ from PIL import Image ref = self._getProperty('mask') try: img = self.root.imageByRef(ref) except FileNotFoundError as e: raise SmartimageError(self, 'Missing mask resource "%s"' % e.filename) if img is not None: if img.mode in ['RGBA', 'LA']: alpha = img.split()[-1] img = Image.new("RGBA", img.size, (0, 0, 0, 255)) img.paste(alpha, mask=alpha) img = img.convert("L") return img
def _transform(self, img): """ For a list of more transformations available, see: http://pillow.readthedocs.io/en/latest/reference/ImageFilter.html """ filterType = self.filterType if filterType == 'shadow': offsX = 10 offsY = 10 blurRadius = self.blurRadius shadow = img.copy() control = ImageEnhance.Brightness(shadow) shadow = control.enhance(0) final = Image.new( "RGBA", (img.width + abs(offsX), img.height + abs(offsX))) final.putalpha(int(self.modifierOpacity * 255)) dest = (int(max(offsX - blurRadius, 0)), int(max(offsY - blurRadius, 0))) final.alpha_composite(shadow, dest=dest) if blurRadius > 0: final = final.filter( ImageFilter.GaussianBlur(radius=blurRadius)) final.alpha_composite(img, dest=(max(-offsX, 0), max(-offsY, 0))) img = final elif filterType == 'convolve': # TODO: make use of separable convoutions, fourier domain convolutions, # and other speed-ups matrix = self.matrix ImageFilter.Kernel((len(matrix), len(matrix)), matrix, scale=self.divide, offset=self.add) elif filterType == 'autocrop': # the idea is you cut off as many rows from each side that are all alpha=0 raise NotImplementedError() elif filterType == 'brightness': control = ImageEnhance.Brightness(img) control.amount = self.amount elif filterType == 'contrast': control = ImageEnhance.Contrast(img) control.amount = self.amount elif filterType == 'saturation': control = ImageEnhance.Color(img) control.amount = self.amount elif filterType == 'blur': img = img.filter(ImageFilter.BLUR) #,self.amount) elif filterType == 'gaussian_blur': blurRadius = self.blurRadius if blurRadius > 0: img = img.filter(ImageFilter.GaussianBlur(radius=blurRadius)) elif filterType == 'box_blur': blurRadius = self.blurRadius if blurRadius > 0: img.filter(ImageFilter.BoxBlur(radius=blurRadius)) elif filterType == 'unsharp_mask': ImageFilter.UnsharpMask(radius=self.blurRadius, percent=self.amount * 100, threshold=self.threshold) elif filterType == 'contour': img = img.filter(ImageFilter.CONTOUR, self.amount) elif filterType == 'detail': img = img.filter(ImageFilter.DETAIL, self.amount) elif filterType == 'edge_enhance': img = img.filter(ImageFilter.EDGE_ENHANCE, self.amount) elif filterType == 'edge_enhance_more': img = img.filter(ImageFilter.EDGE_ENHANCE_MORE, self.amount) elif filterType == 'emboss': img = img.filter(ImageFilter.EMBOSS, self.amount) elif filterType == 'edge_detect': img = img.filter(ImageFilter.FIND_EDGES, self.amount) elif filterType == 'smooth': img = img.filter(ImageFilter.SMOOTH, self.amount) elif filterType == 'smooth_more': img = img.filter(ImageFilter.SMOOTH_MORE, self.amount) elif filterType == 'sharpen': img = img.filter(ImageFilter.SHARPEN, self.amount) elif filterType == 'invert': img = ImageOps.invert(img) elif filterType == 'flip': img = ImageOps.flip(img) elif filterType == 'mirror': img = ImageOps.mirror(img) elif filterType == 'posterize': img = ImageOps.posterize(img) elif filterType == 'solarize': img = ImageOps.solarize(img) else: raise SmartimageError(self, 'Unknown modifier "%s"' % filterType) return img