pyvips icon indicating copy to clipboard operation
pyvips copied to clipboard

how to resize gif image and write to file or buffer

Open 2h4dl opened this issue 5 years ago • 28 comments

I have a image like below: img I want to scale it and write to buffer or file, how to do it?

2h4dl avatar Oct 31 '19 04:10 2h4dl

Hello @2h4dl, use thumbnail, it has extra logic for resizing animated images.

thumb = pyvips.Image.thumbnail("brush.gif", 128, n=-1)
thumb.write_to_file("x.gif")

To make:

x

jcupitt avatar Oct 31 '19 09:10 jcupitt

Thanks your answer @jcupitt. Still have a question.

I'm trying to read image from internet without knowing its real format, maybe gif image but named a jpg, how to read and scale or crop it?

I used pyvips.Image.new_from_buffer and pyvips.Image.thumbnail_image to read a gif, then saved it with write_to_file, a still image is given not a animated image.

Add arg n=-1 to new_from_buffer is working with gif image, but when reading other format image, like jpg, will raise an error: pyvips.error.Error: VipsForeignLoadJpegBuffer does not support argument n.

2h4dl avatar Nov 01 '19 03:11 2h4dl

Don't use thumbnail_image -- it's much slower than thumbnail.

You can open an image with new_from_file and then check vips-loader to get the file type:

>>> import pyvips
>>> x = pyvips.Image.new_from_file("k2.jpg")
>>> x.get("vips-loader")
'jpegload'

jcupitt avatar Nov 01 '19 03:11 jcupitt

Hi @jcupitt , it's all right to read an image from local disk with new_from_file. How to read a internet image. Here is my code to read image from interent data.

import requests
import pyvips

def image_format(vips_loader):
    vips_loader = vips_loader.split("_")[0]
    if vips_loader == "jpegload":
        return "jpeg"
    elif vips_loader == "pngload":
        return "png"
    elif vips_loader == "gifload":
        return "gif"
    else:
        return "unknown"


image_url = "http://www.example.com/example.jpg"
resp = requests.get(image_url)
try:
    web_image = pyvips.Image.new_from_buffer(resp.content, options="", n=-1) # for gif image
except:
    web_image = pyvips.Image.new_from_buffer(resp.content, options="") # for jpg, png

vips_loader = web_image.get("vips-loader")
image_fmt = image_format(vips_loader)
if image_fmt == "unknown":
    pass
elif image_fmt == "gif":
    # resize op
    scaled_image = pyvips.Image.thumbnail_image(web_image, width=w, height=h)
    # crop op
    # TODO
    #  unknowning how to do it
else:
    # resize op
    scaled_image = pyvips.Image.resize(web_image, scale=0.5)
    # crop
    cropped_image = Image.extract_area(scaled_image , x, y, w, h)

It looks ugly and hard to read. Any suggestion will be appreciated.

2h4dl avatar Nov 01 '19 05:11 2h4dl

new_from_buffer is very quick -- you can do it just to get the image type.

#!/usr/bin/python3

# test with:
# https://upload.wikimedia.org/wikipedia/en/b/b4/Sceicbia.jpg
# https://upload.wikimedia.org/wikipedia/commons/6/69/F4-motion.gif

import sys
import requests
import pyvips

def loader_from_buffer(buf):
    image = pyvips.Image.new_from_buffer(buf, "")
    return image.get("vips-loader").split("_")[0]

image_url = sys.argv[1]
target_width = 128
buf = requests.get(image_url).content
loader = loader_from_buffer(buf)
options = ""
if loader == "gifload" or loader == "webpload":
    # an animated format -- make a thumbnail of all frames
    options = "n=-1"
thumb = pyvips.Image.thumbnail_buffer(buf, target_width, option_string=options)
thumb.write_to_file(sys.argv[2])

jcupitt avatar Nov 01 '19 07:11 jcupitt

I need to crop specific area, extract_area is preferred, so the manual way is appropriate, also resize op. Plz tell me how to get every frame and reassemble as a new animation with pyvips, I still have no idea.

2h4dl avatar Nov 04 '19 02:11 2h4dl

There's some demo code here:

https://github.com/libvips/libvips/issues/1167#issuecomment-440598247

You'd need to run that after thumbnail.

jcupitt avatar Nov 04 '19 10:11 jcupitt

Hello @2h4dl, use thumbnail, it has extra logic for resizing animated images.

thumb = pyvips.Image.thumbnail("brush.gif", 128, n=-1)
thumb.write_to_file("x.gif")

To make:

x

Current, I use Python 3.8, but throw error

Traceback (most recent call last):
  File "resize_gif.py", line 7, in <module>
    thumb = pyvips.Image.thumbnail("brush.gif", 128, n=-1)
  File "/Users/xzh/.local/share/virtualenvs/vips-image-yHrOgoj2/lib/python3.8/site-packages/pyvips/vimage.py", line 135, in call_function
    return pyvips.Operation.call(name, *args, **kwargs)
  File "/Users/xzh/.local/share/virtualenvs/vips-image-yHrOgoj2/lib/python3.8/site-packages/pyvips/voperation.py", line 265, in call
    details = intro.details[name]
KeyError: 'n'

zhaohuxing avatar May 08 '20 10:05 zhaohuxing

Hello @zhaohuxing,

Your libvips is probably too old. Do you know what version you are using?

jcupitt avatar May 08 '20 10:05 jcupitt

Hello @zhaohuxing,

Your libvips is probably too old. Do you know what version you are using?

Thanks, but I use version 8.9.2

vips-8.9.2-Tue Apr 21 09:26:11 UTC 2020

zhaohuxing avatar May 08 '20 11:05 zhaohuxing

Ooops, sorry, it should be:

x = pyvips.Image.thumbnail("3198.gif[n=-1]", 128)

jcupitt avatar May 08 '20 12:05 jcupitt

Ooops, sorry, it should be:

x = pyvips.Image.thumbnail("3198.gif[n=-1]", 128)

Thanks, BTW, what does n=-1 mean? I call thumbnail("3198.gif", 128) without n=-1, produced gif picture is static.

zhaohuxing avatar May 08 '20 12:05 zhaohuxing

Sorry, I should not post untested code.

Here's a complete working program to thumbnail URLs, including animated images:

#!/usr/bin/python3

# test with:
# https://upload.wikimedia.org/wikipedia/en/b/b4/Sceicbia.jpg
# https://upload.wikimedia.org/wikipedia/commons/6/69/F4-motion.gif

import sys
import requests
import pyvips

def loader_from_buffer(buf):
    image = pyvips.Image.new_from_buffer(buf, "")
    return image.get("vips-loader").split("_")[0]

image_url = sys.argv[1]
target_width = 128
buf = requests.get(image_url).content
loader = loader_from_buffer(buf)
options = ""
if loader == "gifload" or loader == "webpload":
    # an animated format -- make a thumbnail of all frames
    options = "n=-1"
thumb = pyvips.Image.thumbnail_buffer(buf, target_width, option_string=options)
thumb.write_to_file(sys.argv[2])

jcupitt avatar May 08 '20 13:05 jcupitt

When you load an animated image, pyvips will just give you the first frame. You can select the number of frames to load with n= (1 meaning load 1 frame) and the first frame to load with page= (default 0, the first frame).

n=-1 means load all frames.

jcupitt avatar May 08 '20 13:05 jcupitt

@jcupitt If I did not call thumbnail function, use magick save the picture of gif format, how does resize this picture? In the background, I use bimg library, but it isn't supported gif; so I not known how to modify bimg for supported gif.

zhaohuxing avatar May 08 '20 13:05 zhaohuxing

I would use thumbnail. It has the extra logic for resizing animated images.

jcupitt avatar May 08 '20 14:05 jcupitt

@jcupitt Can you provide a complete c program?

zhaohuxing avatar May 09 '20 02:05 zhaohuxing

I had this example, it might help:

/* Compile with:
 *
 * gcc -g -Wall buffer.c `pkg-config vips --cflags --libs`
 */

#include <stdio.h>
#include <stdlib.h>

#include <vips/vips.h>

int
main( int argc, char **argv )
{
	char *buffer_in;
	size_t length_in;
	GError *error = NULL;
	VipsImage *image, *x;
	char *buffer_out;
	size_t length_out;

	if( VIPS_INIT( argv[0] ) )
		vips_error_exit( NULL );

	/* Load source image into memory. This image will be in some format,
	 * like JPEG.
	 */
	if( !g_file_get_contents( argv[1], &buffer_in, &length_in, &error ) ) {
		fprintf( stderr, "unable to read file %s\n%s\n", 
			argv[1], error->message );
		g_error_free( error );
		exit( 1 );
	}

	/* Make a vips image from the memory buffer. This image will
	 * decompress from buffer_in as required.
	 */
	if( !(image = vips_image_new_from_buffer( buffer_in, length_in, NULL, 
		"access", VIPS_ACCESS_SEQUENTIAL,
		NULL )) ) 
		vips_error_exit( NULL );

	/* Perform some operation on the image.
	 */
	if( vips_invert( image, &x, NULL ) )
		vips_error_exit( NULL );

	/* x now holds a reference to image, so we can drop image and update
	 * our pointer to point to the head of the pipeline.
	 *
	 * buffer_in will still be in use -- we can't free that until the
	 * whole pipeline completes.
	 */
	g_object_unref( image );
	image = x;

	/* Create an output memory buffer. Again, this buffer will contain an
	 * image packed into some format, it will not be an array of pixel
	 * values.
	 */
	if( vips_image_write_to_buffer( image, 
		".jpg", (void **) &buffer_out, &length_out, NULL ) ) 
		vips_error_exit( NULL );

	if( !g_file_set_contents( argv[2], buffer_out, length_out, &error ) ) {
		fprintf( stderr, "unable to write file %s\n%s\n", 
			argv[1], error->message );
		g_error_free( error );
		exit( 1 );
	}

	g_object_unref( image ); 

	g_free( buffer_in );
	g_free( buffer_out );

	return( 0 );
}

jcupitt avatar May 09 '20 02:05 jcupitt

@jcupitt Thanks

zhaohuxing avatar May 09 '20 06:05 zhaohuxing

When you load an animated image, pyvips will just give you the first frame. You can select the number of frames to load with n= (1 meaning load 1 frame) and the first frame to load with page= (default 0, the first frame).

n=-1 means load all frames.

Hi @jcupitt, I use thumbnail to resize picture of the GIF format from c, vips_thumbnail("new.gif[n=-1]", out, 200). If I use vips_thumbnail_buffer, how to set n=-1?

zhaohuxing avatar May 12 '20 02:05 zhaohuxing

There's an argument called option_string you can use to pass extra flags to the loader. In C:

vips_thumbnail_buffer( data, length, &image, 200, "option_string", "n=-1", NULL )

Now I look at the docs I see we'd forgotten to add a note about this extra argument. I've fixed it, thanks!

jcupitt avatar May 12 '20 09:05 jcupitt

It works for me. Perhaps there's something wrong with your GIF? You need to check the return code from thumbnail_buffer.

Test program:

/* compile with
 *
 * gcc -g -Wall zhaohuxing.c `pkg-config vips --cflags --libs`
 */

#include <vips/vips.h>

int
main(int argc, char *argv[])
{
        char *contents;
        size_t length;
        VipsImage *image;

        if (VIPS_INIT (argv[0]))
                vips_error_exit (NULL);

        if (!g_file_get_contents (argv[1], &contents, &length, NULL))
                vips_error_exit (NULL);

        if (vips_thumbnail_buffer (contents, length, &image, 100,
                "option_string", "n=-1",
                NULL))
                vips_error_exit (NULL);

        if (vips_image_write_to_file (image, argv[2], NULL))
                return -1;

        g_free (contents);

        return 0;
}

I see:

$ ./a.out ~/pics/dancing-banana.gif x.gif
$ vipsheader ~/pics/dancing-banana.gif x.gif
temp-0: 121x128 uchar, 4 bands, srgb, gifload
temp-0: 95x100 uchar, 4 bands, srgb, gifload

Generating:

x

jcupitt avatar May 12 '20 11:05 jcupitt

Hi, @jcupitt I use vips_thumbnail_buffer with the image of the GIF format, but How to write the processed picture to buffer from c? I tried to use vips_image_write_to_buffer and vips_magicksave_buffer, both failed.

zhaohuxing avatar May 12 '20 12:05 zhaohuxing

You need to give vips_magicksave_buffer() the format to encode in, eg.:

        if (vips_magicksave_buffer (image,
                &output_contents, &output_length, 
                "format", "GIF",
                NULL))
                vips_error_exit (NULL);

jcupitt avatar May 12 '20 13:05 jcupitt

You need to give vips_magicksave_buffer() the format to encode in, eg.:

        if (vips_magicksave_buffer (image,
                &output_contents, &output_length, 
                "format", "GIF",
                NULL))
                vips_error_exit (NULL);

I truly appreciate your timely help

zhaohuxing avatar May 13 '20 02:05 zhaohuxing

Hi @jcupitt, use vips_image_new_from_buffer init image in gif format, option_str set n=-1?

zhaohuxing avatar Nov 03 '21 09:11 zhaohuxing

Yes, like the example above (if I understand what you mean).

jcupitt avatar Nov 03 '21 09:11 jcupitt

Thanks @jcupitt

zhaohuxing avatar Nov 15 '21 08:11 zhaohuxing