Easily make a swipe-able image carousel

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.

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

@Simeon thanks and thanks! I’ve updated the .zip.

Any insight as to why swiping right on the first image looks so weird?

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

@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.

@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

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.

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

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

@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.

@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

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


@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.

@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

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

@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

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

@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…

@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

@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…