gif
でサイズを変更してサイズを小さくしたいpillow
があります。 gif
の現在のサイズは2MBです。
私は。。をしようとしています
高さ/幅が小さくなるようにサイズを変更します
その品質を低下させます。
JPEGの場合、通常は次のコードで十分なので、大きな画像のサイズは大幅に小さくなります。
from PIL import Image
im = Image.open("my_picture.jpg")
im = im.resize((im.size[0] // 2, im.size[1] // 2), Image.ANTIALIAS) # decreases width and height of the image
im.save("out.jpg", optimize=True, quality=85) # decreases its quality
ただし、GIFを使用すると、機能しないようです。次のコードは、out.gif
を最初のgifよりも大きくします。
im = Image.open("my_gif.gif")
im.seek(im.tell() + 1) # loads all frames
im.save("out.gif", save_all=True, optimize=True, quality=10) # should decrease its quality
print(os.stat("my_gif.gif").st_size) # 2096558 bytes / roughly 2MB
print(os.stat("out.gif").st_size) # 7536404 bytes / roughly 7.5MB
次の行を追加すると、GIFのすべてのフレームではなく、最初のフレームのみが保存されます。
im = im.resize((im.size[0] // 2, im.size[1] // 2), Image.ANTIALIAS) # should decrease its size
resize()
またはim.seek()
でim.tell()
を呼び出すことを考えていましたが、どちらのメソッドもImageオブジェクトを返さないため、resize()
を呼び出すことはできません。出力。
Pillowを使用して、すべてのフレームを維持しながらGIFのサイズを小さくする方法を知っていますか?
[編集]部分的な解決策:
Old Bearの応答 に続いて、次の変更を行いました。
BigglesZXのスクリプト を使用してすべてのフレームを抽出しています。これはPython 2スクリプトであり、私のプロジェクトはPython 3で書かれていることに注意してください(最初にその詳細について言及しましたが、 Stack Overflow Communityによって編集されました。)2to3 -w gifextract.py
を実行すると、そのスクリプトはPython 3)と互換性があります。
私は各フレームを個別にリシックしています:frame.resize((frame.size[0] // 2, frame.size[1] // 2), Image.ANTIALIAS)
すべてのフレームを一緒に保存しています:img.save("out.gif", save_all=True, optimize=True)
。
新しいgifが保存されて機能するようになりましたが、2つの主な問題があります。
out.gif
はまだ7.5MBであるため、サイズ変更方法が機能するかどうかはわかりません。最初のgifは2MBでした。
Gifの速度が上がり、gifはループしません。最初の実行後に停止します。
例:
オリジナルgifmy_gif.gif
:
処理後のGIF(out.gif
) https://i.imgur.com/zDO4cE4.mp4 (Stack Overflowに追加できませんでした)。 Imgurはそれを遅くしました(そしてそれをmp4に変換しました)。コンピューターからgifファイルを開くと、gif全体が約1.5秒続きます。
BigglesZXのスクリプト を使用して、Pillowを使用してGIFのサイズを変更する新しいスクリプトを作成しました。
オリジナルGIF(2.1 MB):
サイズ変更後の出力GIF(1.7 MB):
スクリプトを保存しました ここ 。 thumbnail
メソッドが機能しないことがわかったため、resize
メソッドではなくresize
メソッドを使用しています。
完璧ではないので、気軽にフォークして改善してください。未解決の問題がいくつかあります。
stack.imgur
にアップロードしようとすると、GIFが正しく表示されませんでした。最初のフレームのみが表示されました(表示されます ここ )。完全なコード(上記の要点を削除する必要があります):
def resize_gif(path, save_as=None, resize_to=None):
"""
Resizes the GIF to a given length:
Args:
path: the path to the GIF file
save_as (optional): Path of the resized gif. If not set, the original gif will be overwritten.
resize_to (optional): new size of the gif. Format: (int, int). If not set, the original GIF will be resized to
half of its size.
"""
all_frames = extract_and_resize_frames(path, resize_to)
if not save_as:
save_as = path
if len(all_frames) == 1:
print("Warning: only 1 frame found")
all_frames[0].save(save_as, optimize=True)
else:
all_frames[0].save(save_as, optimize=True, save_all=True, append_images=all_frames[1:], loop=1000)
def analyseImage(path):
"""
Pre-process pass over the image to determine the mode (full or additive).
Necessary as assessing single frames isn't reliable. Need to know the mode
before processing all frames.
"""
im = Image.open(path)
results = {
'size': im.size,
'mode': 'full',
}
try:
while True:
if im.tile:
tile = im.tile[0]
update_region = tile[1]
update_region_dimensions = update_region[2:]
if update_region_dimensions != im.size:
results['mode'] = 'partial'
break
im.seek(im.tell() + 1)
except EOFError:
pass
return results
def extract_and_resize_frames(path, resize_to=None):
"""
Iterate the GIF, extracting each frame and resizing them
Returns:
An array of all frames
"""
mode = analyseImage(path)['mode']
im = Image.open(path)
if not resize_to:
resize_to = (im.size[0] // 2, im.size[1] // 2)
i = 0
p = im.getpalette()
last_frame = im.convert('RGBA')
all_frames = []
try:
while True:
# print("saving %s (%s) frame %d, %s %s" % (path, mode, i, im.size, im.tile))
'''
If the GIF uses local colour tables, each frame will have its own palette.
If not, we need to apply the global palette to the new frame.
'''
if not im.getpalette():
im.putpalette(p)
new_frame = Image.new('RGBA', im.size)
'''
Is this file a "partial"-mode GIF where frames update a region of a different size to the entire image?
If so, we need to construct the new frame by pasting it on top of the preceding frames.
'''
if mode == 'partial':
new_frame.paste(last_frame)
new_frame.paste(im, (0, 0), im.convert('RGBA'))
new_frame.thumbnail(resize_to, Image.ANTIALIAS)
all_frames.append(new_frame)
i += 1
last_frame = new_frame
im.seek(im.tell() + 1)
except EOFError:
pass
return all_frames
Pillow 4.0xによると、Image.resize関数は単一の画像/フレームでのみ機能します。
目的を達成するには、最初に.gifファイルからすべてのフレームを抽出し、各フレームのサイズを一度に1つずつ変更してから、再度組み立てる必要があると思います。
最初のステップを実行するには、注意が必要な詳細があるようです。例えば。各gifフレームがローカルパレットを使用するかグローバルパレットを使用するか、およびgifが完全または部分的なフレームを使用して各画像を置き換えるかどうか。 BigglesZX gifファイルからすべてのフレームを抽出しながらこれらの問題に対処するスクリプトを開発したので、それを活用してください。
次に、抽出された各フレームのサイズを変更し、PIL.Image.resize()およびPIL.Image.save()を使用してそれらをすべて新しい.gifとしてアセンブルするスクリプトを作成する必要があります。
「im.seek(im.tell() + 1) # load all frames
」と書いているのに気づきました。これは間違っていると思います。むしろ、.gifファイルのフレーム間でインクリメントするために使用されます。 .gifファイルの保存関数でquality = 10を使用していることに気付きました。 PILドキュメント で提供されているようにこれは見つかりませんでした。 BiggleZXのスクリプトで言及されているタイル属性について詳しくは、これを読んでください link
以下の関数を使用して、アニメーション画像(GIF、WEBP)を含む画像のサイズ変更とトリミングを行っています。単純に、gifまたはwebpの各フレームを反復処理する必要があります。
from math import floor, fabs
from PIL import Image, ImageSequence
def transform_image(original_img, crop_w, crop_h):
"""
Resizes and crops the image to the specified crop_w and crop_h if necessary.
Works with multi frame gif and webp images also.
args:
original_img is the image instance created by pillow ( Image.open(filepath) )
crop_w is the width in pixels for the image that will be resized and cropped
crop_h is the height in pixels for the image that will be resized and cropped
returns:
Instance of an Image or list of frames which they are instances of an Image individually
"""
img_w, img_h = (original_img.size[0], original_img.size[1])
n_frames = getattr(original_img, 'n_frames', 1)
def transform_frame(frame):
"""
Resizes and crops the individual frame in the image.
"""
# resize the image to the specified height if crop_w is null in the recipe
if crop_w is None:
if crop_h == img_h:
return frame
new_w = floor(img_w * crop_h / img_h)
new_h = crop_h
return frame.resize((new_w, new_h))
# return the original image if crop size is equal to img size
if crop_w == img_w and crop_h == img_h:
return frame
# first resize to get most visible area of the image and then crop
w_diff = fabs(crop_w - img_w)
h_diff = fabs(crop_h - img_h)
enlarge_image = True if crop_w > img_w or crop_h > img_h else False
shrink_image = True if crop_w < img_w or crop_h < img_h else False
if enlarge_image is True:
new_w = floor(crop_h * img_w / img_h) if h_diff > w_diff else crop_w
new_h = floor(crop_w * img_h / img_w) if h_diff < w_diff else crop_h
if shrink_image is True:
new_w = crop_w if h_diff > w_diff else floor(crop_h * img_w / img_h)
new_h = crop_h if h_diff < w_diff else floor(crop_w * img_h / img_w)
left = (new_w - crop_w) // 2
right = left + crop_w
top = (new_h - crop_h) // 2
bottom = top + crop_h
return frame.resize((new_w, new_h)).crop((left, top, right, bottom))
# single frame image
if n_frames == 1:
return transform_frame(original_img)
# in the case of a multiframe image
else:
frames = []
for frame in ImageSequence.Iterator(original_img):
frames.append( transform_frame(frame) )
return frames