def perlinNoise(size=(256,256),octaves=None,seed=None): """ generate perlin noise """ if octaves is None: octaves=math.log(max(size),2.0) ret=np.zeros(size) for n in range(1,int(octaves)): k=n amp=1.0#/octaves/float(n) px=randomNoise((size[0]/k,size[1]/k),seed) px=scipy.ndimage.zoom(px,(float(ret.shape[0])/px.shape[0],float(ret.shape[1])/px.shape[1]),order=2) px=np.clip(px,0.0,1.0) # unfortunately zoom function can cause values to go out of bounds :( ret=(ret+px*amp)/2.0 # average with existing ret=np.clip(ret,0.0,1.0) return ret
def curves(image, controlPoints, clampBlack=0, clampWhite=1, degree=None, extrapolate=True): """ Perform a curves adjustment on an image :param image: evaluate the curve at these points can be pil image or numpy array :param controlPoints: set of [x,y] points that define the mapping :param clampBlack: clamp the black pixels to this value :param clampBlack: clamp the white pixels to this value :param degree: polynomial degree (if omitted, make it the same as the number of control points) :param extrapolate: go beyond the defined area of the curve to get values (oterwise returns NaN for outside values) :return: the adjusted image """ import scipy.interpolate img = numpyArray(image) count = len(controlPoints) if degree is None: degree = count - 1 else: degree = np.clip(degree, 1, count - 1) knots = np.clip(np.arange(count + degree + 1) - degree, 0, count - degree) spline = scipy.interpolate.BSpline(knots, controlPoints, degree) if len(img.shape) < 3: # for some reason it keeps the original point, which we need to strip off resultPoints = spline(img[:, :], extrapolate=extrapolate)[:, :, 0] else: # for some reason it keeps the original point, which we need to strip off resultPoints = spline(img[:, :, :], extrapolate=extrapolate)[:, :, :, 0] resultPoints = clampImage(resultPoints, clampBlack, clampWhite) return pilImage(resultPoints)
def _blendArray(front, back, fn, opacity: float = 1.0): """ :param fn: represents the B function from from the adobe blend modes documentation. It takes two parameters, Cb - the background pixels, and Cs - the source pixels The documentation originally appeared http://www.adobe.com/devnet/pdf/pdfs/blend_modes.pdf Copy included in source. (TODO: remove for copyright reasons?) :param opacity: blend mode opacity NOTE: always creates new image """ shift = 255.0 useOpacity = False # find some common ground w = max(front.width, back.width) h = max(front.height, back.height) if front.width < w or front.height < h: front = extendImageCanvas(front, (0, 0, w, h)) if back.width < w or back.height < h: back = extendImageCanvas(back, (0, 0, w, h)) mode = maxMode(front, back) if front.mode != mode: front = front.convert(mode) if back.mode != mode: back = back.convert(mode) # convert to array front = np.asarray(front) / shift back = np.asarray(back) / shift # calculate the alpha channel comp_alpha = np.maximum( np.minimum(front[:, :, 3], back[:, :, 3]) * opacity, shift) new_alpha = front[:, :, 3] + (1.0 - front[:, :, 3]) * comp_alpha np.seterr(divide='ignore', invalid='ignore') alpha = comp_alpha / new_alpha alpha[alpha == np.NAN] = 0.0 # blend the pixels combined = fn(front[:, :, :3], back[:, :, :3]) * shift combined = np.clip(combined, 0.0, 255.0) # clean up and reassemble #ratio_rs = final = np.reshape( combined, [combined.shape[0], combined.shape[1], combined.shape[2]]) #final = combined * ratio_rs + front[:, :, :3] * (1.0 - ratio_rs) if useOpacity: final = np.dstack((final, alpha)) # convert back to PIL image if useOpacity: final = Image.fromarray(final.astype('uint8'), mode) else: final = Image.fromarray(final.astype('uint8'), 'RGB') return final
def clampImage(img, minimum=None, maximum=None): """ Clamp an image's pixel to a valid color range :param img: clamp a numpy image to valid pixel values can be a PIL image for "do nothing" :param minimum: minimum value to clamp to (default is 0) :param maximum: maximum value to clamp to (default is the maximum pixel value) """ if minimum is None: # assign default if isFloat(img): minimum = 0.0 else: minimum = 0 elif isFloat(minimum) != isFloat( img): # make sure it matches the image's number space if isFloat(minimum): minimum = int(minimum * 255) else: minimum = minimum / 255.0 if maximum is None: # assign default if isFloat(img): maximum = 1.0 else: maximum = 255 elif isFloat(maximum) != isFloat( img): # make sure it matches the image's number space if isFloat(maximum): maximum = int(maximum * 255) else: maximum = maximum / 255.0 if isinstance(img, np.ndarray): if minimum == 0 and (maximum >= 255 or (maximum >= 1.0 and isFloat(maximum))): # because conversion implies clamping to a valid range return img img = numpyArray(img) #print(img.shape,minimum,maximum) return np.clip(img, minimum, maximum)
def valueRotate(img, amount=0.5): """ Rotate the values, wrapping around at the beginning """ return np.mod(np.clip(img, 0.0, 1.0) + amount, 1.0)
def trapez(y,y0,w): return np.clip(np.minimum(y+1+w/2-y0, -y+1+w/2+y0),0,1)
def section(img,minVal=0.0,maxVal=1.0): """ clamp the values of an image to a given range """ return normalize(np.clip(img,minVal,maxVal))
def generalBlend(topImage, mathStr, botImage, opacity=1.0, position=(0, 0), resize=True): """ mathstr - operators: basic math symbols ()*/%-+&| comparison operators == <= >= && || != comma as combining operator functions: abs() sqrt() pow() min() max() count() sum() sin() cos() tan() if(condition,then,else) images: top, bot channel: RGB, CMYK, HSV, A ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Notes: * Case sensitive * All values are percent values from 0..1 * After this operation, values will be cropped to 0..1 * Functions have two modes. They work between two channels if two given. If one given, they work on all values of that channel. ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Examples: RGB=top.RGB/bottom.RGB ... simple divide blend mode RGB=min(topRGB,bottomRGB) ... min blend mode RGB=(topRGB-min(topRGB)) ... use min in the other mode to normalize black values A=1-topV ... extract inverted black levels to alpha channel RGBA=topRGB/bottomRGB,1-topV ... use commas to specify different operations for different channels """ shift = 255.0 mathStr = mathStr.replace('\n', '').replace(' ', '').replace('\t', '') resultForm, equation = mathStr.split('=', 1) # find some common ground w = max(topImage.width, botImage.width) h = max(topImage.height, botImage.height) if topImage.width < w or topImage.height < h: topImage = extendImageCanvas(topImage, (0, 0, w, h)) if botImage.width < w or botImage.height < h: botImage = extendImageCanvas(botImage, (0, 0, w, h)) mode = 'RGBA' #maxMode(topImage,botImage) if topImage.mode != mode: topImage = topImage.convert(mode) if botImage.mode != mode: botImage = botImage.convert(mode) # convert to arrays topRGBA = np.asarray(topImage) / shift for tag in 'HSV': if equation.find('top' + tag) >= 0: topHSV = rgb2hsvArray(topRGBA) break for tag in 'CMYK': if equation.find('top' + tag) >= 0: topCMYK = rgb2cmykArray(topRGBA) break botRGBA = np.asarray(botImage) / shift for tag in 'HSV': if equation.find('bottom' + tag) >= 0: botHSV = rgb2hsvArray(botRGBA) break for tag in 'CMYK': if equation.find('bottom' + tag) >= 0: botCMYK = rgb2cmykArray(botRGBA) break # convert the equation into python code import re tokenizer = re.compile(r"([!<>=|&]+|[,()%*-+/])") equation = tokenizer.split(equation) replacements = { 'min': 'np.minimum', 'max': 'np.maximum', 'abs': 'np.abs', 'sqrt': 'np.sqrt', 'pow': 'np.pow', 'count': 'np.count', 'sum': 'np.sum', 'sin': 'np.sin', 'cos': 'np.cos', 'tan': 'np.tan', 'if': 'np.where', 'top.RGBA': 'topRGBA[:,:,:]', 'top.RGB': 'topRGBA[:,:,:3]', 'top.R': 'topRGBA[:,:,0]', 'top.G': 'topRGBA[:,:,1]', 'top.B': 'topRGBA[:,:,2]', 'top.A': 'topRGBA[:,:,3]', 'top.CMYK': 'topCMYK[:,:,:]', 'top.CMY': 'topCMYK[:,:,:3]', 'top.C': 'topCMYK[:,:,0]', 'top.M': 'topCMYK[:,:,1]', 'top.Y': 'topCMYK[:,:,2]', 'top.K': 'topCMYK[:,:,3]', 'top.HSV': 'topHSV[:,:,:]', 'top.H': 'topHSV[:,:,0]', 'topS': 'topHSV[:,:,1]', 'topV': 'topHSV[:,:,2]', 'bottom.RGBA': 'bottomRGBA[:,:,:]', 'bottom.RGB': 'bottomRGBA[:,:,:3]', 'bottom.R': 'bottomRGBA[:,:,0]', 'bottom.G': 'bottomRGBA[:,:,1]', 'bottom.B': 'bottomRGBA[:,:,2]', 'bottom.A': 'bottomRGBA[:,:,3]', 'bottom.CMYK': 'bottomCMYK[:,:,:]', 'bottom.CMY': 'bottomCMYK[:,:,:3]', 'bottom.C': 'bottomCMYK[:,:,0]', 'bottom.M': 'bottomCMYK[:,:,1]', 'bottom.Y': 'bottomCMYK[:,:,2]', 'bottom.K': 'bottomCMYK[:,:,3]', 'bottom.HSV': 'bottomHSV[:,:,:]', 'bottom.H': 'bottomHSV[:,:,0]', 'bottom.S': 'bottomHSV[:,:,1]', 'bottom.V': 'bottomHSV[:,:,2]', } for i, val in enumerate(equation): if val and val[0] not in r'0123456789,()%*-+/!<>=|&': if val not in replacements: raise Exception('ERR: illegal value in equation "' + val + '"') equation[i] = replacements[val] equation = '(' + (''.join(equation)) + ')' # run the operation and join the results with dstack() final = None for channelSet in eval(equation): if final is None: final = channelSet else: final = np.dstack((final, channelSet)) # convert to RGB colorspace if necessary if resultForm == 'HSV': final = hsv2rgbArray(final) elif resultForm == 'CMYK': final = cmyk_to_rgb(final) final = final * shift # if alpha channel was missing, add one if len(final[0][1]) < 4: # calculate the alpha channel comp_alpha = np.maximum( np.minimum(topRGBA[:, :, 3], botRGBA[:, :, 3]) * opacity, shift) new_alpha = topRGBA[:, :, 3] + (1.0 - topRGBA[:, :, 3]) * comp_alpha np.seterr(divide='ignore', invalid='ignore') alpha = comp_alpha / new_alpha alpha[alpha == np.NAN] = 0.0 # blend the pixels combined = final combined = np.clip(combined, 0.0, 255.0) # clean up and reassemble #ratio_rs= final = np.reshape( combined, [combined.shape[0], combined.shape[1], combined.shape[2]]) #final=combined*ratio_rs+topRGBA[:,:,:3]*(1.0-ratio_rs) final = np.dstack((final, alpha)) # convert the final result back into a PIL image final = Image.fromarray(final.astype('uint8'), mode) return final