def posterize(image): """ Posterize an image, that is, more or less to clamp each color to the nearest multiple of 1/4 :param image: the image to posterize :return: the converted image """ image = numpyArray(image) return np.where( image <= 0.25, 0.20, np.where(image <= 0.5, 0.40, np.where(image <= 0.75, 0.60, 0.80)))
def waveImage(size=(256,256),repeats=2,angle=0,wave='sine',radial=False): """ create an image based on a sine, saw, or triangle wave function """ ret=np.zeros(size) if radial: raise NotImplementedError() # TODO: Implement radial wave images else: twopi=2*pi thetas=np.arange(size[0])*float(repeats)/size[0]*twopi if wave=='sine': ret[:,:]=0.5+0.5*np.sin(thetas) elif wave=='saw': n=np.round(thetas/twopi) thetas-=n*twopi ret[:,:]=np.where(thetas<0,thetas+twopi,thetas)/twopi elif wave=='triangle': ret[:,:]=1.0-2.0*np.abs(np.floor((thetas*(1.0/twopi))+0.5)-(thetas*(1.0/twopi))) return ret
def selectByPoint(img, location, tolerance=0, soften=10, smartSoften=True, colorspace='RGB', pickMode='average'): """ Works like the "magic wand" selection tool. It is different than selectByColor(img,pickColor(img,location)) in that only a contiguious region is selected :param img: can be a pil image, numpy array, etc :param location: can be a single point or an [x,y,w,h] :param tolerance: how close the selection must be :param soften: apply a soften radius to the edges of the selection :param smartSoften: multiply the soften radius by how near the pixel is to the selection color :param colorspace: change the given img (assumed to be RGB) into another corlorspace before matching TODO: implement this! :returns: black and white image in a numpy array (usable as a selection or a mask) """ img = numpyArray(img) img = changeColorspace(img) # selectByColor, but the selection = selectByColor(img, pickColor(img, location, pickMode), tolerance, soften, smartSoften, colorspace=imageMode(img)) # now identify islands from selection labels, _ = scipy.ndimage.label(selection) # grab only the named islands within the original location if len(location) < 4: labelsInSel = [labels[location[0], location[1]]] else: labelsInSel = np.unique( labels[location[0]:location[0] + location[2] + 1, location[0]:location[0] + location[2] + 1]) # only keep portions of selection within our islands selection = np.where(np.isin(labels, labelsInSel), selection, 0.0) return selection
def selectionToPath(img, midpoint=0.5): """ convert a black and white selection (or mask) into a path :param img: a pil image, numpy array, etc :param midpoint: for grayscale images, this is the cuttoff point for yes/no :return: a closed polygon [(x,y)] TODO: I resize the image to give laplace a border to lock onto, but do I need to scale the points back down again?? """ img = imageBorder(img, 1, 0) img = numpyArray(img) img = np.where(img >= midpoint, 1.0, 0.0) img = scipy.ndimage.laplace(img) points = np.nonzero(img) # returns [[y points],[x points]] points = np.transpose(points) # convert to a nice point-pair representation points = [(point[1], point[0]) for point in points] return points
def colormap(img, colors=None): """ apply the colors to a grayscale image if a color image is provided, convert it (thus, acts like a "colorize" function) :param img: a grayscale image :param colors: [(decimalPercent,color),(...)] if no colors are given, then [(0.0,black),(1.0,white)] if a single color and no percent is given, assume [(0.0,black),(0.5,theColor),(1.0,white)] :return: the resulting image """ img = grayscale(img) if not isFloat(img): img = img / 255.0 if colors is None: colors = [(0.0, (0.0, 0.0, 0.0)), (1.0, (1.0, 1.0, 1.0))] elif not isinstance(colors[0], (tuple, list, np.ndarray)): white = [] black = [] if isinstance(colors, str): colors = strToColor(colors) if isFloat(colors): imax = 1.0 imin = 0.0 else: imax = 255 imin = 0 for _ in range(len(colors)): white.append(imax) black.append(imin) if len(colors) in [2, 4]: # keep same alpha value black[-1] = colors[-1] white[-1] = white[-1] colors = [(0.0, black), (0.5, colors), (1.0, white)] else: colors.sort() # make sure we go from low to high # make sure colors are in the shape we need colors = [[ matchColorToImage(color[0], img), np.array(strToColor(color[1])) ] for color in colors] shape = (img.shape[0], img.shape[1], len(color[1])) img2 = np.ndarray(shape) img = img[..., None] if True: lastColor = None for color in colors: if lastColor is None: img2 += color[1] else: percent = (img - lastColor[0]) * lastColor[0] / color[0] img2 = np.where( np.logical_and(img > lastColor[0], img <= color[0]), (color[1] * percent) + (lastColor[1] * (1 - percent)), img2) lastColor = color img2 = np.where(img > lastColor[0], lastColor[1], img2) else: def gradMap(c): lastColor = None for color in colors: if c < color[0]: if lastColor is None: return color[1] percent = (c - lastColor[0]) / color[0] return (lastColor[1] * percent + color[1]) / (2 * percent) lastColor = color return lastColor[1] img2 = perPixel(gradMap, img, clamp=False) return img2
def shadows(img, threshold=0.1): """ return a mask of all shadows """ return np.where(img > threshold, 0, 1 - img)
def highlights(img, threshold=0.9): """ return a mask of all highlights """ return np.where(img < threshold, 0, img)
def _hardMix(bottom, top): # NOTE: I think this is right...? return np.where(bottom > 0.5, 1.0, 0.0)
def _pinLight(bottom, top): # NOTE: I think this is right...? return np.where(bottom > 0.5, _darken(bottom, top), _lighten(bottom, top))
def _linearLight(bottom, top): return np.where(top > 0.5, _linearDodge(bottom, top), _linearBurn(bottom, top))
def _vividLight(bottom, top): return np.where(top > 0.5, _colorDodge(bottom, top), _colorBurn(bottom, top))
def _hardLight(bottom, top): return np.where(bottom <= 0.5, _multiply(top, 2.0 * bottom), _screen(top, 2.0 * bottom - 1.0))
def _hardOverlay(bottom, top): # this is a krita thing return np.where(bottom > 0.5, _multiply(top, 2.0 * bottom), _divide(top, 2.0 * bottom - 1.0))
def _overlay(bottom, top): # TODO: the colors saturation comes out a little higher than gimp, but close return np.where(top <= 0.5, 2.0 * bottom * top, 1.0 - 2.0 * (1.0 - bottom) * (1.0 - top))
def _dissolve(bottom, top): # TODO: there is a bug. instead of randomly merging pixels, it randomly merges color values rand = np.random.random(bottom.shape) return np.where(rand > 0.5, bottom, top)
def selectByColor(img, color, tolerance=0, soften=10, smartSoften=True, colorspace='RGB'): """ Select all pixels of a given color :param img: can be a pil image, numpy array, etc :param color: (in colorspace units) can be anything that pickColor returns: a color int array a color string to decode a color range (colorLow,colorHigh) an array of any of these :param tolerance: how close the selection must be :param soften: apply a soften radius to the edges of the selection :param smartSoften: multiply the soften radius by how near the pixel is to the selection color :param colorspace: change the given img (assumed to be RGB) into another corlorspace before matching :returns: black and white image in a numpy array (usable as a selection or a mask) """ from . import colorSpaces img = numpyArray(img) img = colorSpaces.changeColorspace(img, colorspace) if (isinstance(color, list) and isinstance(color[0], list)) or (isinstance(color, np.ndarray) and len(color.shape) > 1): # there are multiple colors, so select them all one at a time # TODO: this could possibly be made faster with array operations?? ret = None for c in color: if ret is None: ret = selectByColor(img, c, tolerance, soften, smartSoften) else: ret = ret + selectByColor(img, c, tolerance, soften, smartSoften) ret = clampImage(ret) elif isinstance(color, tuple): # color range - select all colors "between" these two in the given color space color = (matchColorToImage(color[0], img), matchColorToImage(color[1], img)) matches = np.logical_and(img >= color[0], img <= color[1]) ret = np.where(matches.all(axis=2), 1.0, 0.0) else: # a single color (or a series of colors that have been averaged down to one) color = matchColorToImage(color, img) if isFloat(color): imax = 1.0 imin = 0.0 else: imax = 255 imin = 0 numColors = img.shape[-1] avgDelta = np.sum(np.abs(img[:, :] - color), axis=-1) / numColors ret = np.where(avgDelta <= tolerance, imax, imin) if soften > 0: ret = gaussianBlur(ret, soften) if smartSoften: ret = np.minimum(imax, ret / avgDelta) return ret