Circle view for ui
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 , 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.
@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
io.StringIOexists on Python 2 and 3, and
urllib3is a third-party (not Python standard library) module, so it also exists on both versions. (The Python 3 version of
urllib.) And if you do
from PIL import Image, you don't need to do
@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.StringIOis because I was under the impressions that
cStringIOis 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 bytesat line 23 where
(Keep in mind I did
import io as cStringIOand
import urllib.request as urllib2for when it's in Python3 mode )
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
stris a byte string and
unicodeis (almost) full Unicode; in Python 3, default
stris full Unicode and
bytesis obviously a byte string. (In Python 2,
bytesis a valid alternate name for
In this case it looks like
bytes, and you're feeding it into a
StringIO, which expects a text string. Python 2 is sloppy with the distinction and force-decodes
strusing UTF-8 into
unicode, so everything works fine there. In Python 3, no automatic conversion happens, and
StringIOcomplains because you gave it
bytesinstead of a Unicode
How to solve this:
import iono matter what Python version you use. Then
io.StringIOworks with Unicode strings, and
io.BytesIOwith byte strings. (Also when working with local files, use
open. On Python 2 this allows proper use of
unicode, and on Python 3
open == io.open.)
- In this case, always use
bytes, as you're working with binary image data. Use the name
StringIO, and if you need to use literals, write
"...". (This of course only applies to cases where you want to use binary data. When working with text, you should use Unicode when possible.)
- When writing scripts that should work on Python 2 and 3, start in Python 3. There you will get errors when you mix up
strby 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.
@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.StringIOfor 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.
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?
about bytes - if i do
print(type(bytes(imageDataFromURL)))it still prints out as
<type "str">in python2.
Finally, I decided to test
io.BytesIOand 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():
Pythonista 2 using io.BytesIO:
Pythonista 3 using io.BytesIO():
As you can see the difference between
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.
# 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 * 3, img.size * 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()
Forget that with
io.openfor now - that is for reading local files (I forgot for a moment that you read the data from a URL, not a file).
io.openis Python 3's
openfunction backported to Python 2, which means that (among other things) Unicode is handled correctly.
On Python 2, there is no dedicated
stris a bytestring in Python 2,
bytesis simply an alias for
str. In pseudo-Python code:
class str(object): ... bytes = str
For reference, for anyone searching for this topic, why was this not done with