Example #1
0
def simple_brushstroke(outIm, X, Y, sizeX, sizeY, angle, color, regLine):
    if sizeX > 1 and sizeY > 1:  # because computing a curved brush strokes for these small sizes fails (no curvature for width 1) and painting a line of with 1 looks ugly
        capSize = randint(int(sizeX * 0.3), int(sizeX * 0.5))
        bulgeSize = int(sizeX * 0.75)
        
        arc = create_arc_from_regLine(regLine)
        arc = randomizeArc(arc)
        sizeY = len(arc)
        if sizeY <= 1:
            return
        
        strokeim, strokepix = imgIO.createTransparentImg(sizeX + max(arc) + 1, sizeY)
        upperTemplate, lowerTemplate = build_simple_CutoffTemplates(sizeX, capSize)
        
        for x in range(0, sizeX):
            for y in range(0, sizeY):
                # if we are below the area our upperTemplate tells us not to paint and above the area lowerTemplate tells us not to paint:
                if y >= upperTemplate[x] and y <= (sizeY - lowerTemplate[x]):
                    strokepix[x + arc[y], y] = (color[0], color[1], color[2], 255)
        
        if angle < 0:
            template_midpoint = ((sizeX//2) + arc[0], upperTemplate[sizeX//2])  # len(upperTemplate) = sizeX
        elif angle > 0:
            template_midpoint = ((sizeX//2) + arc[-1], sizeY - max(1, lowerTemplate[sizeX//2]))  # len(upperTemplate) = sizeX
        else:  # angle == 0
            template_midpoint = ((sizeX//2) + arc[0], upperTemplate[sizeX//2])
            brushStart = template_midpoint
        if angle != 0:
            brushStart = getBrushStart(template_midpoint, strokeim, angle, color)  # get the coordinates of the brush stroke apex within the brushstroke image. These have to be subtracted from the X,Y position where the upper left corner of the brushstroke image should be pasted into the resulting image outIm
        
        # angle sais by how many degrees we need to rotate a segment to make it upright, so we need 360-angle here to revert it again
        strokeim = strokeim.rotate(360 - angle, resample=Image.BICUBIC, expand=True)
        strokeim_bbox = strokeim.getbbox() # because after rotating, there will be big empty areas around the segment
        strokeim = strokeim.crop(strokeim_bbox)
        strokepix = strokeim.load() # reload the pixelAccess object after rotating the image
        
        # now we paste the generated brush stroke so that the start of the regression line has the same coordinates as the start of the brush stroke (e.g. the midpoint of its left cap)
        outIm.paste(strokeim, (X - brushStart[0], Y - brushStart[1]), strokeim)
Example #2
0
def complex_brushstroke(outIm, X, Y, sizeX, sizeY, startColors, endColors, angleStart=-60, angleStop=10, arcType='log', groundSegment=False, colorify=0, noMargins=False):
    
    if colorify > 0:
        for i in range(0, len(startColors)):
            maxChannel = startColors[i].argmax()
            if random.choice([True, False]):
                startColors[i][maxChannel] = min(255, startColors[i][maxChannel] + colorify)
            else:
                startColors[i][maxChannel] = max(0, startColors[i][maxChannel] - colorify)
        for i in range(0, len(endColors)):
            maxChannel = startColors[i].argmax()
            if random.choice([True, False]):
                endColors[i][maxChannel] = min(255, endColors[i][maxChannel] + colorify)
            else:
                endColors[i][maxChannel] = max(0, endColors[i][maxChannel] - colorify)
            
    
    if groundSegment:
        sizeX = max(1, int(sizeX * 0.4))
        sizeY = max(1, int(sizeY * 2))
        angle = randint(80,100)
        capSize = randint(int(sizeX * 0.3), int(sizeX * 0.5))
        upperTemplate, lowerTemplate = build_simple_CutoffTemplates(sizeX, capSize)
        bulgeSize = int(sizeX * (3/5))
        if arcType == "cos":
            bulgeSize = int(sizeX * (2/5))
        whitenMargin = False
    else:
        angle = randint(angleStart, angleStop)  # rotate each brushstroke clockwise by -10 to 60 degrees
        upperTemplate, lowerTemplate = buildCutoffTemplates(sizeX, sizeY)
        bulgeSize = int(sizeX * (2/5))
        if arcType == "cos":
            bulgeSize = int(sizeX * (1/5))
        if noMargins:
            whitenMargin = False
        else:
            whitenMargin = random.choice([True, False])
    
    # need to subtract bulgeSize//2 from sizeXedge to make brush strokes narrower because the polynomial template cutoffs (cutting of from top and bottom) make them wider. Subtracting the stroke width by bulgeSize instead of bulgeSize//2 would make the strokes too narrow.
    strokeim, strokepix = imgIO.createTransparentImg(sizeX + int(ceil(bulgeSize/2)), sizeY)
    sizeXedge = sizeX - bulgeSize//2 # the number of pixels along the x-axis that will actually be colored in
    
    startColors = interpolateColors(startColors, sizeXedge) # from startColors, create a vector of length sizeXedge of RGBA values with smooth color transitions (unlike in the original startColors)
    endColors = interpolateColors(endColors, sizeXedge)
    
    if arcType == "log":
        arc = createLogArc(sizeY, bulgeSize)
    elif arcType == "cos":
        arc = createCosArc(sizeY, bulgeSize)
    arcLen = len(arc)
    
    # group hairlines into color streaks (smudges) along the brush stroke length:
    streakWidthMax = sizeXedge // 4
    streakWidthMin = randint(1, max(1,streakWidthMax))
    streakWidth = 0
    sizeYedge = sizeY - upperTemplate[0] - lowerTemplate[0]
    gradientStart = randint(sizeYedge//3, max(1,sizeYedge//2 -1)) # distance from upper margin at which gradient starts
    gradientStop = 0
    
    for x in range(0, sizeXedge):
        sizeYedge = sizeY - upperTemplate[x] - lowerTemplate[x] # y-size of the visible portion of brush stroke
        
        gradientStart, gradientStop, streakWidth, streakWidthMin = getStreakGradientPoints(streakWidth, streakWidthMin, streakWidthMax, gradientStart, gradientStop, sizeYedge)
        
        if sizeYedge == 1:
            strokepix[x + arc[0], 0] = (startColors[0][0], startColors[0][1], startColors[0][2], 255)
        else:
            progressStop = sizeYedge - gradientStop - gradientStart # size of range for which color gradient is computed. The rest uses constant colors.
            
            RGBdists = startColors[x] - endColors[x]  # color distances between start and end colors. This is sort of a gradient maximum. With this, and the spatial distance from a startColor pixel, we can compute the gradient for any y value between upperTemplate[x] (startColors) and sizeYedge (endColors).
            A = 255
            
            for y in range(upperTemplate[x], sizeYedge):
                # if we are below the area our upperTemplate tells us not to paint and above the area lowerTemplate tells us not to paint:
                
                # compute color gradient along y-axis (highly variable gradientStart and gradientStop values cause color streaks along the length of the brush stroke):
                R, G, B, A = computeYcolorGradient(y, sizeY, sizeYedge, startColors[x], endColors[x], gradientStart, gradientStop, whitenMargin, progressStop, RGBdists, A)
                strokepix[x + arc[y], y] = (R, G, B, A)
    
    strokeim = strokeim.rotate(angle, resample=Image.BICUBIC, expand=True)
    strokeim_bbox = strokeim.getbbox()
    strokeim = strokeim.crop(strokeim_bbox) # crop away unnecessary empty margins caused by rotation

    strokeimWidth, strokeimHeight = strokeim.size
    xMidpoint = int(round(strokeimWidth / 2))
    yMidpoint = int(round(strokeimHeight / 2))
    
    outIm.paste(strokeim, (X - xMidpoint, Y - yMidpoint), strokeim)
    del strokepix
    del strokeim