Skip to content

The parameter “compress_level” is invalid for the APNG file extension #9299

Description

@petercham
--------------------------------------------------------------------
Pillow 12.0.0
Python 3.12.10 | packaged by conda-forge | (main, Apr 10 2025, 22:08:16) [MSC v.1943 64 bit (AMD64)]
--------------------------------------------------------------------
Python executable is C:\Work\SD-ComfyUI\python_comfy_env\python.exe
System Python files loaded from C:\Work\SD-ComfyUI\python_comfy_env
--------------------------------------------------------------------
Python Pillow modules loaded from C:\Work\SD-ComfyUI\python_comfy_env\Lib\site-packages\PIL
Binary Pillow modules loaded from C:\Work\SD-ComfyUI\python_comfy_env\Lib\site-packages\PIL
--------------------------------------------------------------------
--- PIL CORE support ok, compiled for 12.0.0
--- TKINTER support ok, loaded 8.6
--- FREETYPE2 support ok, loaded 2.14.1
--- LITTLECMS2 support ok, loaded 2.17
--- WEBP support ok, loaded 1.6.0
--- AVIF support ok, loaded 1.3.0
--- JPEG support ok, compiled for libjpeg-turbo 3.1.2
--- OPENJPEG (JPEG2000) support ok, loaded 2.5.4
--- ZLIB (PNG/ZIP) support ok, loaded 1.3.1.zlib-ng, compiled for zlib-ng 2.2.5
--- LIBTIFF support ok, loaded 4.7.1
*** RAQM (Bidirectional Text) support not installed
*** LIBIMAGEQUANT (Quantization method) support not installed
*** XCB (X protocol) support not installed
--------------------------------------------------------------------
✅ Loaded color image: (257, 293), RGBA
✅ Loaded BW image: (257, 293), RGBA

🔧 Testing PNG format...
✅ PNG: 8/8 tests passed

🔧 Testing GIF format...
✅ GIF: 8/8 tests passed

🔧 Testing WEBP_LOSSY format...
✅ WEBP_LOSSY: 8/8 tests passed

🔧 Testing WEBP_LOSSLESS format...
✅ WEBP_LOSSLESS: 8/8 tests passed

🔧 Testing TIFF format...
✅ TIFF: 8/8 tests passed

🔧 Testing AVIF format...
✅ AVIF: 8/8 tests passed

📊 RESULTS SUMMARY:

PNG single frame:
  9: 50.5KB, Time: 0.019s, PSNR: ∞dB, MSE: 0.00
  6: 53.0KB, Time: 0.005s, PSNR: ∞dB, MSE: 0.00
  3: 56.9KB, Time: 0.003s, PSNR: ∞dB, MSE: 0.00
  0: 294.6KB, Time: 0.002s, PSNR: ∞dB, MSE: 0.00

PNG 10 frames:
  9: 417.4KB, Time: 0.044s, PSNR: ∞dB, MSE: 0.00
  6: 417.4KB, Time: 0.045s, PSNR: ∞dB, MSE: 0.00
  3: 417.4KB, Time: 0.045s, PSNR: ∞dB, MSE: 0.00
  0: 417.4KB, Time: 0.043s, PSNR: ∞dB, MSE: 0.00

GIF single frame:
  32: 12.4KB, Time: 0.002s, PSNR: 33.0dB, MSE: 32.43
  64: 16.3KB, Time: 0.001s, PSNR: 35.8dB, MSE: 16.97
  128: 16.9KB, Time: 0.002s, PSNR: 36.1dB, MSE: 15.82
  256: 17.3KB, Time: 0.002s, PSNR: 36.1dB, MSE: 15.81

GIF 10 frames:
  32: 120.5KB, Time: 0.026s, PSNR: 33.0dB, MSE: 32.43
  64: 123.6KB, Time: 0.027s, PSNR: 35.8dB, MSE: 16.97
  128: 124.2KB, Time: 0.027s, PSNR: 36.1dB, MSE: 15.82
  256: 124.6KB, Time: 0.028s, PSNR: 36.1dB, MSE: 15.81

WEBP_LOSSY single frame:
  25: 2.9KB, Time: 0.009s, PSNR: 36.4dB, MSE: 15.02
  55: 3.8KB, Time: 0.010s, PSNR: 38.1dB, MSE: 10.03
  85: 5.4KB, Time: 0.010s, PSNR: 43.8dB, MSE: 2.74
  100: 11.1KB, Time: 0.013s, PSNR: 47.7dB, MSE: 1.09

WEBP_LOSSY 10 frames:
  25: 36.7KB, Time: 0.024s, PSNR: 36.3dB, MSE: 15.37
  55: 68.1KB, Time: 0.031s, PSNR: 38.3dB, MSE: 9.61
  85: 77.3KB, Time: 0.033s, PSNR: 43.5dB, MSE: 2.91
  100: 156.8KB, Time: 0.042s, PSNR: 47.5dB, MSE: 1.16

WEBP_LOSSLESS single frame:
  0: 36.4KB, Time: 0.043s, PSNR: 52.8dB, MSE: 0.34
  33: 35.1KB, Time: 0.063s, PSNR: 52.8dB, MSE: 0.34
  67: 35.2KB, Time: 0.071s, PSNR: 52.8dB, MSE: 0.34
  100: 34.5KB, Time: 0.145s, PSNR: 52.8dB, MSE: 0.34

WEBP_LOSSLESS 10 frames:
  0: 316.2KB, Time: 0.027s, PSNR: 52.1dB, MSE: 0.40
  33: 311.8KB, Time: 0.031s, PSNR: 52.1dB, MSE: 0.40
  67: 307.4KB, Time: 0.047s, PSNR: 52.1dB, MSE: 0.40
  100: 304.5KB, Time: 0.069s, PSNR: 52.1dB, MSE: 0.40

TIFF single frame:
  lzma: 57.6KB, Time: 0.036s, PSNR: ∞dB, MSE: 0.00
  tiff_adobe_deflate: 69.8KB, Time: 0.002s, PSNR: ∞dB, MSE: 0.00
  tiff_lzw: 146.6KB, Time: 0.002s, PSNR: ∞dB, MSE: 0.00
  none: 294.3KB, Time: 0.001s, PSNR: ∞dB, MSE: 0.00

TIFF 10 frames:
  lzma: 444.1KB, Time: 0.358s, PSNR: ∞dB, MSE: 0.00
  tiff_adobe_deflate: 559.4KB, Time: 0.023s, PSNR: ∞dB, MSE: 0.00
  tiff_lzw: 1179.7KB, Time: 0.019s, PSNR: ∞dB, MSE: 0.00
  none: 2943.3KB, Time: 0.004s, PSNR: ∞dB, MSE: 0.00

AVIF single frame:
  25: 1.7KB, Time: 0.026s, PSNR: 34.8dB, MSE: 21.31
  55: 3.4KB, Time: 0.028s, PSNR: 39.8dB, MSE: 6.81
  85: 6.8KB, Time: 0.024s, PSNR: 44.2dB, MSE: 2.46
  100: 22.7KB, Time: 0.038s, PSNR: 47.5dB, MSE: 1.15

AVIF 10 frames:
  25: 7.0KB, Time: 0.106s, PSNR: 41.6dB, MSE: 4.49
  55: 10.8KB, Time: 0.097s, PSNR: 45.0dB, MSE: 2.06
  85: 22.3KB, Time: 0.112s, PSNR: 47.0dB, MSE: 1.29
  100: 40.2KB, Time: 0.119s, PSNR: 47.5dB, MSE: 1.15

Activity

  1. radarhere commented on Nov 13, 2025

    @radarhere
    Member

    Could you provide a self-contained example demonstrating your point? In other words, code, along with any image files required to run that code?

  2. petercham commented on Nov 13, 2025

    @petercham
    Author

    Could you provide a self-contained example demonstrating your point? In other words, code, along with any image files required to run that code?

    I also tested all formats supporting Sequence and included a comparison report of their differences.

    Test code:

    import time
    from pathlib import Path
    import numpy as np
    import pandas as pd
    from PIL import Image
    from PIL import report  # 自动打印PIL报告
    
    def test_image_compression():
        """优化版图像压缩测试 - 支持多种格式"""
        script_dir = Path(__file__).resolve().parent
        output_dir = script_dir / 'compression_test'
        output_dir.mkdir(exist_ok=True)
        
        # 加载图像
        img = Image.open(script_dir / 'example.png')
        bw_img = Image.open(script_dir / 'example_bw.png') if (script_dir / 'example_bw.png').exists() else img.convert('L')
        print(f"✅ Loaded color image: {img.size}, {img.mode}")
        print(f"✅ Loaded BW image: {bw_img.size}, {bw_img.mode}")
        
        # 创建动画序列
        frames = [img if i % 2 == 0 else bw_img.resize(img.size) for i in range(10)]
        
        # 统一测试配置 - 所有格式都测试4个质量等级
        test_configs = [
            ('PNG', [9, 6, 3, 0]),           # 4个压缩级别
            ('GIF', [256, 128, 64, 32]),     # 4个调色板大小
            ('WEBP_LOSSY', [100, 85, 55, 25]), # 4个质量等级
            ('WEBP_LOSSLESS', [100, 67, 33, 0]),  # 4个质量等级(无损模式)
            ('TIFF', ['none', 'tiff_adobe_deflate', 'tiff_lzw', 'lzma']),  # 4种压缩算法
            ('AVIF', [100, 85, 55, 25])      # 4个质量等级
        ]
        
        results = []
        
        for format_type, params in test_configs:
            # 为每个格式统计成功/失败数量
            success_count = 0
            total_tests = len(params) * 2  # 单帧和多帧
            
            print(f"\n🔧 Testing {format_type} format...")
            
            for param in params:
                for frame_count in [1, 10]:  # 单帧和多帧
                    # 生成文件名
                    prefix = 'seq_' if frame_count > 1 else ''
                    param_display = str(param).replace('/', '_')
                    filename = f"{prefix}{format_type.lower()}_{param_display}.{format_type.split('_')[0].lower()}"
                    filepath = output_dir / filename
                    
                    try:
                        start_time = time.time()
                        test_img = frames if frame_count > 1 else img
                        
                        # 统一保存逻辑
                        if format_type == 'PNG':
                            save_args = {'compress_level': param}
                        elif format_type == 'GIF':
                            save_args = {'colors': param}
                        elif format_type == 'WEBP_LOSSY':
                            save_args = {'quality': param}
                        elif format_type == 'WEBP_LOSSLESS':
                            save_args = {'lossless': True, 'quality': param}
                        elif format_type == 'TIFF':
                            save_args = {'compression': param}
                        else:  # AVIF
                            save_args = {'quality': param}
                        
                        # 多帧处理
                        if frame_count > 1:
                            save_args.update({'save_all': True, 'append_images': test_img[1:], 'duration': 200, 'loop': 0})
                            if format_type == 'GIF':
                                gif_frames = [f.quantize(colors=param) if f.mode != 'P' else f for f in test_img]
                                gif_frames[0].save(filepath, **save_args)
                            else:
                                # 对于TIFF和AVIF,尝试多帧保存
                                try:
                                    test_img[0].save(filepath, **save_args)
                                except Exception as e:
                                    # 如果不支持多帧,回退到单帧
                                    if "animation" in str(e).lower() or "multiple" in str(e).lower():
                                        test_img[0].save(filepath, **{k: v for k, v in save_args.items() 
                                                                     if k not in ['save_all', 'append_images', 'duration', 'loop']})
                                    else:
                                        raise e
                        else:
                            if format_type == 'GIF':
                                gif_img = test_img.quantize(colors=param) if test_img.mode != 'P' else test_img
                                gif_img.save(filepath)
                            else:
                                test_img.save(filepath, **save_args)
                        
                        # 计算差异
                        write_time = time.time() - start_time
                        file_size = filepath.stat().st_size
                        
                        saved_img = Image.open(filepath)
                        if frame_count > 1 and hasattr(saved_img, 'n_frames'):
                            saved_img.seek(0)
                        
                        # 简单差异计算
                        orig_arr = np.array(img.convert('RGB'))
                        saved_arr = np.array(saved_img.convert('RGB'))
                        mse = np.mean((orig_arr - saved_arr) ** 2)
                        psnr = 20 * np.log10(255.0 / np.sqrt(mse)) if mse > 0 else float('inf')
                        
                        saved_img.close()
                        
                        result = {
                            'Type': format_type,
                            'Frames': frame_count,
                            'Filename': filename,
                            'Param': param,
                            'Size_KB': file_size / 1024,
                            'Time_Sec': write_time,
                            'MSE': mse,
                            'PSNR': psnr
                        }
                        results.append(result)
                        success_count += 1
                        
                    except Exception as e:
                        error_msg = str(e)
                        if "encoder" in error_msg.lower() or "format" in error_msg.lower():
                            error_msg = "Format not supported"
                        
                        # 只打印失败信息
                        frame_info = f"{frame_count} frames" if frame_count > 1 else "single frame"
                        print(f"❌ {format_type} {frame_info} {param}: Failed - {error_msg[:50]}")
                        
                        result = {
                            'Type': format_type,
                            'Frames': frame_count,
                            'Filename': filename,
                            'Param': param,
                            'Size_KB': float('nan'),
                            'Time_Sec': float('nan'),
                            'MSE': float('nan'),
                            'PSNR': float('nan'),
                            'Error': error_msg[:100]
                        }
                        results.append(result)
            
            # 每个格式测试完成后打印汇总
            print(f"✅ {format_type}: {success_count}/{total_tests} tests passed")
        
        # 保存和显示结果
        df = pd.DataFrame(results)
        df.to_csv(output_dir / 'results.csv', index=False)
        
        # 英文报告,标题包含帧数
        print("\n📊 RESULTS SUMMARY:")
        for format_type in df['Type'].unique():
            format_df = df[df['Type'] == format_type]
            
            for frame_count in sorted(format_df['Frames'].unique()):
                frame_df = format_df[format_df['Frames'] == frame_count]
                
                # 按参数值排序(特殊处理无损WebP)
                if 'WEBP_LOSSLESS' in format_type:
                    frame_df = frame_df.sort_values('Param')
                else:
                    frame_df = frame_df.sort_values('Size_KB')
                
                frame_label = f"{frame_count} frames" if frame_count > 1 else "single frame"
                print(f"\n{format_type} {frame_label}:")
                
                for _, row in frame_df.iterrows():
                    if pd.isna(row['Size_KB']):
                        print(f"  {row['Param']}: FAILED")
                    else:
                        psnr_info = f"∞" if row['PSNR'] == float('inf') else f"{row['PSNR']:.1f}"
                        print(f"  {row['Param']}: {row['Size_KB']:.1f}KB, Time: {row['Time_Sec']:.3f}s, PSNR: {psnr_info}dB, MSE: {row['MSE']:.2f}")
        
        return df
    
    if __name__ == "__main__":
        test_image_compression()
  3. petercham commented on Nov 13, 2025

    @petercham
    Author

    Simply pick any image, use Photoshop or any other tool to create an identical black-and-white version of the same dimensions, then use both as looping sequence frames.

    The file size of the written PNG sequence shows absolutely no difference in quality.

    PNG 10 frames:
    9: 417.4KB, Time: 0.044s, PSNR: ∞dB, MSE: 0.00
    6: 417.4KB, Time: 0.045s, PSNR: ∞dB, MSE: 0.00
    3: 417.4KB, Time: 0.045s, PSNR: ∞dB, MSE: 0.00
    0: 417.4KB, Time: 0.043s, PSNR: ∞dB, MSE: 0.00

    Other formats can produce files of different sizes as expected.

  4. changed the title [-]The parameter “compress_level” is invalid for the APNG file extension.[/-] [+]The parameter “compress_level” is invalid for the APNG file extension[/+] on Nov 13, 2025
  5. radarhere commented on Nov 14, 2025

    @radarhere
    Member

    I've created #9300

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions