4
\$\begingroup\$

Hi all!

I want to put some screen activity into gif, I achieved this, but it take a lot of time.

How I do it: I want to record gif 100x100 with 25 frames:

1) I take 25 screenshots: In render method I set scalingViewport 100x100, then I take screenshots:

    gl.glPixelStorei(GL20.GL_PACK_ALIGNMENT, 1);
    final Pixmap pixmap = new Pixmap(width, height, Pixmap.Format.RGBA8888);
    ByteBuffer pixels = pixmap.getPixels();
    byte[] bytes = new byte[pixels.remaining()];
    gl.glReadPixels(0, 0, width, height, GL20.GL_RGBA, GL20.GL_UNSIGNED_BYTE, pixels);
    pixels.get(bytes);

    int bytesSize = bytes.length;
    int[] arrayWitoutAlpha = new int[bytesSize/4];
    int arrayPos = 0;
    for(int i = 0; i < bytesSize; i+=4) {
        arrayWitoutAlpha[arrayPos] = ((bytes[i]&0x0ff) << 16) | ((bytes[i+1]&0x0ff) << 8) | (bytes[i+2]&0x0ff);
        arrayPos++;
    }
    return arrayWitoutAlpha;

and add this int arrays into one two-dimensional int array. Taking 25 screenshots 100x100 takes about 500ms.

2) After that I combine screenshots with different gif encoders (like this and others). But sometimes frame can be added in encoder in 50ms, or sometimes it can be added in 1000ms, and gif with that size can be generated for 1500ms, or can be generated for 30 seconds or more.

Example of code how i generate gif with gif encoder from Square:

        GifEncoder encoder = new GifEncoder(Gdx.files.external("testgif.gif").write(false),
                gifWidth, gifHeight, 1);
        ImageOptions options = new ImageOptions();
        for (int i = 0; i < framesArray.length; i++) { //framesArray - two-dimentional int array with frames
            encoder.addImage(framesArray[i], gifWidth, options);
        }
        encoder.finishEncoding();

I think even 1500ms for this little gif it is very long. I would be very grateful to that who knows how to deal with it. Thanks in advance!

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

There's a few important notes I want to mention first.

OpenGL's origin is the bottom left corner. While Pixmap's origin is at the top left corner. Thus after calling glReadPixels() you have to flip the Pixmap on its y-axis.

Pixmap pixmap = ...

int width = pixmap.getWidth(), height = pixmap.getHeight();
Pixmap flipped = new Pixmap(width, height, pixmap.getFormat());

for (int x = 0; x < width; ++x)
    for (int y = 0; y < height; ++y)
        flipped.drawPixel(x, y, pixmap.getPixel(x, height - y - 1));

pixmap.dispose();
pixmap = flipped;

Next it should be noted that Pixmap's internal format is RGBA while GifEncoder expects a format of ARGB. Which can be changed by doing:

int pixel = pixmap.getPixel(x, y); // RGBA

int r = (pixel >> 24) & 0xFF;
int g = (pixel >> 16) & 0xFF;
int b = (pixel >> 8) & 0xFF;
int a = pixel & 0xFF;

pixel = (a << 24) + (r << 16) + (g << 8) + b; // ARGB

How to record a GIF with LibGDX

So using GifEncoder. The change I would make to your code, is that I would keep List<Pixmap> frames instead. So for capturing the frame I would in total do:

Pixmap pixmap = new Pixmap(width, height, Pixmap.Format.RGBA8888);
ByteBuffer pixels = pixmap.getPixels();
gl.glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
frames.add(pixmap);

The key is to do the least amount of work while capturing the frame. While later utilizing multithreading to export the GIF.

Then when you have captured enough frames, you can go ahead and save the GIF by doing:

// Note that if loopCount is less than 1, then the gif will loop indefinitely
GifEncoder encoder = new GifEncoder(..., width, height, 0);

ImageOptions options = new ImageOptions();

// Frame rate / Time between each frame
options.setDelay(50, TimeUnit.MILLISECONDS);

for (Pixmap pixmap : frames) {
    int width = pixmap.getWidth(), height = pixmap.getHeight();
    int[][] pixels = new int[height][width];

    ... flip pixmap on the y-axis ...

    for (int x = 0; x < width; ++x) {
        for (int y = 0; y < height; ++y) {
            int pixel = pixmap.getPixel(x, y);

            ... change pixel format ...

            pixels[y][x] = pixel;
        }
    }

    encoder.addImage(pixels, options);
}

encoder.finishEncoding();

To do so that you won't stall the rest of application while saving the GIF. You could do all the saving in a separate thread.

new Thread(new Runnable() {
    @Override
    public void run() {
        ... save gif ...
    }
}).start();

Saving will still take time, as each frame will have to be encoded and written to the file. However multithreading makes so the user wouldn't be stalled and annoyed by the process.

Last but not least, remember to dispose all those Pixmaps.

for (Pixmap pixmap : frames)
    pixmap.dispose();
frames.clear();

If needed, you can also save a Pixmap to a PNG by doing:

PixmapIO.writePNG(fileHandle, pixmap);

Additionally you can get rid of all those GL20., by statically importing GL20.

import static com.badlogic.gdx.graphics.GL20.*;
\$\endgroup\$
0
\$\begingroup\$

Use options.setDelay(delay, TimeUnit.MILLISECONDS); where delay is your time between frames to set how long the images should show.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .