Wrap image around a circle

前端 未结 2 792
眼角桃花
眼角桃花 2021-02-06 04:00

What I\'m trying to do in this example is wrap an image around a circle, like below.

To wrap the image I simply calculated the x,y coordinates using tr

相关标签:
2条回答
  • 2021-02-06 04:33

    You are having problems filling up your circle because you are approaching this from the wrong way – quite literally.

    When mapping from a source to a target, you need to fill your target, and map each translated pixel from this into the source image. Then, there is no chance at all you miss a pixel, and, equally, you will never draw (nor lookup) a pixel more than once.

    The following is a bit rough-and-ready, it only serves as a concept example. I first wrote some code to draw a filled circle, top to bottom. Then I added some more code to remove the center part (and added a variable Ri, for "inner radius"). This leads to a solid ring, where all pixels are only drawn once: top to bottom, left to right.

    How you exactly draw the ring is not actually important! I used trig at first because I thought of re-using the angle bit, but it can be done with Pythagorus' as well, and even with Bresenham's circle routine. All you need to keep in mind is that you iterate over the target rows and columns, not the source. This provides actual x,y coordinates that you can feed into the remapping procedure.

    With the above done and working, I wrote the trig functions to translate from the coordinates I would put a pixel at into the original image. For this, I created a test image containing some text:

    and a good thing that was, too, as in the first attempt I got the text twice (once left, once right) and mirrored – that needed a few minor tweaks. Also note the background grid. I added that to check if the 'top' and 'bottom' lines – the outermost and innermost circles – got drawn correctly.

    Running my code with this image and Ro,Ri at 100 and 50, I get this result:

    You can see that the trig functions make it start at the rightmost point, move clockwise, and have the top of the image pointing outwards. All can be trivially adjusted, but this way it mimics the orientation that you want your image drawn.

    This is the result with your iris-image, using 33 for the inner radius:

    and here is a nice animation, showing the stability of the mapping:

    Finally, then, my code is:

    import math as m
    from PIL import Image
    
    Ro = 100.0
    Ri = 50.0
    
    # img = [[1 for x in range(int(width))] for y in range(int(height))]
    cir = [[0 for x in range(int(Ro * 2))] for y in range(int(Ro * 2))]
    
    # image = Image.open('0vWEI.png')
    image = Image.open('this-is-a-test.png')
    # data = image.convert('RGB')
    pixels = image.load()
    width, height = image.size
    
    def shom_im(img):  # for showing data as image
        list_image = [item for sublist in img for item in sublist]
        new_image = Image.new("RGB", (len(img[0]), len(img)))
        new_image.putdata(list_image)
        new_image.save("result1.png","PNG")
        new_image.show()
    
    
    for i in range(int(Ro)):
        # outer_radius = Ro*m.cos(m.asin(i/Ro))
        outer_radius = m.sqrt(Ro*Ro - i*i)
        for j in range(-int(outer_radius),int(outer_radius)):
            if i < Ri:
                # inner_radius = Ri*m.cos(m.asin(i/Ri))
                inner_radius = m.sqrt(Ri*Ri - i*i)
            else:
                inner_radius = -1
            if j < -inner_radius or j > inner_radius:
                # this is the destination
                # solid:
                # cir[int(Ro-i)][int(Ro+j)] = (255,255,255)
                # cir[int(Ro+i)][int(Ro+j)] = (255,255,255)
                # textured:
    
                x = Ro+j
                y = Ro-i
                # calculate source
                angle = m.atan2(y-Ro,x-Ro)/2
                distance = m.sqrt((y-Ro)*(y-Ro) + (x-Ro)*(x-Ro))
                distance = m.floor((distance-Ri+1)*(height-1)/(Ro-Ri))
            #   if distance >= height:
            #       distance = height-1
                cir[int(y)][int(x)] = pixels[int(width*angle/m.pi) % width, height-distance-1]
                y = Ro+i
                # calculate source
                angle = m.atan2(y-Ro,x-Ro)/2
                distance = m.sqrt((y-Ro)*(y-Ro) + (x-Ro)*(x-Ro))
                distance = m.floor((distance-Ri+1)*(height-1)/(Ro-Ri))
            #   if distance >= height:
            #       distance = height-1
                cir[int(y)][int(x)] = pixels[int(width*angle/m.pi) % width, height-distance-1]
    
    shom_im(cir)
    

    The commented-out lines draw a solid white ring. Note the various tweaks here and there to get the best result. For instance, the distance is measured from the center of the ring, and so returns a low value for close to the center and the largest values for the outside of the circle. Mapping that directly back onto the target image would display the text with its top "inwards", pointing to the inner hole. So I inverted this mapping with height - distance - 1, where the -1 is to make it map from 0 to height again.

    A similar fix is in the calculation of distance itself; without the tweaks Ri+1 and height-1 either the innermost or the outermost row would not get drawn, indicating that the calculation is just one pixel off (which was exactly the purpose of that grid).

    0 讨论(0)
  • 2021-02-06 04:40

    I think what you need is a noise filter. There are many implementations from which I think Gaussian filter would give a good result. You can find a list of filters here. If it gets blurred too much:

    • keep your first calculated image
    • calculate filtered image
    • copy fixed pixels from filtered image to first calculated image

    Here is a crude average filter written by hand:

    cir_R = int(Ro*2) # outer circle 2*r
    inner_r = int(Ro - 0.5 - len(img)) # inner circle r
    for i in range(1, cir_R-1):
        for j in range(1, cir_R-1):
            if cir[i][j] == 0: # missing pixel
                dx = int(i-Ro)
                dy = int(j-Ro)
                pix_r2 = dx*dx + dy*dy # distance to center
                if pix_r2 <= Ro*Ro and pix_r2 >= inner_r*inner_r:
                    cir[i][j] = (cir[i-1][j] + cir[i+1][j] + cir[i][j-1] +
                        cir[i][j+1])/4
    
    
    shom_im(cir)
    

    and the result:

    This basically scans between two ranges checks for missing pixels and replaces them with average of 4 pixels adjacent to it. In this black white case it is all white.

    Hope it helps!

    0 讨论(0)
提交回复
热议问题