├── gifsicle.exe ├── install_python_dependencies.bat ├── DAINAUTO.bat ├── extract_undithered_interpolation_(drop source file here).bat ├── DAINAUTOTEMPLATE_mode_1_interp_2_split_300_pad_100.bat ├── DAINAUTOTEMPLATE_mode_1_interp_4_split_300_pad_100.bat ├── DAINAUTOTEMPLATE_mode_1_interp_8_split_300_pad_100.bat ├── DAINAUTOTEMPLATE_mode_4_interp_2_split_300_pad_100.bat ├── DAINAUTOTEMPLATE_mode_4_interp_4_split_300_pad_100.bat ├── README.txt ├── README.md ├── pixelsafe_interpolation_with_log.bat ├── pixelsafe_interpolation_2x.bat ├── pixelsafe_interpolation_4x.bat ├── pixelsafe_interpolation_8x.bat ├── .gitignore └── DAINAUTO_utils ├── create_gif_from_here.py ├── crop_image.py ├── scale_image.py ├── gif_manips.py └── create_gif_from_here_adv.py /gifsicle.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akirbaes/_AutoDain/HEAD/gifsicle.exe -------------------------------------------------------------------------------- /install_python_dependencies.bat: -------------------------------------------------------------------------------- 1 | pip install pillow 2 | pip install numpy 3 | 4 | @echo --- All Done! --- 5 | @pause -------------------------------------------------------------------------------- /DAINAUTO.bat: -------------------------------------------------------------------------------- 1 | CD "%~dp0" 2 | FOR %%A IN (%*) DO ( 3 | REM For every file in the argument (drag&drop) 4 | echo ---- Calling DAIN on %%A ---- 5 | "%~dp0\DAINAPP.exe" --cli True -i %%A -o "%%~dpnA\\" --output_name "%%~nA.gif" --loop 1 6 | echo ---- DAIN done ---- 7 | ) 8 | 9 | cmd \K -------------------------------------------------------------------------------- /extract_undithered_interpolation_(drop source file here).bat: -------------------------------------------------------------------------------- 1 | Echo Pass the source file next to its interpolated folder to create an interpolated gif without dithering. 2 | 3 | FOR %%A IN (%*) DO ( 4 | "%~dp0..\ffmpeg" -i "%%~dpnxA" -vf palettegen=max_colors=256:stats_mode=full:reserve_transparent=1:transparency_color=0000FF "%%~dpnA\palette.png" -y 5 | 6 | cd "%%~dpnA" 7 | 8 | mkdir interpolated_gifs 9 | 10 | cd interpolated_frames 11 | 12 | FOR %%B IN (0*.png) DO ( 13 | "%~dp0..\ffmpeg" -i %%B -i "%%~dpnA\palette.png" -lavfi paletteuse=dither=0 "..\interpolated_gifs\%%~nB.gif" -y 14 | ) 15 | 16 | cd ..\interpolated_gifs 17 | 18 | python "%~dp0\DAINAUTO_utils\create_gif_from_here.py" 19 | 20 | cd .. 21 | 22 | COPY "interpolated_gifs\output.gif" "%%~dpnA interp.gif" /Y 23 | ) 24 | 25 | if "%main%"=="" ( 26 | pause 27 | ) 28 | -------------------------------------------------------------------------------- /DAINAUTOTEMPLATE_mode_1_interp_2_split_300_pad_100.bat: -------------------------------------------------------------------------------- 1 | echo Drag and Drop several files to automatically interpolate them with the given parameters 2 | 3 | REM Read movie mode value from filename (2nd value that's between the _ signs) 4 | FOR /f "tokens=3 delims=_ " %%d in ("%~n0") DO ( 5 | set mode=%%d 6 | ) 7 | REM Read interpolation value from filename (4th value that's between the _ signs) 8 | FOR /f "tokens=5 delims=_ " %%d in ("%~n0") DO ( 9 | set interp=%%d 10 | ) 11 | REM Read movie mode value from filename (6th value that's between the _ signs) 12 | FOR /f "tokens=7 delims=_ " %%d in ("%~n0") DO ( 13 | set sectionsize=%%d 14 | ) 15 | REM Read movie mode value from filename (8th value that's between the _ signs) 16 | FOR /f "tokens=9 delims=_ " %%d in ("%~n0") DO ( 17 | set sectionpadding=%%d 18 | ) 19 | 20 | CD "%~dp0" 21 | FOR %%A IN (%*) DO ( 22 | 23 | echo ---- Calling DAIN on %%A ---- 24 | "%~dp0..\DAINAPP.exe" --cli True -i %%A -o "%%~dpnA\\" --output_name "%%~nA.gif" --loop 1 --pallete 1 --interpolations %interp% --downsample_fps 50 --frame_handling %mode% --split_size %sectionsize% --split_pad %sectionpadding% --alpha 2 --check_scene_change -1 --interpolation_algo 0 --audio_version 0 --clear_original_folder 1 --clear_interpolated_folder 0 --step_extract 1 --step_interpolate 1 --step_render 1 --clean_cache 1 25 | echo ---- DAIN done ---- 26 | ) 27 | 28 | cmd \K -------------------------------------------------------------------------------- /DAINAUTOTEMPLATE_mode_1_interp_4_split_300_pad_100.bat: -------------------------------------------------------------------------------- 1 | echo Drag and Drop several files to automatically interpolate them with the given parameters 2 | 3 | REM Read movie mode value from filename (2nd value that's between the _ signs) 4 | FOR /f "tokens=3 delims=_ " %%d in ("%~n0") DO ( 5 | set mode=%%d 6 | ) 7 | REM Read interpolation value from filename (4th value that's between the _ signs) 8 | FOR /f "tokens=5 delims=_ " %%d in ("%~n0") DO ( 9 | set interp=%%d 10 | ) 11 | REM Read movie mode value from filename (6th value that's between the _ signs) 12 | FOR /f "tokens=7 delims=_ " %%d in ("%~n0") DO ( 13 | set sectionsize=%%d 14 | ) 15 | REM Read movie mode value from filename (8th value that's between the _ signs) 16 | FOR /f "tokens=9 delims=_ " %%d in ("%~n0") DO ( 17 | set sectionpadding=%%d 18 | ) 19 | 20 | CD "%~dp0" 21 | FOR %%A IN (%*) DO ( 22 | 23 | echo ---- Calling DAIN on %%A ---- 24 | "%~dp0..\DAINAPP.exe" --cli True -i %%A -o "%%~dpnA\\" --output_name "%%~nA.gif" --loop 1 --pallete 1 --interpolations %interp% --downsample_fps 50 --frame_handling %mode% --split_size %sectionsize% --split_pad %sectionpadding% --alpha 2 --check_scene_change -1 --interpolation_algo 0 --audio_version 0 --clear_original_folder 1 --clear_interpolated_folder 0 --step_extract 1 --step_interpolate 1 --step_render 1 --clean_cache 1 25 | echo ---- DAIN done ---- 26 | ) 27 | 28 | cmd \K -------------------------------------------------------------------------------- /DAINAUTOTEMPLATE_mode_1_interp_8_split_300_pad_100.bat: -------------------------------------------------------------------------------- 1 | echo Drag and Drop several files to automatically interpolate them with the given parameters 2 | 3 | REM Read movie mode value from filename (2nd value that's between the _ signs) 4 | FOR /f "tokens=3 delims=_ " %%d in ("%~n0") DO ( 5 | set mode=%%d 6 | ) 7 | REM Read interpolation value from filename (4th value that's between the _ signs) 8 | FOR /f "tokens=5 delims=_ " %%d in ("%~n0") DO ( 9 | set interp=%%d 10 | ) 11 | REM Read movie mode value from filename (6th value that's between the _ signs) 12 | FOR /f "tokens=7 delims=_ " %%d in ("%~n0") DO ( 13 | set sectionsize=%%d 14 | ) 15 | REM Read movie mode value from filename (8th value that's between the _ signs) 16 | FOR /f "tokens=9 delims=_ " %%d in ("%~n0") DO ( 17 | set sectionpadding=%%d 18 | ) 19 | 20 | CD "%~dp0" 21 | FOR %%A IN (%*) DO ( 22 | 23 | echo ---- Calling DAIN on %%A ---- 24 | "%~dp0..\DAINAPP.exe" --cli True -i %%A -o "%%~dpnA\\" --output_name "%%~nA.gif" --loop 1 --pallete 1 --interpolations %interp% --downsample_fps 50 --frame_handling %mode% --split_size %sectionsize% --split_pad %sectionpadding% --alpha 2 --check_scene_change -1 --interpolation_algo 0 --audio_version 0 --clear_original_folder 1 --clear_interpolated_folder 0 --step_extract 1 --step_interpolate 1 --step_render 1 --clean_cache 1 25 | echo ---- DAIN done ---- 26 | ) 27 | 28 | cmd \K -------------------------------------------------------------------------------- /DAINAUTOTEMPLATE_mode_4_interp_2_split_300_pad_100.bat: -------------------------------------------------------------------------------- 1 | echo Drag and Drop several files to automatically interpolate them with the given parameters 2 | 3 | REM Read movie mode value from filename (2nd value that's between the _ signs) 4 | FOR /f "tokens=3 delims=_ " %%d in ("%~n0") DO ( 5 | set mode=%%d 6 | ) 7 | REM Read interpolation value from filename (4th value that's between the _ signs) 8 | FOR /f "tokens=5 delims=_ " %%d in ("%~n0") DO ( 9 | set interp=%%d 10 | ) 11 | REM Read movie mode value from filename (6th value that's between the _ signs) 12 | FOR /f "tokens=7 delims=_ " %%d in ("%~n0") DO ( 13 | set sectionsize=%%d 14 | ) 15 | REM Read movie mode value from filename (8th value that's between the _ signs) 16 | FOR /f "tokens=9 delims=_ " %%d in ("%~n0") DO ( 17 | set sectionpadding=%%d 18 | ) 19 | 20 | CD "%~dp0" 21 | FOR %%A IN (%*) DO ( 22 | 23 | echo ---- Calling DAIN on %%A ---- 24 | "%~dp0..\DAINAPP.exe" --cli True -i %%A -o "%%~dpnA\\" --output_name "%%~nA.gif" --loop 1 --pallete 1 --interpolations %interp% --downsample_fps 50 --frame_handling %mode% --split_size %sectionsize% --split_pad %sectionpadding% --alpha 2 --check_scene_change -1 --interpolation_algo 0 --audio_version 0 --clear_original_folder 1 --clear_interpolated_folder 1 --step_extract 1 --step_interpolate 1 --step_render 1 --clean_cache 1 25 | echo ---- DAIN done ---- 26 | ) 27 | 28 | cmd \K -------------------------------------------------------------------------------- /DAINAUTOTEMPLATE_mode_4_interp_4_split_300_pad_100.bat: -------------------------------------------------------------------------------- 1 | echo Drag and Drop several files to automatically interpolate them with the given parameters 2 | 3 | REM Read movie mode value from filename (2nd value that's between the _ signs) 4 | FOR /f "tokens=3 delims=_ " %%d in ("%~n0") DO ( 5 | set mode=%%d 6 | ) 7 | REM Read interpolation value from filename (4th value that's between the _ signs) 8 | FOR /f "tokens=5 delims=_ " %%d in ("%~n0") DO ( 9 | set interp=%%d 10 | ) 11 | REM Read movie mode value from filename (6th value that's between the _ signs) 12 | FOR /f "tokens=7 delims=_ " %%d in ("%~n0") DO ( 13 | set sectionsize=%%d 14 | ) 15 | REM Read movie mode value from filename (8th value that's between the _ signs) 16 | FOR /f "tokens=9 delims=_ " %%d in ("%~n0") DO ( 17 | set sectionpadding=%%d 18 | ) 19 | 20 | CD "%~dp0" 21 | FOR %%A IN (%*) DO ( 22 | 23 | echo ---- Calling DAIN on %%A ---- 24 | "%~dp0..\DAINAPP.exe" --cli True -i %%A -o "%%~dpnA\\" --output_name "%%~nA.gif" --loop 1 --pallete 1 --interpolations %interp% --downsample_fps 50 --frame_handling %mode% --split_size %sectionsize% --split_pad %sectionpadding% --alpha 2 --check_scene_change -1 --interpolation_algo 0 --audio_version 0 --clear_original_folder 1 --clear_interpolated_folder 0 --step_extract 1 --step_interpolate 1 --step_render 1 --clean_cache 1 25 | echo ---- DAIN done ---- 26 | ) 27 | 28 | cmd \K -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | INSTALL: 2 | 3 | Put this folder into DAIN_APP 4 | It should look like DAIN_APP ALpha\__AUTODAIN_TEMPLATES 5 | 6 | Install Python 7 | Install pillow and numpy 8 | (You can click on install_python_dependencies.bat for that part) 9 | 10 | USE: 11 | 12 | Drag-and-drop gif files on DAINAUTO scripts to interpolate them using pretty good settings for transparent gifs. 13 | 14 | 15 | DAINAUTOTEMPLATE_mode_1_interp_4_split_500_pad_200.bat 16 | and 17 | DAINAUTOTEMPLATE_mode_4_interp_4_split_500_pad_200.bat 18 | You can edit the filename to edit these four parameters. 19 | You can crack it open to save your own set of favorite parameters 20 | 21 | You can then drag your gif file on 22 | extract_undithered_interpolation_(drop source file here).bat 23 | if your version of DAIN-App still has dithering in the output (0.36 and lower) 24 | 25 | 26 | 27 | If you have particularly sensitive pixel-art, you can drag it on 28 | DAINAUTO_pixelsafe_interpolation.bat 29 | This will 30 | 1) Add a 1px border to your gif (DAIN-app tends to mush borders) 31 | 2) Upscale 4X the animation (bigger pixel=less loss of detail using DAIN) 32 | 3) Send it to DAIN-app with good params 33 | 4) Retrieve the undithered interpolation 34 | 5) Downscale back the image 35 | 6) Remove the 1px border 36 | 7) Cleanup :) 37 | 38 | If DAIN_app gives you a Memory Error, edit the file to have a lower section size and padding 39 | 40 | 41 | Details: 42 | Uses DAIN_APP's own ffmpeg.exe to create a palette from gif 43 | Uses PIL and NUMPY to manipulate the images and create gifs 44 | Uses gifsicle to Unoptimise gifs because PIL has trouble with reading transparency -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # _AutoDain 2 | Some scripts to use with Dain-App 3 | 4 | INSTALL: 5 | 6 | Put this folder into DAIN_APP 7 | It should look like DAIN_APP ALpha\__AUTODAIN_TEMPLATES 8 | 9 | Install Python 10 | Install pillow and numpy 11 | (You can click on install_python_dependencies.bat for that part) 12 | 13 | USE: 14 | 15 | Drag-and-drop gif files on DAINAUTO scripts to interpolate them using pretty good settings for transparent gifs. 16 | 17 | 18 | DAINAUTOTEMPLATE_mode_1_interp_4_split_500_pad_200.bat 19 | and 20 | DAINAUTOTEMPLATE_mode_4_interp_4_split_500_pad_200.bat 21 | You can edit the filename to edit these four parameters. 22 | You can crack it open to save your own set of favorite parameters 23 | 24 | You can then drag your gif file on 25 | extract_undithered_interpolation_(drop source file here).bat 26 | if your version of DAIN-App still has dithering in the output (0.36 and lower) 27 | 28 | 29 | 30 | If you have particularly sensitive pixel-art, you can drag it on 31 | DAINAUTO_pixelsafe_interpolation.bat 32 | This will 33 | 1) Add a 1px border to your gif (DAIN-app tends to mush borders) 34 | 2) Upscale 4X the animation (bigger pixel=less loss of detail using DAIN) 35 | 3) Send it to DAIN-app with good params 36 | 4) Retrieve the undithered interpolation 37 | 5) Downscale back the image 38 | 6) Remove the 1px border 39 | 7) Cleanup :) 40 | 41 | If DAIN_app gives you a Memory Error, edit the file to have a lower section size and padding 42 | 43 | 44 | Details: 45 | Uses DAIN_APP's own ffmpeg.exe to create a palette from gif 46 | Uses PIL and NUMPY to manipulate the images and create gifs 47 | Uses gifsicle to Unoptimise gifs because PIL has trouble with reading transparency -------------------------------------------------------------------------------- /pixelsafe_interpolation_with_log.bat: -------------------------------------------------------------------------------- 1 | if "%main%"=="" ( 2 | set main=pixelsafe 3 | ) 4 | 5 | set interp=4 6 | set split=500 7 | set padding=100 8 | 9 | 10 | >batch_log.txt 2>&1( 11 | FOR %%A IN (%*) DO ( 12 | 13 | "%~dp0\gifsicle.exe" --colors=255 "%%~dpnxA" -o "%%~dpnA o.gif" 14 | "%~dp0\gifsicle.exe" -U "%%~dpnA o.gif" -o "%%~dpnA o.gif" 15 | @echo Cropping 16 | python "%~dp0\DAINAUTO_utils\crop_image.py" "%%~dpnA o.gif" 1 17 | 18 | "%~dp0\gifsicle.exe" --colors=255 "%%~dpnA o+1.gif" -o "%%~dpnA o+1.gif" 19 | "%~dp0\gifsicle.exe" -U "%%~dpnA o+1.gif" -o "%%~dpnA o+1.gif" 20 | 21 | 22 | python "%~dp0\DAINAUTO_utils\scale_image.py" "%%~dpnA o+1.gif" %interp% 23 | 24 | echo "Calling DAIN" 25 | 26 | "%~dp0..\DAINAPP.exe" --cli True -i "%%~dpnA o+1_X%interp%.gif" -o "%%~dpnA\\" --output_name "%%~nA.gif" --loop 1 --pallete 1 --interpolations %interp% --downsample_fps 50 --frame_handling 4 --split_size %split% --split_pad %padding% --alpha 2 --check_scene_change -1 --interpolation_algo 0 --audio_version 0 --clear_original_folder 1 --clear_interpolated_folder 0 --step_extract 1 --step_interpolate 1 --step_render 1 --clean_cache 1 27 | 28 | 29 | call "%~dp0\extract_undithered_interpolation_(drop source file here).bat" "%%~dpnxA" 30 | 31 | python "%~dp0\DAINAUTO_utils\scale_image.py" "%%~dpnA interp.gif" -%interp% 32 | 33 | python "%~dp0\DAINAUTO_utils\crop_image.py" "%%~dpnA interp_D4.gif" -1 34 | 35 | "%~dp0\gifsicle.exe" --colors=255 "%%~dpnA interp_D4+-1.gif" -o "%%~dpnA interp_D4+-1.gif" 36 | "%~dp0\gifsicle.exe" -U "%%~dpnA interp_D4+-1.gif" -o "%%~dpnA interp_D4.gif" 37 | 38 | del "%%~dpnA interp_D4+-1.gif" 39 | 40 | cd "%~dp0" 41 | 42 | del "%%~dpnA o%%~xA" 43 | del "%%~dpnA o+1%%~xA" 44 | del "%%~dpnA o+1_X4%%~xA" 45 | ) 46 | ) 47 | if "%main%"=="pixelsafe" ( 48 | pause 49 | ) 50 | 51 | -------------------------------------------------------------------------------- /pixelsafe_interpolation_2x.bat: -------------------------------------------------------------------------------- 1 | if "%main%"=="" ( 2 | set main=pixelsafe 3 | ) 4 | 5 | set interp=2 6 | set split=300 7 | set padding=100 8 | 9 | 10 | 11 | FOR %%A IN (%*) DO ( 12 | 13 | "%~dp0\gifsicle.exe" --colors=255 "%%~dpnxA" -o "%%~dpnA o.gif" 14 | "%~dp0\gifsicle.exe" -U "%%~dpnA o.gif" -o "%%~dpnA o.gif" 15 | @echo Cropping 16 | python "%~dp0\DAINAUTO_utils\crop_image.py" "%%~dpnA o.gif" 1 17 | 18 | "%~dp0\gifsicle.exe" --colors=255 "%%~dpnA o+1.gif" -o "%%~dpnA o+1.gif" 19 | "%~dp0\gifsicle.exe" -U "%%~dpnA o+1.gif" -o "%%~dpnA o+1.gif" 20 | 21 | 22 | python "%~dp0\DAINAUTO_utils\scale_image.py" "%%~dpnA o+1.gif" %interp% 23 | 24 | echo "Calling DAIN" 25 | 26 | "%~dp0..\DAINAPP.exe" --cli True -i "%%~dpnA o+1_X%interp%.gif" -o "%%~dpnA\\" --output_name "%%~nA.gif" --loop 1 --pallete 1 --interpolations %interp% --downsample_fps 50 --frame_handling 4 --split_size %split% --split_pad %padding% --alpha 2 --check_scene_change -1 --interpolation_algo 0 --audio_version 0 --clear_original_folder 1 --clear_interpolated_folder 0 --step_extract 1 --step_interpolate 1 --step_render 1 --clean_cache 1 27 | 28 | 29 | call "%~dp0\extract_undithered_interpolation_(drop source file here).bat" "%%~dpnxA" 30 | 31 | python "%~dp0\DAINAUTO_utils\scale_image.py" "%%~dpnA interp.gif" -%interp% +nearest 32 | 33 | python "%~dp0\DAINAUTO_utils\crop_image.py" "%%~dpnA interp_N%interp%.gif" -1 34 | 35 | "%~dp0\gifsicle.exe" --colors=255 "%%~dpnA interp_N%interp%+-1.gif" -o "%%~dpnA interp_N%interp%+-1.gif" 36 | "%~dp0\gifsicle.exe" -U "%%~dpnA interp_N%interp%+-1.gif" -o "%%~dpnA interp_N%interp%.gif" 37 | 38 | del "%%~dpnA interp_N%interp%+-1.gif" 39 | 40 | cd "%~dp0" 41 | 42 | del "%%~dpnA o%%~xA" 43 | del "%%~dpnA o+1%%~xA" 44 | del "%%~dpnA o+1_X%interp%%%~xA" 45 | move /Y "%%~dpnA interp.gif" "%%~dpnA\\%%~nA interp.gif" 46 | ) 47 | 48 | if "%main%"=="pixelsafe" ( 49 | pause 50 | ) 51 | 52 | -------------------------------------------------------------------------------- /pixelsafe_interpolation_4x.bat: -------------------------------------------------------------------------------- 1 | if "%main%"=="" ( 2 | set main=pixelsafe 3 | ) 4 | 5 | set interp=4 6 | set split=300 7 | set padding=100 8 | 9 | 10 | 11 | FOR %%A IN (%*) DO ( 12 | 13 | "%~dp0\gifsicle.exe" --colors=255 "%%~dpnxA" -o "%%~dpnA o.gif" 14 | "%~dp0\gifsicle.exe" -U "%%~dpnA o.gif" -o "%%~dpnA o.gif" 15 | @echo Cropping 16 | python "%~dp0\DAINAUTO_utils\crop_image.py" "%%~dpnA o.gif" 1 17 | 18 | "%~dp0\gifsicle.exe" --colors=255 "%%~dpnA o+1.gif" -o "%%~dpnA o+1.gif" 19 | "%~dp0\gifsicle.exe" -U "%%~dpnA o+1.gif" -o "%%~dpnA o+1.gif" 20 | 21 | 22 | python "%~dp0\DAINAUTO_utils\scale_image.py" "%%~dpnA o+1.gif" %interp% 23 | 24 | echo "Calling DAIN" 25 | 26 | "%~dp0..\DAINAPP.exe" --cli True -i "%%~dpnA o+1_X%interp%.gif" -o "%%~dpnA\\" --output_name "%%~nA.gif" --loop 1 --pallete 1 --interpolations %interp% --downsample_fps 50 --frame_handling 4 --split_size %split% --split_pad %padding% --alpha 2 --check_scene_change -1 --interpolation_algo 0 --audio_version 0 --clear_original_folder 1 --clear_interpolated_folder 0 --step_extract 1 --step_interpolate 1 --step_render 1 --clean_cache 1 27 | 28 | 29 | call "%~dp0\extract_undithered_interpolation_(drop source file here).bat" "%%~dpnxA" 30 | 31 | python "%~dp0\DAINAUTO_utils\scale_image.py" "%%~dpnA interp.gif" -%interp% +nearest 32 | 33 | python "%~dp0\DAINAUTO_utils\crop_image.py" "%%~dpnA interp_N%interp%.gif" -1 34 | 35 | "%~dp0\gifsicle.exe" --colors=255 "%%~dpnA interp_N%interp%+-1.gif" -o "%%~dpnA interp_N%interp%+-1.gif" 36 | "%~dp0\gifsicle.exe" -U "%%~dpnA interp_N%interp%+-1.gif" -o "%%~dpnA interp_N%interp%.gif" 37 | 38 | del "%%~dpnA interp_N%interp%+-1.gif" 39 | 40 | cd "%~dp0" 41 | 42 | del "%%~dpnA o%%~xA" 43 | del "%%~dpnA o+1%%~xA" 44 | del "%%~dpnA o+1_X%interp%%%~xA" 45 | move /Y "%%~dpnA interp.gif" "%%~dpnA\\%%~nA interp.gif" 46 | ) 47 | 48 | if "%main%"=="pixelsafe" ( 49 | pause 50 | ) 51 | 52 | -------------------------------------------------------------------------------- /pixelsafe_interpolation_8x.bat: -------------------------------------------------------------------------------- 1 | if "%main%"=="" ( 2 | set main=pixelsafe 3 | ) 4 | 5 | set interp=8 6 | set split=300 7 | set padding=100 8 | 9 | 10 | 11 | FOR %%A IN (%*) DO ( 12 | 13 | "%~dp0\gifsicle.exe" --colors=255 "%%~dpnxA" -o "%%~dpnA o.gif" 14 | "%~dp0\gifsicle.exe" -U "%%~dpnA o.gif" -o "%%~dpnA o.gif" 15 | @echo Cropping 16 | python "%~dp0\DAINAUTO_utils\crop_image.py" "%%~dpnA o.gif" 1 17 | 18 | "%~dp0\gifsicle.exe" --colors=255 "%%~dpnA o+1.gif" -o "%%~dpnA o+1.gif" 19 | "%~dp0\gifsicle.exe" -U "%%~dpnA o+1.gif" -o "%%~dpnA o+1.gif" 20 | 21 | 22 | python "%~dp0\DAINAUTO_utils\scale_image.py" "%%~dpnA o+1.gif" %interp% 23 | 24 | echo "Calling DAIN" 25 | 26 | "%~dp0..\DAINAPP.exe" --cli True -i "%%~dpnA o+1_X%interp%.gif" -o "%%~dpnA\\" --output_name "%%~nA.gif" --loop 1 --pallete 1 --interpolations %interp% --downsample_fps 50 --frame_handling 4 --split_size %split% --split_pad %padding% --alpha 2 --check_scene_change -1 --interpolation_algo 0 --audio_version 0 --clear_original_folder 1 --clear_interpolated_folder 0 --step_extract 1 --step_interpolate 1 --step_render 1 --clean_cache 1 27 | 28 | 29 | call "%~dp0\extract_undithered_interpolation_(drop source file here).bat" "%%~dpnxA" 30 | 31 | python "%~dp0\DAINAUTO_utils\scale_image.py" "%%~dpnA interp.gif" -%interp% +nearest 32 | 33 | python "%~dp0\DAINAUTO_utils\crop_image.py" "%%~dpnA interp_N%interp%.gif" -1 34 | 35 | "%~dp0\gifsicle.exe" --colors=255 "%%~dpnA interp_N%interp%+-1.gif" -o "%%~dpnA interp_N%interp%+-1.gif" 36 | "%~dp0\gifsicle.exe" -U "%%~dpnA interp_N%interp%+-1.gif" -o "%%~dpnA interp_N%interp%.gif" 37 | 38 | del "%%~dpnA interp_N%interp%+-1.gif" 39 | 40 | cd "%~dp0" 41 | 42 | del "%%~dpnA o%%~xA" 43 | del "%%~dpnA o+1%%~xA" 44 | del "%%~dpnA o+1_X%interp%%%~xA" 45 | move /Y "%%~dpnA interp.gif" "%%~dpnA\\%%~nA interp.gif" 46 | ) 47 | 48 | if "%main%"=="pixelsafe" ( 49 | pause 50 | ) 51 | 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /DAINAUTO_utils/create_gif_from_here.py: -------------------------------------------------------------------------------- 1 | import os 2 | from PIL import Image 3 | 4 | def create_gif_from_folder(allow_dropped_frames=0): 5 | #Default duration for when there is not actually a duration name in the file 6 | #Allow_dropped_frames=1/True for when frames are 10ms (remove one out of two 10ms frames) 7 | #Allow_dropped_frames=2 to sacrifice as many frames as there is missing time (when interpolated too much) 8 | images = list() 9 | durations = list() 10 | previous_time = 0 11 | borrowed_time = 0 12 | total=0 13 | dropped = 0 14 | added = 0 15 | duration = 0 16 | transparencies = [] 17 | for file in os.listdir(os.getcwd()): 18 | if file[0]=="0" and file.endswith(".gif") or file.endswith(".png"): 19 | total+=1 20 | im = Image.open(file) 21 | if(im.mode!="P"): 22 | print(im.mode) 23 | im=im.quantize(colors=255, method=2, kmeans=0, dither=0) 24 | #print(im.getpalette()[0:20]) 25 | transparencies.append(im.info.get("transparency",None)) 26 | 27 | try: #Load durations from timestamps 28 | time = int(file.split(".")[-2]) 29 | if(time!=0): #Not the first frame 30 | duration = time-previous_time 31 | previous_time = time 32 | if(duration==1): 33 | #Mode 1 or 2, not an actual duration 34 | duration = default_duration 35 | elif(allow_dropped_frames): 36 | #Try to deal with framerate>50 by dropping frames 37 | duration-=borrowed_time 38 | if(duration<=0): #As of now, don't sacrifice more than one frame (I suppose no input over 100fps) 39 | if(allow_dropped_frames>1): 40 | borrowed_time=-duration #Still longing 41 | print(borrowed_time) 42 | else: 43 | borrowed_time=0 #Sacrifice accepted 44 | dropped+=1 45 | continue #A real gestion of that would evenly space out the kept frames 46 | elif(duration<20): 47 | #I suppose borrow at most 10 48 | borrowed_time = 20-duration 49 | duration=20 50 | else: 51 | borrowed_time=0 #No issue 52 | else: 53 | #Force to 50fps 54 | duration=max(20,duration) 55 | except Exception as e: 56 | duration = 20 57 | if(duration): 58 | durations.append(duration) 59 | images.append(im) 60 | added+=1 61 | # images[-1].show() 62 | #Puts the transparent color at index 255 so that I can simply pass 255 as transparency 63 | #It seems PIL reserves 255 for transparency anyway 64 | #Edit: but then optimize=True will make it so that there are less than 128 colors sometimes... so 0 is a safer bet 65 | print("Drop",dropped,"Add",added,"Total",total) 66 | durations+=[20]*(len(images)-len(durations)) #Fill the missing durations. Might get fixed if not using timestamps but actual values... 67 | #durations[-1]=1600 68 | # for im in images: 69 | # try: 70 | # del im.info['transparency'] 71 | # except: pass 72 | print(len(images),len(durations)) 73 | print(durations) 74 | print(transparencies) 75 | #optimize=True will only try to reduce the size of the palette 76 | images[0].save("output.gif", "GIF", save_all=True,append_images=images[1:], optimize=False,disposal=2, duration=durations, loop=0) 77 | 78 | create_gif_from_folder() -------------------------------------------------------------------------------- /DAINAUTO_utils/crop_image.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | import os.path 3 | import sys 4 | import statistics 5 | import numpy as np 6 | from gif_manips import get_background_color, swap_palette_colors 7 | 8 | def rindex(lst, value): 9 | #https://stackoverflow.com/questions/522372/finding-first-and-last-index-of-some-value-in-a-list-in-python 10 | for i, v in enumerate(reversed(lst)): 11 | if v == value: 12 | return len(lst) - i - 1 # return the index in the original list 13 | return None 14 | 15 | def generate_outname(filename,crop,borders): 16 | name,ext = os.path.splitext(filename) 17 | if("+" in name): 18 | plus = name.rindex("+") 19 | c=0 20 | numbers=0 21 | if(len(name)>plus): 22 | c = name[plus+1]=="c" 23 | 24 | for x in range(4): 25 | index = plus+c+x+1 26 | if(index1): 108 | if(transparency!=None): 109 | output[0].save(outname, save_all=True,append_images=output[1:], disposal=disposals, duration=durations, loop=0) 110 | else: 111 | output[0].save(outname, save_all=True,append_images=output[1:], disposal=disposals, duration=durations, loop=0) 112 | 113 | else: 114 | if(transparency!=None): 115 | output[0].save(outname) 116 | else: 117 | output[0].save(outname) 118 | 119 | def isint(arg): 120 | try: 121 | int(arg) 122 | return True 123 | except: 124 | return False 125 | 126 | if __name__ == "__main__": 127 | import sys 128 | if(len(sys.argv)>1): 129 | file = None 130 | border = 1 131 | crop=False 132 | 133 | files = list() 134 | for arg in sys.argv[1:]: 135 | if(isint(arg)): 136 | border = int(arg) 137 | elif(arg=="--crop"): 138 | crop=True 139 | else: 140 | files.append(arg) 141 | for file in files: 142 | crop_image(file,border,crop) 143 | if not files: 144 | exit("Please provide a file!") 145 | else: 146 | input("Usage: python crop_image.py [int:border] [--crop] files") 147 | -------------------------------------------------------------------------------- /DAINAUTO_utils/scale_image.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | import os.path 3 | import sys 4 | import statistics 5 | from gif_manips import swap_palette_colors 6 | 7 | 8 | def get_background_color(image): 9 | return statistics.mode((image.getpixel((0,0)),image.getpixel((-1,0)),image.getpixel((0,-1)),image.getpixel((-1,-1)))) 10 | 11 | def majority_resample(image,zoom): 12 | bgc=get_background_color(image) 13 | #image.show() 14 | #pal = image.getpalette() 15 | #image = image.convert("RGB") 16 | #image.show() 17 | w,h = image.size 18 | w,h=int(round(w*zoom)),int(round(h*zoom)) 19 | out = image.copy().crop((0,0,w,h))#Image.new(mode="RGB", size=(w,h)) 20 | orig_pixels=[[list() for y in range(h)] for x in range(w)] 21 | 22 | #print(image.width,image.height,w,h) 23 | for x in range(image.width): 24 | for y in range(image.height): 25 | X=min(int(x*zoom),w-1) 26 | Y=min(int(y*zoom),h-1) 27 | orig_pixels[X][Y].append(image.getpixel((x,y))) 28 | #print(X,Y,x,y,image.getpixel((x,y))) 29 | #print(zoom) 30 | #print(orig_pixels) 31 | for x in range(out.width): 32 | for y in range(out.height): 33 | try: 34 | #print(x,y,len(orig_pixels[x][y])) 35 | if(bgc in orig_pixels[x][y]): 36 | orig_pixels[x][y].remove(bgc) #remove one 37 | out.putpixel((x,y),statistics.mode(orig_pixels[x][y])) 38 | except Exception as e: 39 | print(e) 40 | pass 41 | #out = out.quantize(palette=pal,dither=0) 42 | return out 43 | 44 | 45 | def scale_file(filename,zoom,mode="mode"): 46 | im = Image.open(filename) 47 | transparency = im.info.get("transparency",None) 48 | if(zoom<0): 49 | zoom=-1/zoom 50 | name,extension = os.path.splitext(filename) 51 | output = list() 52 | 53 | durations = list() 54 | disposals = list() 55 | #print(im.tell()) 56 | try: 57 | while 1: 58 | w,h = im.size 59 | w,h=int(round(w*zoom)),int(round(h*zoom)) 60 | 61 | duration = im.info.get('duration', None) 62 | if(duration is not None): 63 | durations.append(duration) 64 | disposal = im.disposal_method 65 | if(disposal is not None): 66 | disposals.append(disposal) 67 | #print(w,h) 68 | #print(mode) 69 | if(zoom>=1 or mode=="nearest"): 70 | output.append(im.resize((w,h),resample=Image.NEAREST)) 71 | else: 72 | output.append(majority_resample(im,zoom)) 73 | 74 | 75 | 76 | im.seek(im.tell()+1) 77 | #print(im.tell()) 78 | # do something to im 79 | except EOFError: 80 | pass # end of sequence 81 | 82 | 83 | if(zoom<1 and 1/zoom==int(1/zoom)): 84 | zoomtext = "_D"+str(int(1/zoom)) 85 | if(mode=="nearest"): 86 | zoomtext = "_N"+str(int(1/zoom)) 87 | 88 | else: 89 | if(zoom==int(zoom)): 90 | zoom = int(zoom) 91 | zoomtext = "_X"+str(zoom) 92 | outname = name+zoomtext+extension 93 | #print(outname, file=sys.stdout) 94 | #Default: disposal=2 95 | #print(len(output),len(durations),len(disposals)) 96 | #print(durations) 97 | #print(disposals) 98 | for i in range(len(output)): 99 | out = output[i] 100 | tr = out.info.get('transparency', None) 101 | if(transparency!=None): 102 | #There exist at least one transparency 103 | if(tr!=None): 104 | out=swap_palette_colors(out,0,tr) 105 | else: 106 | out=swap_palette_colors(out,0,unused_color(out)) 107 | out.info["transparency"]=0 108 | output[i]=out 109 | if(len(output)>1): 110 | if(transparency!=None): 111 | output[0].save(outname, save_all=True,append_images=output[1:], disposal=2, transparency=0, duration=durations, loop=0) 112 | else: 113 | output[0].save(outname, save_all=True,append_images=output[1:], disposal=2, duration=durations, loop=0) 114 | 115 | else: 116 | if(transparency!=None): 117 | output[0].save(outname) 118 | else: 119 | output[0].save(outname) 120 | 121 | def is_float(value): 122 | try: 123 | float(value) 124 | return True 125 | except: 126 | return False 127 | 128 | if __name__ == "__main__": 129 | import sys 130 | #print(sys.argv) 131 | if(len(sys.argv)>1): 132 | file = None 133 | zoom = None 134 | mode="mode" 135 | 136 | for arg in sys.argv[1:]: 137 | if(is_float(arg)): 138 | zoom = float(arg) 139 | 140 | for arg in sys.argv[1:]: 141 | if(arg[0]=="+"): 142 | mode=arg[1:] 143 | 144 | if not zoom: 145 | exit("Please provide a zoom value!") 146 | for arg in sys.argv[1:]: 147 | if(is_float(arg)): 148 | zoom = float(arg) 149 | elif(arg[0]=="+"): 150 | mode=arg[1:] 151 | else: 152 | file=arg 153 | scale_file(file,zoom,mode) 154 | #Multiple files: multiple calls 155 | #Multiple zooms: applied in-order 156 | if not file: 157 | exit("Please provide a file!") 158 | else: 159 | input("Usage: python scalevalueFLOAT Negative filetoscalePNG/GIF \nvalues will scale to 1/value rather than flipping\nmultiple values and files accepted in-order") 160 | -------------------------------------------------------------------------------- /DAINAUTO_utils/gif_manips.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | import PIL 3 | import os.path 4 | import os 5 | import numpy as np 6 | alpha_limit = 130 7 | from statistics import mode 8 | 9 | def index_rgb_and_alpha(im,palette=None,transparency=0): 10 | #Save the transparency areas beforehand because quantizing doesn't always respect it 11 | if(im.mode=="RGB" or im.mode=="RGBA"): 12 | im2, mask = reduce_and_get_rgba_transparency_area(im) 13 | elif(im.mode=="P"): 14 | mask = get_palette_transparency_area(im) 15 | else: 16 | print("Unhandled image mode:",im.mode) 17 | 18 | im2=im.convert("RGB") #"only RGB or L mode images can be quantized to a palette" says PIL 19 | #No RGBA, hence why the transparency has to be handled elsewheere 20 | im2 = index_image(im2,palette) 21 | im2.info["transparency"]=None 22 | del im2.info["transparency"] 23 | #im2.show() 24 | if not(mask is None): 25 | tr=unused_color(palette) 26 | #Either works, but 255 is more likely to be displaced later 27 | if(tr==None): 28 | print("Palette too full for transparency! Using 255") 29 | tr=255 #TODO: merge an existing color 30 | #Put the transparent areas back in 31 | im2=reset_transparency(im2,mask,tr) 32 | im2=swap_palette_colors(im2,tr,transparency) 33 | im2.info["transparency"]=transparency 34 | # im2.show() 35 | return im2 36 | 37 | def remove_unused_color_from_palette(image): 38 | #The image will be turned into a palette square 39 | #[TODO] Gif:do it for every frame and group all the colors in one image 40 | palettedata = image.getpalette() 41 | data = np.array(image) 42 | 43 | uniquecolors = np.unique(data) 44 | if(image.info.get("transparency",None) != None): 45 | #Remove transparent, because we don't quantize with it 46 | tv = image.info.get("transparency") 47 | uniquecolors = uniquecolors[uniquecolors!=tv] 48 | #Optional: remove it from the frame too 49 | #Chose a color that is NOT tranparency, but exists otherwise 50 | fill = uniquecolors[0] 51 | data[data==tv]=fill 52 | 53 | uniquergb = [tuple(palettedata[x*3:x*3+3]) for x in uniquecolors] 54 | palettesize = len(uniquergb) 55 | newpalette = list() 56 | for rgb in uniquergb: 57 | newpalette.extend(rgb) 58 | while len(newpalette)<3*256: 59 | newpalette.extend(min(uniquergb)) #Fill with +/-darkest color 60 | 61 | # newpalette[0:palettesize] are the used colors 62 | # print("Unique colors:",len(uniquecolors)) 63 | # print(uniquecolors) 64 | image=image.resize((16,16)) 65 | data = np.array(image) 66 | for i in range(256): 67 | data[i//16][i%16]=min(i,len(uniquergb)-1) 68 | image=Image.fromarray(data) 69 | # image.info["transparency"]=255 #Rethink: Not sure for images that have 256 colors 70 | #Plus later quantization reserves one color anyway 71 | image.putpalette(newpalette) 72 | # image.show() 73 | return image 74 | 75 | def get_outline_color(image): 76 | #For pixel-art that has outline 77 | #Looks at the first pixels on every side 78 | bgc = get_background_color(image) 79 | colors = list() 80 | for j in range(image.height): 81 | for i in range(image.width): 82 | col = image.getpixel((i,j)) 83 | if(col)!=bgc: 84 | colors.append(col) 85 | break 86 | # print(colors) 87 | return mode(colors) #Majority wins 88 | 89 | def get_background_color(image): 90 | #For pixel-art with solid background 91 | #Looks at all the 4 corners 92 | return mode((image.getpixel((0,0)),image.getpixel((-1,0)),image.getpixel((0,-1)),image.getpixel((-1,-1)))) 93 | 94 | def unused_color(image): 95 | #Returns unused palette index 96 | #PIL's ImagePalette doesn't actually work, so instead doing it by hand 97 | data = np.array(image) 98 | uniquecolors = set(np.unique(data)) 99 | for i in range(256): 100 | if(i not in uniquecolors): 101 | return i 102 | return None 103 | 104 | def index_image(image,palette=None): 105 | #Does not care about alpha transparency: only use when transparency is not an issue 106 | if(palette!=None): 107 | #255: reserve one for transparency color (to rethink if there's already one) 108 | return image.quantize(colors=255, method=2, kmeans=0, palette=palette, dither=0) 109 | else: 110 | return image.quantize(colors=255, method=2, kmeans=0, dither=0) 111 | 112 | def reverse_black_blending(image): 113 | #Doesn't work. 114 | #https://stackoverflow.com/questions/2139350/what-is-the-formula-for-extracting-the-src-from-a-calculated-blendmode 115 | if(image.mode!=("RGBA")): 116 | print("Wrong image mode for this") 117 | return image 118 | data = np.array(image) 119 | 120 | width,height=image.size 121 | for x in range(width): 122 | for y in range(height): 123 | rn,gn,bn,alpha=data[y][x] 124 | if(alpha<255 and alpha>0): 125 | a=alpha/255 126 | data[y][x] = (min(int(round(rn/a)),255),min(int(round(gn/a)),255),min(int(round(bn/a)),255),255) 127 | result = Image.fromarray(data) 128 | result.show() 129 | return result 130 | 131 | def reduce_and_get_rgba_transparency_area(image,cutoff=alpha_limit): 132 | #Remove alpha transparency and makes a binary transparency mask 133 | if(image.mode=="RGB"): 134 | image=image.convert("RGBA") 135 | image=image.copy() 136 | data = np.array(image) 137 | alpha = data[:,:,3:] 138 | #Here: change data's rgb for alpha>alpha_limit 139 | alpha[alpha<=cutoff]=0 140 | alpha[alpha>cutoff]=255 141 | alphaonly = data[:,:,3].copy()//255 142 | #[TODO] try to fix the colors if they come from a black alpha overlay 143 | result = Image.fromarray(data) 144 | #print(np.unique(alphaonly)) 145 | return result, alphaonly 146 | 147 | def get_palette_transparency_area(image): 148 | if(image.mode!="P"): 149 | input("Wrong input type") 150 | image=image.copy() 151 | tr = image.info.get("transparency",None) 152 | br = image.info.get("background",None) 153 | data = np.array(image) 154 | #print(br,tr,data) #Heavily optimised gifs will have issues here 155 | #Because some transparency areas are inherited from previous frames but with a different palette 156 | #Create a transparency mask where 1 is solid and 0 is transparent 157 | if(tr==0): 158 | data[data!=tr]=1 159 | else: 160 | data[data!=tr]=0 161 | data[data==tr]=2 162 | data[data==0]=1 163 | data[data==2]=0 164 | print("Transparency mask:",data) 165 | return data 166 | 167 | def reset_transparency(pimage,mask,transparency=255): 168 | #Puts back the transparency areas on the image after quantization 169 | data = np.array(pimage) 170 | data = data*mask #Nullifies transparent areas 171 | mask = -(mask-1)*transparency 172 | maskimage = Image.fromarray(255+mask,"L") 173 | 174 | data = data+mask #Makes transparent area the transparency color 175 | result = Image.fromarray(data,"P") 176 | result.putpalette(pimage.getpalette()) 177 | result.info["transparency"]=transparency 178 | 179 | # maskimage.show() 180 | # result.show() 181 | return result 182 | 183 | def swap_palette_colors(image, source_id=None, target_id = 255): 184 | #Puts the source_id color at index target_id 185 | image=image.copy() 186 | palettedata = image.getpalette() 187 | if(source_id==None): 188 | source_id = get_background_color(image) #must be mode P to give an ID 189 | else: 190 | source_id = source_id 191 | if(source_id==target_id): 192 | return image 193 | 194 | source_index = source_id*3 195 | target_index = target_id*3 196 | #Swap the palette entries 197 | target_color = palettedata[target_index:target_index+3] 198 | source_color = palettedata[source_index:source_index+3] 199 | palettedata[target_index:target_index+3] = source_color 200 | palettedata[source_index:source_index+3] = target_color 201 | 202 | data = np.array(image) 203 | #Exchange the image areas 204 | source_mask = np.where(data==source_id,1,0) 205 | target_mask = np.where(data==target_id,1,0) 206 | data = np.where(source_mask==1,target_id,data) 207 | data = np.where(target_mask==1,source_id,data) 208 | 209 | result = Image.fromarray(data) 210 | result.putpalette(palettedata) 211 | return result 212 | -------------------------------------------------------------------------------- /DAINAUTO_utils/create_gif_from_here_adv.py: -------------------------------------------------------------------------------- 1 | import os 2 | from PIL import Image 3 | import numpy as np 4 | alpha_limit = 130 5 | 6 | def create_gif_from_folder(allow_dropped_frames=0): 7 | #Default duration for when there is not actually a duration name in the file 8 | #Allow_dropped_frames=1/True for when frames are 10ms (remove one out of two 10ms frames) 9 | #Allow_dropped_frames=2 to sacrifice as many frames as there is missing time (when interpolated too much) 10 | images = list() 11 | durations = list() 12 | previous_time = 0 13 | borrowed_time = 0 14 | total=0 15 | dropped = 0 16 | added = 0 17 | duration = 0 18 | transparencies = [] 19 | for file in os.listdir(os.getcwd()): 20 | if file[0]=="0" and file.endswith(".gif") or file.endswith(".png"): 21 | total+=1 22 | im = Image.open(file) 23 | if(im.mode!="P"): 24 | print(im.mode) 25 | print("D:",dir(im)) 26 | #input() 27 | 28 | im=im.convert(mode="P")#index_rgb_and_alpha(im,None,0) 29 | #print(im.getpalette()[0:20]) 30 | transparencies.append(im.info.get("transparency",None)) 31 | 32 | try: #Load durations from timestamps 33 | time = int(file.split(".")[-2]) 34 | if(time!=0): #Not the first frame 35 | duration = time-previous_time 36 | previous_time = time 37 | if(duration==1): 38 | #Mode 1 or 2, not an actual duration 39 | duration = default_duration 40 | elif(allow_dropped_frames): 41 | #Try to deal with framerate>50 by dropping frames 42 | duration-=borrowed_time 43 | if(duration<=0): #As of now, don't sacrifice more than one frame (I suppose no input over 100fps) 44 | if(allow_dropped_frames>1): 45 | borrowed_time=-duration #Still longing 46 | print(borrowed_time) 47 | else: 48 | borrowed_time=0 #Sacrifice accepted 49 | dropped+=1 50 | continue #A real gestion of that would evenly space out the kept frames 51 | elif(duration<20): 52 | #I suppose borrow at most 10 53 | borrowed_time = 20-duration 54 | duration=20 55 | else: 56 | borrowed_time=0 #No issue 57 | else: 58 | #Force to 50fps 59 | duration=max(20,duration) 60 | except Exception as e: 61 | duration = 20 62 | if(duration): 63 | durations.append(duration) 64 | images.append(im) 65 | added+=1 66 | # images[-1].show() 67 | #Puts the transparent color at index 255 so that I can simply pass 255 as transparency 68 | #It seems PIL reserves 255 for transparency anyway 69 | #Edit: but then optimize=True will make it so that there are less than 128 colors sometimes... so 0 is a safer bet 70 | print("Drop",dropped,"Add",added,"Total",total) 71 | durations+=[20]*(len(images)-len(durations)) #Fill the missing durations. Might get fixed if not using timestamps but actual values... 72 | #durations[-1]=1600 73 | # for im in images: 74 | # try: 75 | # del im.info['transparency'] 76 | # except: pass 77 | print(len(images),len(durations)) 78 | print(durations) 79 | print(transparencies) 80 | #optimize=True will only try to reduce the size of the palette 81 | images[0].save("output.gif", "GIF", save_all=True,append_images=images[1:], optimize=False,disposal=2, duration=durations, loop=0) 82 | 83 | def index_image(image,palette=None): 84 | #Does not care about alpha transparency: only use when transparency is not an issue 85 | if(palette!=None): 86 | #255: reserve one for transparency color (to rethink if there's already one) 87 | return image.quantize(colors=255, method=2, kmeans=0, palette=palette, dither=0) 88 | else: 89 | return image.quantize(colors=255, method=2, kmeans=0, dither=0) 90 | 91 | def index_rgb_and_alpha(im,palette=None,transparency=0): 92 | #Save the transparency areas beforehand because quantizing doesn't always respect it 93 | if(im.mode=="RGB" or im.mode=="RGBA"): 94 | im2, mask = reduce_and_get_rgba_transparency_area(im) 95 | elif(im.mode=="P"): 96 | mask = get_palette_transparency_area(im) 97 | else: 98 | print("Unhandled image mode:",im.mode) 99 | 100 | im2=im.convert("RGB") #"only RGB or L mode images can be quantized to a palette" says PIL 101 | #No RGBA, hence why the transparency has to be handled elsewheere 102 | im2 = index_image(im2,palette) 103 | if(palette==None): 104 | palette=im2 105 | im2.info["transparency"]=None 106 | del im2.info["transparency"] 107 | #im2.show() 108 | if not(mask is None): 109 | tr=unused_color(palette) 110 | #Either works, but 255 is more likely to be displaced later 111 | if(tr==None): 112 | print("Palette too full for transparency! Using 255") 113 | tr=255 #TODO: merge an existing color 114 | #Put the transparent areas back in 115 | im2=reset_transparency(im2,mask,tr) 116 | im2=swap_palette_colors(im2,tr,transparency) 117 | im2.info["transparency"]=transparency 118 | # im2.show() 119 | return im2 120 | def reduce_and_get_rgba_transparency_area(image,cutoff=alpha_limit): 121 | #Remove alpha transparency and makes a binary transparency mask 122 | if(image.mode=="RGB"): 123 | image=image.convert("RGBA") 124 | image=image.copy() 125 | data = np.array(image) 126 | alpha = data[:,:,3:] 127 | #Here: change data's rgb for alpha>alpha_limit 128 | alpha[alpha<=cutoff]=0 129 | alpha[alpha>cutoff]=255 130 | alphaonly = data[:,:,3].copy()//255 131 | #[TODO] try to fix the colors if they come from a black alpha overlay 132 | result = Image.fromarray(data) 133 | #print(np.unique(alphaonly)) 134 | return result, alphaonly 135 | def unused_color(image): 136 | #Returns unused palette index 137 | #PIL's ImagePalette doesn't actually work, so instead doing it by hand 138 | data = np.array(image) 139 | uniquecolors = set(np.unique(data)) 140 | for i in range(256): 141 | if(i not in uniquecolors): 142 | return i 143 | return None 144 | 145 | def get_palette_transparency_area(image): 146 | if(image.mode!="P"): 147 | input("Wrong input type") 148 | image=image.copy() 149 | tr = image.info.get("transparency",None) 150 | br = image.info.get("background",None) 151 | data = np.array(image) 152 | #print(br,tr,data) #Heavily optimised gifs will have issues here 153 | #Because some transparency areas are inherited from previous frames but with a different palette 154 | #Create a transparency mask where 1 is solid and 0 is transparent 155 | if(tr==0): 156 | data[data!=tr]=1 157 | else: 158 | data[data!=tr]=0 159 | data[data==tr]=2 160 | data[data==0]=1 161 | data[data==2]=0 162 | print("Transparency mask:",data) 163 | return data 164 | 165 | def reset_transparency(pimage,mask,transparency=255): 166 | #Puts back the transparency areas on the image after quantization 167 | data = np.array(pimage) 168 | data = data*mask #Nullifies transparent areas 169 | mask = -(mask-1)*transparency 170 | maskimage = Image.fromarray(255+mask,"L") 171 | 172 | data = data+mask #Makes transparent area the transparency color 173 | result = Image.fromarray(data,"P") 174 | result.putpalette(pimage.getpalette()) 175 | result.info["transparency"]=transparency 176 | 177 | # maskimage.show() 178 | # result.show() 179 | return result 180 | 181 | def swap_palette_colors(image, source_id=None, target_id = 255): 182 | #Puts the source_id color at index target_id 183 | image=image.copy() 184 | palettedata = image.getpalette() 185 | if(source_id==None): 186 | source_id = get_background_color(image) #must be mode P to give an ID 187 | else: 188 | source_id = source_id 189 | if(source_id==target_id): 190 | return image 191 | 192 | source_index = source_id*3 193 | target_index = target_id*3 194 | #Swap the palette entries 195 | target_color = palettedata[target_index:target_index+3] 196 | source_color = palettedata[source_index:source_index+3] 197 | palettedata[target_index:target_index+3] = source_color 198 | palettedata[source_index:source_index+3] = target_color 199 | 200 | data = np.array(image) 201 | #Exchange the image areas 202 | source_mask = np.where(data==source_id,1,0) 203 | target_mask = np.where(data==target_id,1,0) 204 | data = np.where(source_mask==1,target_id,data) 205 | data = np.where(target_mask==1,source_id,data) 206 | 207 | result = Image.fromarray(data) 208 | result.putpalette(palettedata) 209 | return result 210 | 211 | create_gif_from_folder() --------------------------------------------------------------------------------