Forum Archive

Circle view for ui

Phuket2

i am trying to make a circular view/widget like we have for our pics in the forum. I think I am going about it the right way. I am using a custom ui.View class and overriding the draw method.

I can make a white circle that is clipped...Yeah 🎉🎉🎉
Not what I need though. I need to be able to invert my clipping so I get a keyhole effect. The idea is that this view would be placed on top of a image view, just showing the circular cut out of the image beneath.
I think the answer lies with the append_path method. For some reason, I can think through it. Any help appreciated.

import ui

class CircularView(ui.View):
    def __init__(self):
        pass

    def draw(self):
        oval = ui.Path.oval(0,0, self.width, self.height)
        rect = ui.Path.rect(0,0, self.width, self.height)
        #rect.append_path(oval)
        ui.Path.add_clip(oval)
        ui.set_color('white')
        oval.fill()


if __name__ == '__main__':
    cv = CircularView()
    cv.present('sheet')
omz

Something like this should work as your draw method:

def draw(self):
    oval = ui.Path.oval(*self.bounds)
    rect = ui.Path.rect(*self.bounds)
    oval.append_path(rect)
    oval.eo_fill_rule = True
    oval.add_clip()
    ui.set_color('white')
    rect.fill()
Tizzy

UPDATED: WORKS IN PYTHON 2 AND 3 ->
If what you're looking for is a cropped round image, this works


# coding: utf-8

#these are fillers for phhton 3 changes
try:
    import cStringIO
    cStringIO.BytesIO = cStringIO.StringIO
    print("old cString")
    import urllib2
except ImportError:
    import io as cStringIO
    #from io import StringIO as cStringIO
    import urllib.request as urllib2
    print("new stringIO")

#ALSO WORKS INSTEAD OF TRY EXCEPT ABOVE FOR PYTHON 2 AND 3
#import io as cStringIO
#import urllib2

import ui
from PIL import Image, ImageOps, ImageDraw
import io

def grabImageFromURL(url):
    url=url
    #load image from url and show it

    imageDataFromURL = urllib2.urlopen(url).read()
    print("imageData from URL of Type: "+str(type(imageDataFromURL)))

    return imageDataFromURL


def circleMaskViewFromImageData(imageData):
    imageDataFromURL=imageData
    #load image from url and show it

    #imageDataFromURL = urllib2.urlopen(url).read()

    file=cStringIO.BytesIO(imageDataFromURL)
    img = Image.open(file)
    #img = io.open(file)

    #begin mask creation
    bigsize = (img.size[0] * 3, img.size[1] * 3)
    mask = Image.new('L', bigsize, 0)
    draw = ImageDraw.Draw(mask) 
    draw.ellipse((0, 0) + bigsize, fill=255)
    mask = mask.resize(img.size, Image.ANTIALIAS)

    img.putalpha(mask)

    #show final masked image
    #img.show()
    img=pil2ui(img)

    return img


def circleMaskViewFromURL(url):
    url = url
    imageData = grabImageFromURL(url)
    maskedImage = circleMaskViewFromImageData(imageData)

    return maskedImage


# pil <=> ui
def pil2ui(imgIn):
    with io.BytesIO() as bIO:
        imgIn.save(bIO, 'PNG')
        imgOut = ui.Image.from_data(bIO.getvalue())
    del bIO
    return imgOut

def wrapper(func, *args, **kwargs):
    #console.clear()
    def wrapped():
        return func(*args, **kwargs)

    return wrapped




if __name__=="__main__":


    testURL = "http://vignette2.wikia.nocookie.net/jamesbond/images/3/31/Vesper_Lynd_(Eva_Green)_-_Profile.jpg/revision/latest?cb=20130506215331"

    #____TIMING TEST__________
    #testImage = grabImageFromURL(testURL)
    #wrapped = wrapper(circleMaskViewFromImageData, testImage)
    #b= timeit.Timer(wrapped).timeit(number=1000)
    #print(b)
    #_____END TIMING TEST______



    circleMaskViewFromURL(testURL).show()




Phuket2

@omz , thanks. Works great. I searched the forum, could not find a reference to it. So surprised. I would have thought a lot would have been asking about this.

Phuket2

@Tizzy , hey thanks for your solution. I was actually looking for what @omz suggested. Hope you didn't waste your time.
But the above also maybe helpful for you as its light weight.
Once I make it into something reusable,I will post share

dgelessus

@Tizzy FYI, io.StringIO exists on Python 2 and 3, and urllib3 is a third-party (not Python standard library) module, so it also exists on both versions. (The Python 3 version of urllib2 is called urllib.) And if you do from PIL import Image, you don't need to do import Image afterwards.

Tizzy

@Phuket2 No worries had this lying around.

@dgelessus thanks for the tip. Yeah I think I imported image separately because at one point it wasn't working..I'm not sure why (I don't remember) but maybe one of the betas? Goes to show my modus operandi - smack it with a hammer until it works. I'll go ahead and take it out. The reason I didn't do just io.StringIO is because I was under the impressions that cStringIO is faster, at least when in Python 2 - please correct me if I'm wrong.

I've amended the script above.

I thought I had this working in Python 3 but I guess not! As it currently stands in pythonista 3 I get the error TypeError: initial_value must be str or None, not bytes at line 23 where file=cStringIO.StringIO(urllib2.urlopen(url).read())

(Keep in mind I did import io as cStringIO and import urllib.request as urllib2 for when it's in Python3 mode )

dgelessus

If you're dealing with Python 3 compatibility, you need to be very careful about what "string" types you're using. In Python 2, default str is a byte string and unicode is (almost) full Unicode; in Python 3, default str is full Unicode and bytes is obviously a byte string. (In Python 2, bytes is a valid alternate name for str.)

In this case it looks like urllib2.urlopen(url).read() returns bytes, and you're feeding it into a StringIO, which expects a text string. Python 2 is sloppy with the distinction and force-decodes bytes/str using UTF-8 into unicode, so everything works fine there. In Python 3, no automatic conversion happens, and StringIO complains because you gave it bytes instead of a Unicode str.

How to solve this:

  1. Simply import io no matter what Python version you use. Then io.StringIO works with Unicode strings, and io.BytesIO with byte strings. (Also when working with local files, use io.open rather than open. On Python 2 this allows proper use of unicode, and on Python 3 open == io.open.)
  2. In this case, always use bytes, as you're working with binary image data. Use the name bytes instead of str, BytesIO instead of StringIO, and if you need to use literals, write b"..." instead of "...". (This of course only applies to cases where you want to use binary data. When working with text, you should use Unicode when possible.)
  3. When writing scripts that should work on Python 2 and 3, start in Python 3. There you will get errors when you mix up bytes and str by accident, instead of "magical" conversion like in Python 2. Once your code works on Python 3, try to run it on Python 2 and fix what doesn't work.
Tizzy

@dgelessus Thanks so much for imparting your knowledge on me. I've edited the above script such that the code is the same, but the namespace changes based on if it's python 2 or 3 (there's probably a reason this is not recommended but i set cStringIO.BytesIO = cStringIO.StringIO for the python 2 case, and it works in both now. ) I also broke out the getting an image from url and masking it parts into separate functions.

  1. you mention io.open instead of open .... do you mean instead of Image.open()? Because that seems to be different? or did you mean in general, and not in this example?

  2. about bytes - if i do print(type(bytes(imageDataFromURL))) it still prints out as <type "str"> in python2.

Finally, I decided to test cStringIO.StringIO() vs io.BytesIO and Python2 vs 3 by using timeit.Timer().timeit(number=1000) for 1000 operations each. (Done on an iPhone 6s +) Here are the results:

Pythonista 2 using cStringIO.StringIO():
396.739

Pythonista 2 using io.BytesIO:
397.933
397.154

Pythonista 3 using io.BytesIO():
383.361

As you can see the difference between cStringIO.StringIO() and io.BytesIO() seems to be negligible.

Pythonista 3 w/ io.BytesIO seems to outperform Pythonista 2 and io.BytesIO by average of about .0133 seconds per run which is a small but I believe notable gain especially if you do this operation over and over again.

So, in conclusion, there doesn't seem to be any reason to use cStringIO.StringIO() over io.BytesIO() in python2.
I'll post the simplified code to reflect that conclusion below.

Tizzy

# coding: utf-8
#import time
import ui
import io
from PIL import Image, ImageOps, ImageDraw

#Try except for python2 vs 3
try:
    import urllib2
    print("pyth2")
except ImportError:
    import urllib.request as urllib2
    print("pyth3")

def grabImageFromURL(url):
    url=url
    #load image from url and show it

    imageDataFromURL = urllib2.urlopen(url).read()
    print("imageData from URL of Type: ",type(bytes(imageDataFromURL)))

    return imageDataFromURL


def circleMaskViewFromImageData(imageData):
    imageDataFromURL=imageData
    #imageDataFromURL = urllib2.urlopen(url).read()
    #broken out into separate function ^^^

    file=io.BytesIO(imageDataFromURL)
    img = Image.open(file)
    #img = io.open(file)  ????

    #begin mask creation
    bigsize = (img.size[0] * 3, img.size[1] * 3)
    mask = Image.new('L', bigsize, 0)
    draw = ImageDraw.Draw(mask) 
    draw.ellipse((0, 0) + bigsize, fill=255)
    mask = mask.resize(img.size, Image.ANTIALIAS)

    img.putalpha(mask)

    #show final masked image
    #img.show()
    img=pil2ui(img)

    return img


def circleMaskViewFromURL(url):
    url = url
    imageData = grabImageFromURL(url)
    maskedImage = circleMaskViewFromImageData(imageData)

    return maskedImage


def pil2ui(imgIn):
#pil image to ui image
    with io.BytesIO() as bIO:
        imgIn.save(bIO, 'PNG')
        imgOut = ui.Image.from_data(bIO.getvalue())
    del bIO
    return imgOut

def wrapper(func, *args, **kwargs):
#wrapper function for timing with parameters
    def wrapped():
        return func(*args, **kwargs)

    return wrapped




if __name__=="__main__":


    testURL = "http://vignette2.wikia.nocookie.net/jamesbond/images/3/31/Vesper_Lynd_(Eva_Green)_-_Profile.jpg/revision/latest?cb=20130506215331"

    #____TIMING TEST__________
    #testImage = grabImageFromURL(testURL)
    #wrapped = wrapper(circleMaskViewFromImageData, testImage)
    #b= timeit.Timer(wrapped).timeit(number=1000)
    #print(b)
    #_____END TIMING TEST______



    circleMaskViewFromURL(testURL).show()


dgelessus

Forget that with io.open for now - that is for reading local files (I forgot for a moment that you read the data from a URL, not a file). io.open is Python 3's open function backported to Python 2, which means that (among other things) Unicode is handled correctly.

On Python 2, there is no dedicated bytes type. Because str is a bytestring in Python 2, bytes is simply an alias for str. In pseudo-Python code:

class str(object):
    ...

bytes = str
mikael

For reference, for anyone searching for this topic, why was this not done with corner_radius?