|
10 | 10 |
|
11 | 11 | PILLOW_LOADED = True |
12 | 12 | except ModuleNotFoundError: |
13 | | - |
14 | | - class Image: |
15 | | - pass |
16 | | - |
17 | 13 | PILLOW_LOADED = False |
18 | 14 | from .rlottiecommon import LOTLayerNode, LOTMarkerList |
19 | 15 |
|
@@ -659,173 +655,166 @@ def lottie_configure_model_cache_size(self, cache_size: int): |
659 | 655 | self.rlottie_lib.lottie_configure_model_cache_size.restype = ctypes.c_void_p |
660 | 656 | self.rlottie_lib.lottie_configure_model_cache_size(ctypes.c_size_t(cache_size)) |
661 | 657 |
|
662 | | - def render_pillow_frame( |
663 | | - self, |
664 | | - frame_num: int = 0, |
665 | | - buffer_size: Optional[int] = None, |
666 | | - width: Optional[int] = None, |
667 | | - height: Optional[int] = None, |
668 | | - bytes_per_line: Optional[int] = None, |
669 | | - ) -> Image: |
670 | | - """ |
671 | | - Create Pillow Image at frame_num |
672 | | -
|
673 | | - :param int frame_num: the frame number needs to be rendered. |
674 | | - Defaults to 0. |
675 | | - :param Optional[int] buffer_size: size of surface buffer use for rendering |
676 | | - :param Optional[int] width: width of the surface |
677 | | - :param Optional[int] height: height of the surface |
678 | | - :param Optional[int] bytes_per_line: stride of the surface in bytes. |
679 | | -
|
680 | | - :return: rendered Pillow Image |
681 | | - :rtype: PIL.Image.Image |
682 | | - """ |
683 | | - if not PILLOW_LOADED: |
684 | | - raise ModuleNotFoundError("Pillow is required for this function.") |
685 | | - |
686 | | - if width == None or height == None: |
687 | | - width, height = self.lottie_animation_get_size() |
688 | | - |
689 | | - buffer = self.lottie_animation_render( |
690 | | - frame_num=frame_num, |
691 | | - buffer_size=buffer_size, |
692 | | - width=width, |
693 | | - height=height, |
694 | | - bytes_per_line=bytes_per_line, |
695 | | - ) |
696 | | - |
697 | | - im = Image.frombuffer("RGBA", (width, height), buffer, "raw", "BGRA") |
698 | | - |
699 | | - return im |
700 | | - |
701 | | - def save_frame( |
702 | | - self, |
703 | | - save_path: str, |
704 | | - frame_num: int = 0, |
705 | | - buffer_size: Optional[int] = None, |
706 | | - width: Optional[int] = None, |
707 | | - height: Optional[int] = None, |
708 | | - bytes_per_line: Optional[int] = None, |
709 | | - *args, |
710 | | - **kwargs, |
711 | | - ) -> Image: |
712 | | - """ |
713 | | - Save Image at frame_num to save_path |
714 | | -
|
715 | | - :param str save_path: path to save the Pillow Image |
716 | | - :param int frame_num: the frame number needs to be rendered. |
717 | | - Defaults to 0. |
718 | | - :param Optional[int] buffer_size: size of surface buffer use for rendering |
719 | | - :param Optional[int] width: width of the surface |
720 | | - :param Optional[int] height: height of the surface |
721 | | - :param Optional[int] bytes_per_line: stride of the surface in bytes. |
722 | | - :param *args: additional arguments passing to im.save() |
723 | | - :param **kwargs: additional arguments passing to im.save() |
724 | | -
|
725 | | - :return: rendered Pillow Image |
726 | | - :rtype: PIL.Image.Image |
727 | | - """ |
728 | | - if not PILLOW_LOADED: |
729 | | - raise ModuleNotFoundError("Pillow is required for this function.") |
730 | | - |
731 | | - im = self.render_pillow_frame( |
732 | | - frame_num=frame_num, |
733 | | - buffer_size=buffer_size, |
734 | | - width=width, |
735 | | - height=height, |
736 | | - bytes_per_line=bytes_per_line, |
737 | | - ) |
738 | | - im.save(save_path, *args, **kwargs) |
739 | | - |
740 | | - def save_animation( |
741 | | - self, |
742 | | - save_path: str, |
743 | | - fps: Optional[int] = None, |
744 | | - frame_num_start: Optional[int] = None, |
745 | | - frame_num_end: Optional[int] = None, |
746 | | - buffer_size: Optional[int] = None, |
747 | | - width: Optional[int] = None, |
748 | | - height: Optional[int] = None, |
749 | | - bytes_per_line: Optional[int] = None, |
750 | | - *args, |
751 | | - **kwargs, |
752 | | - ) -> Image: |
753 | | - """ |
754 | | - Save Image from frame_num_start to frame_num_end and save it to save_path. |
755 | | -
|
756 | | - It is possible to save animation as apng, gif or webp. |
| 658 | + if PILLOW_LOADED: |
| 659 | + |
| 660 | + def render_pillow_frame( |
| 661 | + self, |
| 662 | + frame_num: int = 0, |
| 663 | + buffer_size: Optional[int] = None, |
| 664 | + width: Optional[int] = None, |
| 665 | + height: Optional[int] = None, |
| 666 | + bytes_per_line: Optional[int] = None, |
| 667 | + ) -> Image.Image: |
| 668 | + """ |
| 669 | + Create Pillow Image at frame_num |
| 670 | +
|
| 671 | + :param int frame_num: the frame number needs to be rendered. |
| 672 | + Defaults to 0. |
| 673 | + :param Optional[int] buffer_size: size of surface buffer use for rendering |
| 674 | + :param Optional[int] width: width of the surface |
| 675 | + :param Optional[int] height: height of the surface |
| 676 | + :param Optional[int] bytes_per_line: stride of the surface in bytes. |
| 677 | +
|
| 678 | + :return: rendered Pillow Image |
| 679 | + :rtype: PIL.Image.Image |
| 680 | + """ |
| 681 | + if width == None or height == None: |
| 682 | + width, height = self.lottie_animation_get_size() |
| 683 | + |
| 684 | + buffer = self.lottie_animation_render( |
| 685 | + frame_num=frame_num, |
| 686 | + buffer_size=buffer_size, |
| 687 | + width=width, |
| 688 | + height=height, |
| 689 | + bytes_per_line=bytes_per_line, |
| 690 | + ) |
757 | 691 |
|
758 | | - For .gif, maximum framerate is capped at 50. |
| 692 | + im = Image.frombuffer("RGBA", (width, height), buffer, "raw", "BGRA") |
759 | 693 |
|
760 | | - Users may override this by specifying fps, at risk of breaking their gif. |
| 694 | + return im |
761 | 695 |
|
762 | | - :param str save_path: Path to save the Pillow Image |
763 | | - :param Optional[int] fps: Set fps of output image. |
764 | | - Will skip frames if lower than original. |
765 | | - :param Optional[int] frame_num_start: the starting frame number |
766 | | - needs to be rendered. |
767 | | - :param Optional[int] frame_num_end: the ending frame number |
768 | | - needs to be rendered. |
769 | | - :param Optional[int] buffer_size: size of surface buffer use for rendering |
770 | | - :param Optional[int] width: width of the surface |
771 | | - :param Optional[int] height: height of the surface |
772 | | - :param Optional[int] bytes_per_line: stride of the surface in bytes. |
773 | | - :param *args: additional arguments passing to im.save() |
774 | | - :param **kwargs: additional arguments passing to im.save() |
775 | | -
|
776 | | - :return: rendered Pillow Image |
777 | | - :rtype: PIL.Image.Image |
778 | | - """ |
779 | | - if not PILLOW_LOADED: |
780 | | - raise ModuleNotFoundError("Pillow is required for this function.") |
781 | | - |
782 | | - fps_orig = self.lottie_animation_get_framerate() |
783 | | - duration = self.lottie_animation_get_duration() |
784 | | - |
785 | | - export_ext = os.path.splitext(save_path)[-1].lower() |
786 | | - |
787 | | - if not fps: |
788 | | - fps = fps_orig |
789 | | - |
790 | | - # For .gif, maximum framerate is capped at 50 |
791 | | - # Users may override this by specifying fps, at risk of breaking their gif |
792 | | - # Reference: https://wunkolo.github.io/post/2020/02/buttery-smooth-10fps/ |
793 | | - if export_ext == ".gif" and fps_orig > 50: |
794 | | - fps = 50 |
795 | | - |
796 | | - if export_ext == ".gif" and kwargs.get("disposal") == None: |
797 | | - kwargs["disposal"] = 2 |
798 | | - |
799 | | - if kwargs.get("loop") == None: |
800 | | - kwargs["loop"] = 0 |
801 | | - |
802 | | - frames = int(duration * fps) |
803 | | - frame_duration = 1000 / fps |
804 | | - |
805 | | - if frame_num_start == None: |
806 | | - frame_num_start = 0 |
807 | | - if frame_num_end == None: |
808 | | - frame_num_end = frames |
809 | | - |
810 | | - im_list = [] |
811 | | - for frame in range(frame_num_start, frame_num_end): |
812 | | - pos = frame / frame_num_end |
813 | | - frame_num = self.lottie_animation_get_frame_at_pos(pos) |
814 | | - im_list.append( |
815 | | - self.render_pillow_frame( |
816 | | - frame_num=frame_num, |
817 | | - buffer_size=buffer_size, |
818 | | - width=width, |
819 | | - height=height, |
820 | | - bytes_per_line=bytes_per_line, |
821 | | - ).copy() |
| 696 | + def save_frame( |
| 697 | + self, |
| 698 | + save_path: str, |
| 699 | + frame_num: int = 0, |
| 700 | + buffer_size: Optional[int] = None, |
| 701 | + width: Optional[int] = None, |
| 702 | + height: Optional[int] = None, |
| 703 | + bytes_per_line: Optional[int] = None, |
| 704 | + *args, |
| 705 | + **kwargs, |
| 706 | + ) -> Image.Image: |
| 707 | + """ |
| 708 | + Save Image at frame_num to save_path |
| 709 | +
|
| 710 | + :param str save_path: path to save the Pillow Image |
| 711 | + :param int frame_num: the frame number needs to be rendered. |
| 712 | + Defaults to 0. |
| 713 | + :param Optional[int] buffer_size: size of surface buffer use for rendering |
| 714 | + :param Optional[int] width: width of the surface |
| 715 | + :param Optional[int] height: height of the surface |
| 716 | + :param Optional[int] bytes_per_line: stride of the surface in bytes. |
| 717 | + :param *args: additional arguments passing to im.save() |
| 718 | + :param **kwargs: additional arguments passing to im.save() |
| 719 | +
|
| 720 | + :return: rendered Pillow Image |
| 721 | + :rtype: PIL.Image.Image |
| 722 | + """ |
| 723 | + im = self.render_pillow_frame( |
| 724 | + frame_num=frame_num, |
| 725 | + buffer_size=buffer_size, |
| 726 | + width=width, |
| 727 | + height=height, |
| 728 | + bytes_per_line=bytes_per_line, |
822 | 729 | ) |
823 | | - |
824 | | - im_list[0].save( |
825 | | - save_path, |
826 | | - save_all=True, |
827 | | - append_images=im_list[1:], |
828 | | - duration=int(frame_duration), |
| 730 | + im.save(save_path, *args, **kwargs) |
| 731 | + |
| 732 | + def save_animation( |
| 733 | + self, |
| 734 | + save_path: str, |
| 735 | + fps: Optional[int] = None, |
| 736 | + frame_num_start: Optional[int] = None, |
| 737 | + frame_num_end: Optional[int] = None, |
| 738 | + buffer_size: Optional[int] = None, |
| 739 | + width: Optional[int] = None, |
| 740 | + height: Optional[int] = None, |
| 741 | + bytes_per_line: Optional[int] = None, |
829 | 742 | *args, |
830 | 743 | **kwargs, |
831 | | - ) |
| 744 | + ) -> Image.Image: |
| 745 | + """ |
| 746 | + Save Image from frame_num_start to frame_num_end and save it to save_path. |
| 747 | +
|
| 748 | + It is possible to save animation as apng, gif or webp. |
| 749 | +
|
| 750 | + For .gif, maximum framerate is capped at 50. |
| 751 | +
|
| 752 | + Users may override this by specifying fps, at risk of breaking their gif. |
| 753 | +
|
| 754 | + :param str save_path: Path to save the Pillow Image |
| 755 | + :param Optional[int] fps: Set fps of output image. |
| 756 | + Will skip frames if lower than original. |
| 757 | + :param Optional[int] frame_num_start: the starting frame number |
| 758 | + needs to be rendered. |
| 759 | + :param Optional[int] frame_num_end: the ending frame number |
| 760 | + needs to be rendered. |
| 761 | + :param Optional[int] buffer_size: size of surface buffer use for rendering |
| 762 | + :param Optional[int] width: width of the surface |
| 763 | + :param Optional[int] height: height of the surface |
| 764 | + :param Optional[int] bytes_per_line: stride of the surface in bytes. |
| 765 | + :param *args: additional arguments passing to im.save() |
| 766 | + :param **kwargs: additional arguments passing to im.save() |
| 767 | +
|
| 768 | + :return: rendered Pillow Image |
| 769 | + :rtype: PIL.Image.Image |
| 770 | + """ |
| 771 | + fps_orig = self.lottie_animation_get_framerate() |
| 772 | + duration = self.lottie_animation_get_duration() |
| 773 | + |
| 774 | + export_ext = os.path.splitext(save_path)[-1].lower() |
| 775 | + |
| 776 | + if not fps: |
| 777 | + fps = fps_orig |
| 778 | + |
| 779 | + # For .gif, maximum framerate is capped at 50 |
| 780 | + # Users may override this by specifying fps, at risk of breaking their gif |
| 781 | + # Reference: https://wunkolo.github.io/post/2020/02/buttery-smooth-10fps/ |
| 782 | + if export_ext == ".gif" and fps_orig > 50: |
| 783 | + fps = 50 |
| 784 | + |
| 785 | + if export_ext == ".gif" and kwargs.get("disposal") == None: |
| 786 | + kwargs["disposal"] = 2 |
| 787 | + |
| 788 | + if kwargs.get("loop") == None: |
| 789 | + kwargs["loop"] = 0 |
| 790 | + |
| 791 | + frames = int(duration * fps) |
| 792 | + frame_duration = 1000 / fps |
| 793 | + |
| 794 | + if frame_num_start == None: |
| 795 | + frame_num_start = 0 |
| 796 | + if frame_num_end == None: |
| 797 | + frame_num_end = frames |
| 798 | + |
| 799 | + im_list = [] |
| 800 | + for frame in range(frame_num_start, frame_num_end): |
| 801 | + pos = frame / frame_num_end |
| 802 | + frame_num = self.lottie_animation_get_frame_at_pos(pos) |
| 803 | + im_list.append( |
| 804 | + self.render_pillow_frame( |
| 805 | + frame_num=frame_num, |
| 806 | + buffer_size=buffer_size, |
| 807 | + width=width, |
| 808 | + height=height, |
| 809 | + bytes_per_line=bytes_per_line, |
| 810 | + ).copy() |
| 811 | + ) |
| 812 | + |
| 813 | + im_list[0].save( |
| 814 | + save_path, |
| 815 | + save_all=True, |
| 816 | + append_images=im_list[1:], |
| 817 | + duration=int(frame_duration), |
| 818 | + *args, |
| 819 | + **kwargs, |
| 820 | + ) |
0 commit comments