Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Easily make a swipe-able image carousel

edited April 27 in Code Sharing Posts: 972

From this thread: https://codea.io/talk/discussion/7472/carousel

...I’ve modified the version made by @Dwins to easily display images.

Now you can just feed it a table of image names (or asset descriptors) and it will make a nice swipe-able carousel out of them!

With page-indicator dots and everything!

In theory it can accommodate any number of images, though after fifteen ten or so the page-indicator dots might get messed up.

There’s one thing I can’t iron out: when you swipe right on the very first screen, the new image that should be sliding in from the left doesn’t display until the whole page-switching animation is complete.

If any of you fine folks can give me pointers on how to fix that I’d be much obliged.

Tagged:

Comments

  • SimeonSimeon Admin Mod
    Posts: 5,587

    That's pretty cool! Just a note that the print(self.images[i]) statement inside the draw() function can make the carousel feel slow as the images array becomes more populated, because it's printing out every image path multiple times per second

  • Posts: 972
    @Simeon thanks and thanks! I’ve updated the .zip.

    Any insight as to why swiping right on the first image looks so weird?
  • Posts: 122

    i tried looking through this but there's too many things going on that i can't follow, some math in places and i'm left wondering what does this do etc

    but my initial thoughts are that when you loop through the images and draw them, you're not including any "fake" images before the first image, so when you swipe right from the first position there's no image to the left that is drawn, only once the touch ends does the logic seem to fit the ending image into the draw, so you need a way to move the end image all the way to the left of the first image when you detect a right swipe from the first position and vice vera for the last image when going around to the first image

  • Posts: 972

    @skar tbh I don’t know what a lot of the code does either! I just took Dwins’s code and fiddled with it until it did what I want. That’s kind of why I was hoping someone could help me figure this out.

  • Posts: 860

    @UberGoober here is an updated one - I bookended the image table with copies of the first and last then modified the code limits to wrap around later/earlier to fake it. There probably is a more elegant solution, but this should give you some pointers

  • Posts: 2,118

    All I had an idea for a scrolling carousel when I started with Codea, together with a series of effects in transposing one image over another. Got the interest back from seeing this and will dig out the old code once I’ve finished my latest project. Thanks for the idea again.

  • Posts: 972

    @West interesting, and when I swipe right now there’s an odd stuttering effect, does that happen for you?

  • Posts: 860

    Ah yes - my mistake. Good eyes!. Lines 115 and 119 would be -2 not -1 as there are two extra images. This should fix it

  • edited April 27 Posts: 972
    @West I think you helped me understand the code enough to implement the more elegant fix you speculated about.

    The code draws all the existing images in one long strip, and then translates the drawing area to match the current page number, so I just drew a copy of the first image at the end of that strip and a copy of the last image before the start of that strip, and since the code already swaps the page count once you wrap around the start or end of the image table, when a transition of that kind is finished animating the page-count-based-translation becomes correct again.

    Now, ideally, the code shouldn’t draw all of the images every single time, but instead just draw the current, previous, and next image, but I haven’t figured that out yet.
  • Posts: 860

    @UberGoober changing line 186 to the following will literally only draw the image of interest plus its two neighbours


    if self.images[i] ~= nil and math.abs(i-self.page)<2 then

    You could also put in if statement checks on self.page to control whether the first/last image is also drawn (currently they always are). Not sure if this makes much of a performance impact though

  • Posts: 860

    You could also swap the circles for image previews for a different preview. You could also look at a tap to focus on that particular image function too


    if i == self.page then tint(255,200) sprite(self.images[i],WIDTH/2-s+i*spacingConstant,75,radius*1.5,radius*1.5) else tint(255,50) sprite(self.images[i],WIDTH/2-s+i*spacingConstant,75,radius*0.9,radius*0.9) end
  • Posts: 972

    @West I think a tap on the circles already focuses on the corresponding image. I’m not sure how because I still don’t get all the code.

  • Posts: 972

    @West line 186?

    At any rate I think this does it, with no extraneous drawing:


    --image drawing sequence (only draws the current, previous, and next image) for i,v in ipairs(self.images) do if self.images[i] ~= nil and math.abs(i-self.page)<2 then sprite(self.images[i],i*WIDTH-WIDTH/2,HEIGHT/2, WIDTH, HEIGHT) end --special cases for beginning and end of image table if i == 1 then sprite(self.images[#self.images], -WIDTH/2, HEIGHT/2, WIDTH, HEIGHT) elseif i == #self.images then sprite(self.images[1], ((#self.images + 1) * WIDTH)-WIDTH/2, HEIGHT/2, WIDTH, HEIGHT) end end
  • Posts: 972

    @West hmmm tapping the dots only kind of works for some of them.

  • Posts: 860

    @UberGoober yes, that’s what I meant (though I poorly explained it). You’re right about the tapping to select a picture - here is the fixed code - previous iteration had the sizes hard coded to a set number of dots I think


    if touch.state == ENDED and touch.tapCount > 0 then for i = 1,#self.images do local radius = WIDTH / #self.images / 1.5 local spacingConstant = radius * 1.25 local s = #self.images*spacingConstant/2 local x,y = WIDTH/2-s+i*50,75 local v = vec2(touch.x,touch.y)-vec2(x,y) if v:len() < radius then self.page = i end end end
  • Posts: 122

    this line is strange
    local radius = WIDTH / #self.images / 1.5

    it's based on the screen width and number of images, which i guess makes sense for larger numbers of images, but try it with only 2, and the dots are huge

  • Posts: 972

    @skar, yes, it needs to have an upper limit on size, I couldn’t off the top of my head figure out what it should be.

    @West, @skar, I mean, if you guys are into it, we could go one more ridiculous step and somehow mask the preview images to fit inside the circles!

    A quick search for “mask” comes up with a small code snippet from @Jmv38 at the bottom of this post: https://codea.io/talk/discussion/3842/how-to-efficiently-erase-pixels-from-an-image. ...but I’d have to pick at it a bunch to get how it works...

  • Posts: 860

    @UberGoober or there is this one which is good to go using meshes by @JakAttak: just replace the camera with a readimage

    https://codea.io/talk/discussion/4993/camera-image-on-a-circle

  • Posts: 860

    @skar this will clamp the max size of the circles:

        local radius = math.min(WIDTH / #self.images / 1.5,WIDTH/20)
    

    Not convinced that doesn’t break the individual dot selection function though...

  • Posts: 972
    @West , @skar

    Here’s my latest, it uses @JakAttak’s circle mesh as suggested to make all the dots into preview images.

    It also uses the radius-size-clamping code as suggested, but tweaked a little to make the dots smaller.

    It’s got some problems: the dots are off-center now, I don’t know why, and the dot tapping is super broken.
  • Posts: 122

    there’s a problem in this project that the “x” position is not correct
    141: local x,y = WIDTH/2-s+i*50,75 idk what the math here is supposed to be but it’s not the correct positions of the dots

    so i’ve been thinking a lot about touch and how it works
    basically the x,y is tied to the screen, so you have to figure out if your object that you want to respond to touch falls into the x,y and having to loop through every object can get huge since it’s O(n)

    so one thing i was thinking that would be unconventional but probably way easier is to add a circle physics body that is just a sensor behind every dot and then do a raycast on touch, i think that’s much easier and less performance intensive, i’m going to try it eventually but maybe you guys can try it out of it have the time

  • edited April 28 Posts: 972

    I don’t understand how the fancy sizing thing works so I tried to do it my own dumb way and it doesn’t work either:


    local sidePadding = WIDTH / 20 local totalSpaceForDots = WIDTH - (sidePadding * 2) local spacePerDot = totalSpaceForDots / #self.images local calculatedSize = spacePerDot * 0.85 local maxDotSize = WIDTH / 30 if calculatedSize < maxDotSize then dotRadius = calculatedSize else dotRadius = maxDotSize end dotRadius = dotRadius * 0.75 --ugh why do I even need this -- but withkut it the dots are way too big

    Without the multiplier at the end the dots are way too big and I don’t know why.

    About the dot-tapping, the code as is worked with the default dots in @Dwins’s code, and in fact it still works, it’s just that the tappable areas are not aligned with the dots correctly. So rather than implementing an entirely new system I’d like to just figure out how to re-align the tap spots.

    The problem is I just have no idea where in the touched(touch) function it’s detecting those taps. Honestly almost all the touched(touch) function is way beyond my ability to decipher. I can tell it must be in the part for touch.state == ENDED, but not much more than that.

  • edited April 28 Posts: 972
    @West , @skar

    I think I got the dot-tapping working.

    The only two things bugging me now are the size calculations and the fuzziness of the white circle around the current page—why is it so fuzzy?

    Also I made a new icon because the old one didn’t show our nice new preview dots.

    Also also the Carousel class has been renamed ImageCarousel, and moved to a separate tab so it can be used as a dependency if desired.
  • Posts: 860

    @UberGoober Here is a cleaned up version which now (hopefully) clarifies the size calculations of the dots and also allows you to limit the span of the thumbnail.

    I’m not sure what you mean about the fuzzyness- is it the slight antialiasing effect on the circle?

    I’ve also tidied up the touch code from main - not sure what was going on there before but when the main touch function is called it is just a case of calling the carousel touch code.

  • edited April 28 Posts: 972
    @West, that’s great work! Your clarification is very helpful for a slow-at-math type like me.

    I made a few aesthetic tweaks and a bug fix and one feature addition.

    * the feature is an optional Boolean used when initializing the carousel that tells it whether or not to use thumbnails in the dots. Useful when there are too many dots and they’re very small, in which case the whole effect is ruined, or when you just don’t want thumbnails for whatever reason.

    * the bug fix is a tweak to the calculation of _s_ in the dot-drawing section. You’d done an efficient simplification of the calculation but I think you removed a slight adjustment that resulted in them looking off-center at some sizes. I don’t think this was a mistake on your part, because it wasn’t noticeable that all until I started adjusting the sizes of the dots. It wouldn’t even have seemed like a bug before then.

    * when not using thumbnails, the maximum dot size is dramatically smaller, more in keeping with their general size in carousels

    * when using thumbnails, I made the dot size, the overall dot space width, and the maximum dot size larger, so it’s easier to see the images on them.

    * otoh I made the selected-dot size smaller, because when using thumbnails I think the combination of the white outline and the size bump makes it easy enough to tell which one is selected, and when not using them it looks odd for the size to bump up too dramatically.

    * speaking of which I slightly narrowed the white outline. I would have preferred to make it thinner in landscape, but that makes it too thin in portrait, so I think I chose a happy medium.

    * I modified the version numbering in the title to follow the “major update/minor update” tradition ;)


    ...oh and yes, by “fuzziness” I meant the anti-aliasing. I wish the ellipses were crisper, but it’s not a big deal.
  • Posts: 972

    @West, @skar :

    I think this is a cool addition but I can only get it working 90%.

    I thought I would be neat you could have multiple image carousels in different places on screen.

    As you’ll see, it’s working, but with one teeny weeny massive problem.

  • Posts: 860

    @Ubergoober I don’t think multiple instances will work out of the box - and it’s not a simple fix. There are issues if you start allowing the individual images to have different widths and heights. I would probably look at an alternative way of rendering the image - probably be using image copy with x percentage of one image on 100%-x% of the neighbouring image. I think it’s probably run its course at the current feature set.

  • Posts: 972

    @West have you run the project I attached?

    It seems to work swimmingly except for the issue of drawing the images to the left and right.

    If I could hide the "off-screen" images, I think it would be working perfectly, or close to it.

  • Posts: 860

    @UberGoober Yes - and there are a few issues with it. On iPad the circle thumbnails are bunched, the current image thumbnail isn’t scaled up (caused by non-square images). Touching the thumbnail bar no longer works. There is bleed across to the full strip (as you’ve mentioned). Bottom carousel only responds to touches on certain parts of it.

    Happily resolving the offscreen images could be simply solved by using the clip command. Here is a simple example of it in action:


    function setup() parameter.number("splitpoint",0,1,0.5) srcimg1=readImage(asset.builtin.Blocks.Brick_Grey) srcimg2=readImage(asset.builtin.Blocks.Cactus_Inside) end function draw() background(57, 9, 252) strokeWidth(5) destsize=400 clip(WIDTH/2-destsize/2,HEIGHT/2-destsize/2,destsize,destsize) sprite(srcimg1,WIDTH/2-splitpoint*destsize,HEIGHT/2,destsize,destsize) sprite(srcimg2,WIDTH/2-splitpoint*destsize+destsize,HEIGHT/2,destsize,destsize) clip() end
  • Posts: 860

    And I looked at the image copy approach that I’d suggested previously- not going to be a viable solution as you run into problems with being restricted to whole pixels which leads to non-smooth transitions

  • dave1707dave1707 Mod
    Posts: 9,109

    @West You can get at the half pixels using rawget and rawset . Of course that only works for images that have the half pixels.

  • Posts: 860

    @dave1707 not come across those commands before - thanks. Was exploring the image copy function to grab the left/right hand portions of two images, but it didn’t really work out - pasting two images and clipping is much more straightforward (and closer to the original approach)

  • dave1707dave1707 Mod
    Posts: 9,109

    @West Are you just trying to resize an image. Not clear by reading all the above discussions.

  • edited April 30 Posts: 972

    @West This version has four carousels, of differing sizes and positions, and as far as I can tell they all work great.

    I think even the dots work pretty good now, and you can also now configure a carousel to have no dots at all.

    I think you’re right that there’s not much more to do with this, but it does seem to me that this last feature is working just fine.

  • Posts: 860

    @UberGoober looks good. I think there is a difference in expectations though. I was thinking a set thumbnails would be associated with each carousel regardless of size and position whereas you have constrained it to only if the carousel is the width of the screen (regardless of starting x position) and the thumbnails will appear in same position regardless of the number of valid instances.

    For example having two full width carousels stacked vertically both full width.


    imgCarousel = ImageCarousel(images, true,0,0,WIDTH,HEIGHT/4) imgCarousel2 = ImageCarousel(images2, true,0,HEIGHT*0.7,WIDTH,HEIGHT/4)

    Thumbnails are wrong for one of the carousels.

    The "only show thumbnails if full width" constraint seems excessively strict - what happens if I wanted to use this at nearly full width - say 98% but have a small surrounding border?

  • Posts: 972

    @West I updated my previous post a little while after first posting. I think I figured out the whole dots problem anyway and you can now have dots or not no matter the size or position of the carousel. The link above should download this version now, if it doesn’t please let me know.

  • edited April 30 Posts: 860

    @UberGoober the version you posted above (1.5) will always draw the thumbnails in the same y position, 75 pixels up from the bottom of the screen and they will always be in the spread around the centre of the screen (WIDTH/2). If you’ve changed this, then these changes haven't shown in the code you posted.


    translate(WIDTH/2-s+i*spacingConstant,75)
  • edited April 30 Posts: 972

    @West you’re right, it didn’t update for some reason.

    I just edited it, re-attached the up-to-date version, and then downloaded it from that post again to be sure it’s the right one.

    It should be right now. Also it now draws non-thumbnail dots at a consistent size no matter what size the carousel is, to more closely match standard iOS behavior.

  • Posts: 860

    @UberGoober works great now! Good job!

  • Posts: 972

    Thank you @West, and thanks for all your help, it made all the difference.

    And also thanks to @Dwins for the amazingly efficient code that this was built on top of.

  • Posts: 84

    Fantastic piece of work!

    This has now been added to Codea Community Repo.

Sign In or Register to comment.