Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Cover Flow view
-
Maybe this is useless code. But could spark some ideas
# coding: utf-8 import ui import math def pic_ratios(num_items): pi = math.pi divisor = (100. / num_items) / 100. #print divisor , '*' for i in range(1,num_items ): s = math.sin(pi * (i * divisor)) cs = math.cos(s) print s if __name__ == '__main__': f = (-100,142,100, 100) # even param , you will get 1.0 pic_ratios(24)
-
keep in mind you basically need 3 equations: one for size, one for x and one for y. assuming we like the original proportions,
first, note that the width varies as:
w=370*exp(-2.4*(abs((i-N/2.0-0.5)/N)**(1.38/N**0.25))
where i is the index of the cover. i have written this in a way that scales with number of covers N, still leaving the same min width, but also trying to keep a prominent center frame.
next, the relationship between center and width from the original is roughly
w=370.*exp(-abs(xc-512)/470.)
so, solving for the center,
xc=512+copysign(470.*log(w/370.),i-N/2)
where exp, log, and copysign are all imports from math. This only works properly with an odd number, so increase N by 1 if N is even.
finally,
yc= (192-w)
All of the large numbers (512,470,370,192) porbably should be expressed as a fraction of screen width (divide by 1024 and multiply by screen width). Also, everything above assumes future division. Also, note this solves for xc and yc, i.e center positions. Use the center property, or else subtract 0.5*w to get x and y.
-
@JonB , that's great. Only if I was 100 years younger and could go back to school and actually learn something.
Would be fantastic to be able to express things like this. I suspect it didn't take much effort on your part. Would love to be able to do the same. But thanks for sharing -
@JonB, Thanks a ton! I'll try it. Probably will end up making the numbers a function of screen size. Is the 192 half screen height?
-
@Webmaster4o , look fwd to seeing the revised code. Maybe think to make some of the variables constants so can change the look and feel of the coverflow without changing the methods directly.i think a great candidate for the pythonistia tools repo once you nail it.
-
192 is actually related to the 512, and the amount that things move vertically. I think you could sart with having it scale only by width, and see how that does.
-
Ok, I'm ready to start implementation now.
-
Please make it a Github repo so that we can add Pythonista-Tools link to it.
-
Sure, @ccc I'll do that when I'm finished with this bit.
-
@JonB None of the numbers besides 512 make sense to me as logical fractions of 1024. Where did they come from?
Additionally, the sentence
divide by 1024 and multiply by screen width
is confusing to me, because 1024 IS the screen width...
x/1024*1024 = x
Also, if it's not a lot of work, could you try to explain the math behind your equations? I don't like to borrow code I don't understand, it's a mini-policy of mine. (No, I don't understand all the code behind all the modules I import, I mean I don't like to insert others' code into my own without understanding it)
-
Yes you correct re screen width... my point is if you want this to scale for a different screen width, like say 768 in portrait, you would divide by 1024, and multiply by screen width. in retrospect, you could just scale the final numbers.
when i plotted width vs index, and noticed it looked a lot like an exponential decay about the center.. That gives an exp(-abs(i-N/2)) form. The leading factor is the width of the central frame. To get the width at the end, i played with the factor and exponent inside the exponential. Then, i experimented with scaling for more frames, keeping the center width and end width the same. I thought you would still want a big difference betwenn center and next frames, so i played with the exponent.
Likewise, i plotted xc vs w, and saw a similar exponential factor, and again some trial and error. Y vs w appeared very linear, so i just fit a line.
-
Spent today working on image2ASCII, will get to this tomorrow.
-
I get the feeling I'm using your code wrong. What am I doing that isn't as intended? some of your sentences don't make a lot of sense to me:
the relationship between center and width from the original
I've implemented your code as follows:
#Make frames for all views N = 9 frames = [] for i in range(N): #Thanks to @JonB for this code. w=370*exp(-2.4*(abs((i-N/2.0-0.5)/N)**(1.38/N**0.25))) xc=512+copysign(470.*log(w/370.),i-N/2) yc= (192-w) frames.append((xc+.5*w, yc+.5*w, w, w))
Looks pretty good for the first half, the rest are messed up. I feel stupid, but if you could please correct my mistakes that'd be wonderful. (>ლ)
-
Sorry, I think I made an off-by-one error in the width equation. The
i-N/2.0-0.5
should bei-N/2.0+0.5
.Also, I think you want
frames.append((xc-0.5*w, yc-0.5*w,w,w))
instead of +0.5 in both places -
Thanks! It actually needed to be
(xc-0.5*w, yc+0.5*w, w, w)
in order for it to work correctly. -
by the way, one of the nice things re: the equation is that you can use touch_moved to create continuous motion along the curve. Basically you would associate each image with a index variable, touch_moved generates a fractional index offset added to each one, say, the x part of the motion divided by the center diff, and wrapping around negative values. still use a zero time animatin to sync up the motion. Then touch_ended would round to the nearest index, and do a slower animation to snap everybody in place. You could also use the "velocity" of the last swipe to over scan.
-
New code, supports different screen sizes, and auto-resizes with device rotation. Also supports arbitrary numbers of images. It will display your images whether you have 1 image or 100. Now running it will also pick random images from the included images to present.
# coding: utf-8 import ui from PIL import Image from io import BytesIO from time import sleep from math import exp, log, copysign, ceil BGCOLOR = '#0F0F0F' def pil_to_ui(img): b = BytesIO() img.save(b, "PNG") data = b.getvalue() b.close() return ui.Image.from_data(data) class CoverFlow(ui.View): def __init__(self, images): self.images = [pil_to_ui(image) for image in images] if len(self.images) < 9: self.images *= int(ceil(9./len(self.images))) self.frame = (0, 0, 1024, 768) self.oldframe = (0, 0, 1024) self.background_color = BGCOLOR #Make frames for all views N = 9 frames = [] #Create frames for i in xrange(N): #Thanks to @JonB for this code. w=370/1024.*self.width*exp(-2.4*(abs((i-N/2.0+0.5)/N)**(1.38/N**0.25))) xc=512/1024.*self.width+copysign(470.0/1024*self.width*log(w/(370.0/1024*self.width)),i-N/2) yc= (192/1024.*self.width-w) frames.append((xc-0.5*w, yc+0.5*w, w, w)) #Create subviews for the minimum of 9 images for index, frame in enumerate(frames): iv = ui.ImageView(frame=frame) iv.image = self.images[index] iv.prev_frame = iv.frame self.add_subview(iv) if index > 4: iv.send_to_back() #Handle any additional images that are provided for i in self.images[9:]: iv9 = ui.ImageView(frame=frames[-1]) iv9.image = i iv9.prev_frame = iv9.frame self.add_subview(iv9) iv9.send_to_back() def layout(self): if self.frame != self.oldframe: N = 9 frames = [] #Create frames for i in range(N): #Thanks to @JonB for this code. w=370/1024.*self.width*exp(-2.4*(abs((i-N/2.0+0.5)/N)**(1.38/N**0.25))) xc=512/1024.*self.width+copysign(470.0/1024*self.width*log(w/(370.0/1024*self.width)),i-N/2) yc= (192/1024.*self.width-w) frames.append((xc-0.5*w, yc+0.5*w, w, w)) for sv in self.subviews: self.remove_subview(sv) #Create subviews for the minimum of 9 images for index, frame in enumerate(frames): iv = ui.ImageView(frame=frame) iv.image = self.images[index] iv.prev_frame = iv.frame self.add_subview(iv) if index > 4: iv.send_to_back() #Handle any additional images that are provided for i in self.images[9:]: iv9 = ui.ImageView(frame=frames[-1]) iv9.image = i iv9.prev_frame = iv9.frame self.add_subview(iv9) iv9.send_to_back() self.oldframe = self.frame def anim(self): #Change frames len_subviews = len(self.subviews) for i, sub in enumerate(self.subviews): next = self.subviews[(i + (1 if self.touch_direction else -1)) % len_subviews] sub.frame = next.prev_frame for sub in self.subviews: sub.prev_frame = sub.frame #Resort self.images if self.touch_direction: self.images = [self.images[-1]]+self.images self.images.pop(-1) else: self.images = self.images + [self.images[0]] self.images.pop(0) #Reorder layers based on the fact that biggest images are in front #Bring smallest images to front first for s in sorted(self.subviews, key=lambda x: x.frame[3]): s.bring_to_front() def touch_began(self, touch): self.touch_direction = None def touch_moved(self, touch): if touch.location != touch.prev_location: self.touch_direction = int(touch.location.x > touch.prev_location.x) else: pass def touch_ended(self, touch): if self.touch_direction != None: ui.animate(self.anim, duration=0.25) if __name__ == '__main__': #Pick random images from default textures import os, random app_path = os.path.abspath(os.path.join(os.__file__, '../../../..')) os.chdir(app_path + '/Textures') imagenames = os.listdir(os.curdir) validnames = [] for x in imagenames: if not (x.startswith('ionicons') or x.startswith('Typicons')): validnames.append(x) images = [Image.open(random.choice(validnames)) for x in xrange(15)] view = ui.View(background_color=BGCOLOR) cf = CoverFlow(images) view.add_subview(cf) cf.present(hide_title_bar=1)
layout
literally removes all the subviews and recreates them, because I couldn't figure out how to adjust the frames easily, but the layout method takes under a hundredth of a second to perform so I don't think it's a problem. -
Realized I can cut out most of
__init__
, gist is here. I think I'm done with it, now. I'd like to maybe get rid of the objects animating sliding behind, I'll edit the gist for that. -
@Webmaster4o I would recommend making it a repo instead- as you can accept pull requests and make it easier to collaborate and build on.
-
Sure. I've also just realized that the only thing (I think) preventing it from being 1.5-compatible is the assigning of arbitrary attributes to views (
sv.prev_frame
). I should be able to eliminate this easily, then it'll work on non-beta devices.