Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trying to merge gif and image file but getting unexpected output... Plz help #4920

Closed
Pratham31 opened this issue Sep 14, 2020 · 19 comments
Closed
Labels

Comments

@Pratham31
Copy link

What did you do?

Actually I have two files. One is a Transparent gif and the second one is a JPG image and my aim is to use image as a background and gif as a foreground. In short, I want to merge them.

What did you expect to happen?

I want an image as a background and gif as a foreground. In short, I want to merge them.

What actually happened?

But using the pillow library is giving me output like this.
output

What are your OS, Python, and Pillow versions?

  • OS: Windows
  • Python: Python 3.8.5
  • Pillow:
from PIL import Image, ImageSequence
transparent_foreground = Image.open("C:\\Users\\PRATHAMESH\\Desktop\\Python try\\me.jpg")
animated_gif = Image.open("C:\\Users\\PRATHAMESH\\Desktop\\Python try\\celeb (2).gif")

x, y = transparent_foreground.size
frames = []
for frame in ImageSequence.Iterator(animated_gif):
    frame = frame.copy()
    frame.paste(transparent_foreground,(x,y),mask=transparent_foreground)
    frames.append(frame)
    
frames[0].save('output.gif', save_all=True, append_images=frames[1:])
@radarhere radarhere added the GIF label Sep 14, 2020
@radarhere
Copy link
Member

This is similar to #4917 - except that issue has disappeared.
Please provide copies of me.jpg and 'celeb (2).jpg' so that we have everything necessary to run your script like you do.

@Pratham31
Copy link
Author

Pratham31 commented Sep 15, 2020

Yeah Sir, sure.
me.jpg -
me

celeb (2).gif -
celeb (2)

I want output like this(made this for reference) -

final

Note - celeb (2) is a gif not jpg.

@radarhere
Copy link
Member

A few comments.

  • As per https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.paste, a 2-tuple for the box argument is the upper left corner of the paste. You want it to be 0, 0, to paste one image exactly on top of the other. If it is the width and height of one of the images, then it will be pasted off down and to the right.
  • You've labelled your JPG image as 'transparent_foreground'. This is clearly not correct, as you want the JPG to be the background image.

Given that, and working around a transparency mask error, I get

from PIL import Image, ImageSequence
background = Image.open("me.jpg")
animated_gif = Image.open("celeb (2).gif")

frames = []
for frame in ImageSequence.Iterator(animated_gif):
	output = background.copy()
	transparent_foreground = frame.convert('RGBA')
	output.paste(transparent_foreground, (0, 0), mask=transparent_foreground)
	frames.append(output)

frames[0].save('output.gif', save_all=True, append_images=frames[1:])

But there is an issue with the background there.

Instead, see what you think of this - it loops through the pixels and copies them individually, ignoring the transparent or background pixels.

from PIL import Image, ImageSequence
background = Image.open("me.jpg")
animated_gif = Image.open("celeb (2).gif")

frames = []
for frame in ImageSequence.Iterator(animated_gif):
	output = background.copy()
	frame_px = frame.load()
	output_px = output.load()
	transparent_foreground = frame.convert('RGBA')
	transparent_foreground_px = transparent_foreground.load()
	for x in range(frame.width):
		for y in range(frame.height):
			if frame_px[x, y] in (frame.info["background"], frame.info["transparency"]):
				continue
			output_px[x, y] = transparent_foreground_px[x, y]
	frames.append(output)

frames[0].save('output.gif', save_all=True, append_images=frames[1:])

@Pratham31
Copy link
Author

Thank you, sir. It works 👍🏻

@Pratham31
Copy link
Author

But I have one question that It is taking time to save the GIF, right. Why is it so?

@Pratham31
Copy link
Author

And after some time it is again showing that weird image.

@radarhere
Copy link
Member

As is noted in the docs, manipulating individual pixels like I am doing in my second example is slow. The first code example I provided is much faster, but has a problem with the background.

With the 'weird image', I presume you're referring to the very last frame of the output GIF?

@radarhere
Copy link
Member

Not the best way, but the easiest way to fix that would be to just remove the last frame.

from PIL import Image, ImageSequence
background = Image.open("me.jpg")
animated_gif = Image.open("celeb (2).gif")

frames = []
for frame in ImageSequence.Iterator(animated_gif):
	output = background.copy()
	frame_px = frame.load()
	output_px = output.load()
	transparent_foreground = frame.convert('RGBA')
	transparent_foreground_px = transparent_foreground.load()
	for x in range(frame.width):
		for y in range(frame.height):
			if frame_px[x, y] in (frame.info["background"], frame.info["transparency"]):
				continue
			output_px[x, y] = transparent_foreground_px[x, y]
	frames.append(output)

frames[0].save('output.gif', save_all=True, append_images=frames[1:-1])

@Pratham31
Copy link
Author

Can we reduce the file size of GIF?

@nulano
Copy link
Contributor

nulano commented Sep 15, 2020

Can we reduce the file size of GIF?

Not with Pillow, see #617 (comment) for that.

@Pratham31
Copy link
Author

Pratham31 commented Sep 15, 2020

And what if I want a gif at the left below corner?

@Pratham31
Copy link
Author

Any modification in the above removed last frame program ?

@radarhere
Copy link
Member

Here is a modified version where I first scale down your GIF to half the size, and then locate it in the lower left corner.

from PIL import Image, ImageSequence
background = Image.open("me.jpg")
animated_gif = Image.open("celeb (2).gif")

frames = []
for frame in ImageSequence.Iterator(animated_gif):
	output = background.copy()
	
	# Reduce the GIF to half the size
	frame = frame.resize((frame.width // 2, frame.height // 2))
	
	frame_px = frame.load()
	output_px = output.load()
	transparent_foreground = frame.convert('RGBA')
	transparent_foreground_px = transparent_foreground.load()
	for x in range(frame.width):
		for y in range(frame.height):
			if frame_px[x, y] in (frame.info["background"], frame.info["transparency"]):
				continue
			
			# Offset the y value when setting the pixel
			# since this is now the lower left, rather than the upper left
			output_px[x, y+output.width/2] = transparent_foreground_px[x, y]
	frames.append(output)

frames[0].save('output.gif', save_all=True, append_images=frames[1:-1])

There are some bugs in the way that Pillow handles GIFs. The code I have given you in this issue is not ideal, but works because I presume you are personally more interested in an immediate solution than in a permanent one.

@mahesh548
Copy link

how can we add multiple gif over a jpg file??

@radarhere
Copy link
Member

You'd like to use a JPEG file as the background for an animated GIF that currently has transparency?

Taking two of our test images,
hopper
dispose_none_load_end
this code will combine them. The loop=0 argument is just to make it loop infinitely, so it is clear that it is animated.

from PIL import Image, ImageSequence

im = Image.open("dispose_none_load_end.gif")
background = Image.open("hopper.jpg")
frames = []
for im_frame in ImageSequence.Iterator(im):
	frame = background.copy()
	frame.paste(im_frame, mask=im_frame.convert("LA"))
	frames.append(frame)
frames[0].save("out.gif", save_all=True, append_images=frames[1:], loop=0)

out

@mahesh548
Copy link

I want to add 2 animated gif over a jpg image

@radarhere
Copy link
Member

Ok, adding in another test image,
dispose_prev_first_frame

from PIL import Image, ImageSequence

gif1 = Image.open("dispose_none_load_end.gif")
gif2 = Image.open("dispose_prev_first_frame.gif")
background = Image.open("hopper.jpg")
frames = []
for gif1_frame in ImageSequence.Iterator(gif1):
	for gif2_frame in ImageSequence.Iterator(gif2):
		frame = background.copy()
		frame.paste(gif1_frame, mask=gif1_frame.convert("LA"))
		frame.paste(gif2_frame, mask=gif2_frame.convert("LA"))
		frames.append(frame)
frames[0].save("out.gif", save_all=True, append_images=frames[1:], loop=0)

out

@mahesh548
Copy link

thank you soo much

@radarhere
Copy link
Member

Can we reduce the file size of GIF?

Not with Pillow, see #617 (comment) for that.

Between #5291 in Pillow 8.2.0 and #7568 when using optimize in the upcoming Pillow 10.2.0, GIFs created by Pillow should now be smaller.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants