Movatterモバイル変換


[0]ホーム

URL:


ContentsMenuExpandLight modeDark modeAuto light/dark, in light modeAuto light/dark, in dark modeSkip to content
Pillow (PIL Fork) 12.0.0 documentation
Light LogoDark Logo
Pillow (PIL Fork) 12.0.0 documentation
Back to top

Source code for PIL.PngImagePlugin

## The Python Imaging Library.# $Id$## PNG support code## See "PNG (Portable Network Graphics) Specification, version 1.0;# W3C Recommendation", 1996-10-01, Thomas Boutell (ed.).## history:# 1996-05-06 fl   Created (couldn't resist it)# 1996-12-14 fl   Upgraded, added read and verify support (0.2)# 1996-12-15 fl   Separate PNG stream parser# 1996-12-29 fl   Added write support, added getchunks# 1996-12-30 fl   Eliminated circular references in decoder (0.3)# 1998-07-12 fl   Read/write 16-bit images as mode I (0.4)# 2001-02-08 fl   Added transparency support (from Zircon) (0.5)# 2001-04-16 fl   Don't close data source in "open" method (0.6)# 2004-02-24 fl   Don't even pretend to support interlaced files (0.7)# 2004-08-31 fl   Do basic sanity check on chunk identifiers (0.8)# 2004-09-20 fl   Added PngInfo chunk container# 2004-12-18 fl   Added DPI read support (based on code by Niki Spahiev)# 2008-08-13 fl   Added tRNS support for RGB images# 2009-03-06 fl   Support for preserving ICC profiles (by Florian Hoech)# 2009-03-08 fl   Added zTXT support (from Lowell Alleman)# 2009-03-29 fl   Read interlaced PNG files (from Conrado Porto Lopes Gouvua)## Copyright (c) 1997-2009 by Secret Labs AB# Copyright (c) 1996 by Fredrik Lundh## See the README file for information on usage and redistribution.#from__future__importannotationsimportitertoolsimportloggingimportreimportstructimportwarningsimportzlibfromenumimportIntEnumfromtypingimportIO,NamedTuple,castfrom.importImage,ImageChops,ImageFile,ImagePalette,ImageSequencefrom._binaryimporti16beasi16from._binaryimporti32beasi32from._binaryimporto8from._binaryimporto16beaso16from._binaryimporto32beaso32from._deprecateimportdeprecatefrom._utilimportDeferredErrorTYPE_CHECKING=FalseifTYPE_CHECKING:fromcollections.abcimportCallablefromtypingimportAny,NoReturnfrom.import_imaginglogger=logging.getLogger(__name__)is_cid=re.compile(rb"\w\w\w\w").match_MAGIC=b"\211PNG\r\n\032\n"_MODES={# supported bits/color combinations, and corresponding modes/rawmodes# Grayscale(1,0):("1","1"),(2,0):("L","L;2"),(4,0):("L","L;4"),(8,0):("L","L"),(16,0):("I;16","I;16B"),# Truecolour(8,2):("RGB","RGB"),(16,2):("RGB","RGB;16B"),# Indexed-colour(1,3):("P","P;1"),(2,3):("P","P;2"),(4,3):("P","P;4"),(8,3):("P","P"),# Grayscale with alpha(8,4):("LA","LA"),(16,4):("RGBA","LA;16B"),# LA;16B->LA not yet available# Truecolour with alpha(8,6):("RGBA","RGBA"),(16,6):("RGBA","RGBA;16B"),}_simple_palette=re.compile(b"^\xff*\x00\xff*$")MAX_TEXT_CHUNK=ImageFile.SAFEBLOCK"""Maximum decompressed size for a iTXt or zTXt chunk.Eliminates decompression bombs where compressed chunks can expand 1000x.See :ref:`Text in PNG File Format<png-text>`."""MAX_TEXT_MEMORY=64*MAX_TEXT_CHUNK"""Set the maximum total text chunk size.See :ref:`Text in PNG File Format<png-text>`."""# APNG frame disposal modes
[docs]classDisposal(IntEnum):OP_NONE=0""" No disposal is done on this frame before rendering the next frame. See :ref:`Saving APNG sequences<apng-saving>`. """OP_BACKGROUND=1""" This frame’s modified region is cleared to fully transparent black before rendering the next frame. See :ref:`Saving APNG sequences<apng-saving>`. """OP_PREVIOUS=2""" This frame’s modified region is reverted to the previous frame’s contents before rendering the next frame. See :ref:`Saving APNG sequences<apng-saving>`. """
# APNG frame blend modes
[docs]classBlend(IntEnum):OP_SOURCE=0""" All color components of this frame, including alpha, overwrite the previous output image contents. See :ref:`Saving APNG sequences<apng-saving>`. """OP_OVER=1""" This frame should be alpha composited with the previous output image contents. See :ref:`Saving APNG sequences<apng-saving>`. """
def_safe_zlib_decompress(s:bytes)->bytes:dobj=zlib.decompressobj()plaintext=dobj.decompress(s,MAX_TEXT_CHUNK)ifdobj.unconsumed_tail:msg="Decompressed data too large for PngImagePlugin.MAX_TEXT_CHUNK"raiseValueError(msg)returnplaintextdef_crc32(data:bytes,seed:int=0)->int:returnzlib.crc32(data,seed)&0xFFFFFFFF# --------------------------------------------------------------------# Support classes. Suitable for PNG and related formats like MNG etc.
[docs]classChunkStream:def__init__(self,fp:IO[bytes])->None:self.fp:IO[bytes]|None=fpself.queue:list[tuple[bytes,int,int]]|None=[]
[docs]defread(self)->tuple[bytes,int,int]:"""Fetch a new chunk. Returns header information."""cid=Noneassertself.fpisnotNoneifself.queue:cid,pos,length=self.queue.pop()self.fp.seek(pos)else:s=self.fp.read(8)cid=s[4:]pos=self.fp.tell()length=i32(s)ifnotis_cid(cid):ifnotImageFile.LOAD_TRUNCATED_IMAGES:msg=f"broken PNG file (chunk{repr(cid)})"raiseSyntaxError(msg)returncid,pos,length
def__enter__(self)->ChunkStream:returnselfdef__exit__(self,*args:object)->None:self.close()
[docs]defclose(self)->None:self.queue=self.fp=None
[docs]defpush(self,cid:bytes,pos:int,length:int)->None:assertself.queueisnotNoneself.queue.append((cid,pos,length))
[docs]defcall(self,cid:bytes,pos:int,length:int)->bytes:"""Call the appropriate chunk handler"""logger.debug("STREAM%r%s%s",cid,pos,length)returngetattr(self,f"chunk_{cid.decode('ascii')}")(pos,length)
[docs]defcrc(self,cid:bytes,data:bytes)->None:"""Read and verify checksum"""# Skip CRC checks for ancillary chunks if allowed to load truncated# images# 5th byte of first char is 1 [specs, section 5.4]ifImageFile.LOAD_TRUNCATED_IMAGESand(cid[0]>>5&1):self.crc_skip(cid,data)returnassertself.fpisnotNonetry:crc1=_crc32(data,_crc32(cid))crc2=i32(self.fp.read(4))ifcrc1!=crc2:msg=f"broken PNG file (bad header checksum in{repr(cid)})"raiseSyntaxError(msg)exceptstruct.errorase:msg=f"broken PNG file (incomplete checksum in{repr(cid)})"raiseSyntaxError(msg)frome
[docs]defcrc_skip(self,cid:bytes,data:bytes)->None:"""Read checksum"""assertself.fpisnotNoneself.fp.read(4)
[docs]defverify(self,endchunk:bytes=b"IEND")->list[bytes]:# Simple approach; just calculate checksum for all remaining# blocks. Must be called directly after open.cids=[]assertself.fpisnotNonewhileTrue:try:cid,pos,length=self.read()exceptstruct.errorase:msg="truncated PNG file"raiseOSError(msg)fromeifcid==endchunk:breakself.crc(cid,ImageFile._safe_read(self.fp,length))cids.append(cid)returncids
[docs]classiTXt(str):""" Subclass of string to allow iTXt chunks to look like strings while keeping their extra information """lang:str|bytes|Nonetkey:str|bytes|None
[docs]@staticmethoddef__new__(cls,text:str,lang:str|None=None,tkey:str|None=None)->iTXt:""" :param cls: the class to use when creating the instance :param text: value for this key :param lang: language code :param tkey: UTF-8 version of the key name """self=str.__new__(cls,text)self.lang=langself.tkey=tkeyreturnself
[docs]classPngInfo:""" PNG chunk container (for use with save(pnginfo=)) """def__init__(self)->None:self.chunks:list[tuple[bytes,bytes,bool]]=[]
[docs]defadd(self,cid:bytes,data:bytes,after_idat:bool=False)->None:"""Appends an arbitrary chunk. Use with caution. :param cid: a byte string, 4 bytes long. :param data: a byte string of the encoded data :param after_idat: for use with private chunks. Whether the chunk should be written after IDAT """self.chunks.append((cid,data,after_idat))
[docs]defadd_itxt(self,key:str|bytes,value:str|bytes,lang:str|bytes="",tkey:str|bytes="",zip:bool=False,)->None:"""Appends an iTXt chunk. :param key: latin-1 encodable text key name :param value: value for this key :param lang: language code :param tkey: UTF-8 version of the key name :param zip: compression flag """ifnotisinstance(key,bytes):key=key.encode("latin-1","strict")ifnotisinstance(value,bytes):value=value.encode("utf-8","strict")ifnotisinstance(lang,bytes):lang=lang.encode("utf-8","strict")ifnotisinstance(tkey,bytes):tkey=tkey.encode("utf-8","strict")ifzip:self.add(b"iTXt",key+b"\0\x01\0"+lang+b"\0"+tkey+b"\0"+zlib.compress(value),)else:self.add(b"iTXt",key+b"\0\0\0"+lang+b"\0"+tkey+b"\0"+value)
[docs]defadd_text(self,key:str|bytes,value:str|bytes|iTXt,zip:bool=False)->None:"""Appends a text chunk. :param key: latin-1 encodable text key name :param value: value for this key, text or an :py:class:`PIL.PngImagePlugin.iTXt` instance :param zip: compression flag """ifisinstance(value,iTXt):returnself.add_itxt(key,value,value.langifvalue.langisnotNoneelseb"",value.tkeyifvalue.tkeyisnotNoneelseb"",zip=zip,)# The tEXt chunk stores latin-1 textifnotisinstance(value,bytes):try:value=value.encode("latin-1","strict")exceptUnicodeError:returnself.add_itxt(key,value,zip=zip)ifnotisinstance(key,bytes):key=key.encode("latin-1","strict")ifzip:self.add(b"zTXt",key+b"\0\0"+zlib.compress(value))else:self.add(b"tEXt",key+b"\0"+value)
# --------------------------------------------------------------------# PNG image stream (IHDR/IEND)class_RewindState(NamedTuple):info:dict[str|tuple[int,int],Any]tile:list[ImageFile._Tile]seq_num:int|None
[docs]classPngStream(ChunkStream):def__init__(self,fp:IO[bytes])->None:super().__init__(fp)# local copies of Image attributesself.im_info:dict[str|tuple[int,int],Any]={}self.im_text:dict[str,str|iTXt]={}self.im_size=(0,0)self.im_mode=""self.im_tile:list[ImageFile._Tile]=[]self.im_palette:tuple[str,bytes]|None=Noneself.im_custom_mimetype:str|None=Noneself.im_n_frames:int|None=Noneself._seq_num:int|None=Noneself.rewind_state=_RewindState({},[],None)self.text_memory=0
[docs]defcheck_text_memory(self,chunklen:int)->None:self.text_memory+=chunklenifself.text_memory>MAX_TEXT_MEMORY:msg=("Too much memory used in text chunks: "f"{self.text_memory}>MAX_TEXT_MEMORY")raiseValueError(msg)
[docs]defsave_rewind(self)->None:self.rewind_state=_RewindState(self.im_info.copy(),self.im_tile,self._seq_num,)
[docs]defrewind(self)->None:self.im_info=self.rewind_state.info.copy()self.im_tile=self.rewind_state.tileself._seq_num=self.rewind_state.seq_num
[docs]defchunk_iCCP(self,pos:int,length:int)->bytes:# ICC profileassertself.fpisnotNones=ImageFile._safe_read(self.fp,length)# according to PNG spec, the iCCP chunk contains:# Profile name 1-79 bytes (character string)# Null separator 1 byte (null character)# Compression method 1 byte (0)# Compressed profile n bytes (zlib with deflate compression)i=s.find(b"\0")logger.debug("iCCP profile name%r",s[:i])comp_method=s[i+1]logger.debug("Compression method%s",comp_method)ifcomp_method!=0:msg=f"Unknown compression method{comp_method} in iCCP chunk"raiseSyntaxError(msg)try:icc_profile=_safe_zlib_decompress(s[i+2:])exceptValueError:ifImageFile.LOAD_TRUNCATED_IMAGES:icc_profile=Noneelse:raiseexceptzlib.error:icc_profile=None# FIXMEself.im_info["icc_profile"]=icc_profilereturns
[docs]defchunk_IHDR(self,pos:int,length:int)->bytes:# image headerassertself.fpisnotNones=ImageFile._safe_read(self.fp,length)iflength<13:ifImageFile.LOAD_TRUNCATED_IMAGES:returnsmsg="Truncated IHDR chunk"raiseValueError(msg)self.im_size=i32(s,0),i32(s,4)try:self.im_mode,self.im_rawmode=_MODES[(s[8],s[9])]exceptException:passifs[12]:self.im_info["interlace"]=1ifs[11]:msg="unknown filter category"raiseSyntaxError(msg)returns
[docs]defchunk_IDAT(self,pos:int,length:int)->NoReturn:# image dataif"bbox"inself.im_info:tile=[ImageFile._Tile("zip",self.im_info["bbox"],pos,self.im_rawmode)]else:ifself.im_n_framesisnotNone:self.im_info["default_image"]=Truetile=[ImageFile._Tile("zip",(0,0)+self.im_size,pos,self.im_rawmode)]self.im_tile=tileself.im_idat=lengthmsg="image data found"raiseEOFError(msg)
[docs]defchunk_IEND(self,pos:int,length:int)->NoReturn:msg="end of PNG image"raiseEOFError(msg)
[docs]defchunk_PLTE(self,pos:int,length:int)->bytes:# paletteassertself.fpisnotNones=ImageFile._safe_read(self.fp,length)ifself.im_mode=="P":self.im_palette="RGB",sreturns
[docs]defchunk_tRNS(self,pos:int,length:int)->bytes:# transparencyassertself.fpisnotNones=ImageFile._safe_read(self.fp,length)ifself.im_mode=="P":if_simple_palette.match(s):# tRNS contains only one full-transparent entry,# other entries are full opaquei=s.find(b"\0")ifi>=0:self.im_info["transparency"]=ielse:# otherwise, we have a byte string with one alpha value# for each palette entryself.im_info["transparency"]=selifself.im_modein("1","L","I;16"):self.im_info["transparency"]=i16(s)elifself.im_mode=="RGB":self.im_info["transparency"]=i16(s),i16(s,2),i16(s,4)returns
[docs]defchunk_gAMA(self,pos:int,length:int)->bytes:# gamma settingassertself.fpisnotNones=ImageFile._safe_read(self.fp,length)self.im_info["gamma"]=i32(s)/100000.0returns
[docs]defchunk_cHRM(self,pos:int,length:int)->bytes:# chromaticity, 8 unsigned ints, actual value is scaled by 100,000# WP x,y, Red x,y, Green x,y Blue x,yassertself.fpisnotNones=ImageFile._safe_read(self.fp,length)raw_vals=struct.unpack(f">{len(s)//4}I",s)self.im_info["chromaticity"]=tuple(elt/100000.0foreltinraw_vals)returns
[docs]defchunk_sRGB(self,pos:int,length:int)->bytes:# srgb rendering intent, 1 byte# 0 perceptual# 1 relative colorimetric# 2 saturation# 3 absolute colorimetricassertself.fpisnotNones=ImageFile._safe_read(self.fp,length)iflength<1:ifImageFile.LOAD_TRUNCATED_IMAGES:returnsmsg="Truncated sRGB chunk"raiseValueError(msg)self.im_info["srgb"]=s[0]returns
[docs]defchunk_pHYs(self,pos:int,length:int)->bytes:# pixels per unitassertself.fpisnotNones=ImageFile._safe_read(self.fp,length)iflength<9:ifImageFile.LOAD_TRUNCATED_IMAGES:returnsmsg="Truncated pHYs chunk"raiseValueError(msg)px,py=i32(s,0),i32(s,4)unit=s[8]ifunit==1:# meterdpi=px*0.0254,py*0.0254self.im_info["dpi"]=dpielifunit==0:self.im_info["aspect"]=px,pyreturns
[docs]defchunk_tEXt(self,pos:int,length:int)->bytes:# textassertself.fpisnotNones=ImageFile._safe_read(self.fp,length)try:k,v=s.split(b"\0",1)exceptValueError:# fallback for broken tEXt tagsk=sv=b""ifk:k_str=k.decode("latin-1","strict")v_str=v.decode("latin-1","replace")self.im_info[k_str]=vifk==b"exif"elsev_strself.im_text[k_str]=v_strself.check_text_memory(len(v_str))returns
[docs]defchunk_zTXt(self,pos:int,length:int)->bytes:# compressed textassertself.fpisnotNones=ImageFile._safe_read(self.fp,length)try:k,v=s.split(b"\0",1)exceptValueError:k=sv=b""ifv:comp_method=v[0]else:comp_method=0ifcomp_method!=0:msg=f"Unknown compression method{comp_method} in zTXt chunk"raiseSyntaxError(msg)try:v=_safe_zlib_decompress(v[1:])exceptValueError:ifImageFile.LOAD_TRUNCATED_IMAGES:v=b""else:raiseexceptzlib.error:v=b""ifk:k_str=k.decode("latin-1","strict")v_str=v.decode("latin-1","replace")self.im_info[k_str]=self.im_text[k_str]=v_strself.check_text_memory(len(v_str))returns
[docs]defchunk_iTXt(self,pos:int,length:int)->bytes:# international textassertself.fpisnotNoner=s=ImageFile._safe_read(self.fp,length)try:k,r=r.split(b"\0",1)exceptValueError:returnsiflen(r)<2:returnscf,cm,r=r[0],r[1],r[2:]try:lang,tk,v=r.split(b"\0",2)exceptValueError:returnsifcf!=0:ifcm==0:try:v=_safe_zlib_decompress(v)exceptValueError:ifImageFile.LOAD_TRUNCATED_IMAGES:returnselse:raiseexceptzlib.error:returnselse:returnsifk==b"XML:com.adobe.xmp":self.im_info["xmp"]=vtry:k_str=k.decode("latin-1","strict")lang_str=lang.decode("utf-8","strict")tk_str=tk.decode("utf-8","strict")v_str=v.decode("utf-8","strict")exceptUnicodeError:returnsself.im_info[k_str]=self.im_text[k_str]=iTXt(v_str,lang_str,tk_str)self.check_text_memory(len(v_str))returns
[docs]defchunk_eXIf(self,pos:int,length:int)->bytes:assertself.fpisnotNones=ImageFile._safe_read(self.fp,length)self.im_info["exif"]=b"Exif\x00\x00"+sreturns
# APNG chunks
[docs]defchunk_acTL(self,pos:int,length:int)->bytes:assertself.fpisnotNones=ImageFile._safe_read(self.fp,length)iflength<8:ifImageFile.LOAD_TRUNCATED_IMAGES:returnsmsg="APNG contains truncated acTL chunk"raiseValueError(msg)ifself.im_n_framesisnotNone:self.im_n_frames=Nonewarnings.warn("Invalid APNG, will use default PNG image if possible")returnsn_frames=i32(s)ifn_frames==0orn_frames>0x80000000:warnings.warn("Invalid APNG, will use default PNG image if possible")returnsself.im_n_frames=n_framesself.im_info["loop"]=i32(s,4)self.im_custom_mimetype="image/apng"returns
[docs]defchunk_fcTL(self,pos:int,length:int)->bytes:assertself.fpisnotNones=ImageFile._safe_read(self.fp,length)iflength<26:ifImageFile.LOAD_TRUNCATED_IMAGES:returnsmsg="APNG contains truncated fcTL chunk"raiseValueError(msg)seq=i32(s)if(self._seq_numisNoneandseq!=0)or(self._seq_numisnotNoneandself._seq_num!=seq-1):msg="APNG contains frame sequence errors"raiseSyntaxError(msg)self._seq_num=seqwidth,height=i32(s,4),i32(s,8)px,py=i32(s,12),i32(s,16)im_w,im_h=self.im_sizeifpx+width>im_worpy+height>im_h:msg="APNG contains invalid frames"raiseSyntaxError(msg)self.im_info["bbox"]=(px,py,px+width,py+height)delay_num,delay_den=i16(s,20),i16(s,22)ifdelay_den==0:delay_den=100self.im_info["duration"]=float(delay_num)/float(delay_den)*1000self.im_info["disposal"]=s[24]self.im_info["blend"]=s[25]returns
[docs]defchunk_fdAT(self,pos:int,length:int)->bytes:assertself.fpisnotNoneiflength<4:ifImageFile.LOAD_TRUNCATED_IMAGES:s=ImageFile._safe_read(self.fp,length)returnsmsg="APNG contains truncated fDAT chunk"raiseValueError(msg)s=ImageFile._safe_read(self.fp,4)seq=i32(s)ifself._seq_num!=seq-1:msg="APNG contains frame sequence errors"raiseSyntaxError(msg)self._seq_num=seqreturnself.chunk_IDAT(pos+4,length-4)
# --------------------------------------------------------------------# PNG readerdef_accept(prefix:bytes)->bool:returnprefix.startswith(_MAGIC)### Image plugin for PNG images.
[docs]classPngImageFile(ImageFile.ImageFile):format="PNG"format_description="Portable network graphics"def_open(self)->None:ifnot_accept(self.fp.read(8)):msg="not a PNG file"raiseSyntaxError(msg)self._fp=self.fpself.__frame=0## Parse headers up to the first IDAT or fDAT chunkself.private_chunks:list[tuple[bytes,bytes]|tuple[bytes,bytes,bool]]=[]self.png:PngStream|None=PngStream(self.fp)whileTrue:## get next chunkcid,pos,length=self.png.read()try:s=self.png.call(cid,pos,length)exceptEOFError:breakexceptAttributeError:logger.debug("%r%s%s (unknown)",cid,pos,length)s=ImageFile._safe_read(self.fp,length)ifcid[1:2].islower():self.private_chunks.append((cid,s))self.png.crc(cid,s)## Copy relevant attributes from the PngStream. An alternative# would be to let the PngStream class modify these attributes# directly, but that introduces circular references which are# difficult to break if things go wrong in the decoder...# (believe me, I've tried ;-)self._mode=self.png.im_modeself._size=self.png.im_sizeself.info=self.png.im_infoself._text:dict[str,str|iTXt]|None=Noneself.tile=self.png.im_tileself.custom_mimetype=self.png.im_custom_mimetypeself.n_frames=self.png.im_n_framesor1self.default_image=self.info.get("default_image",False)ifself.png.im_palette:rawmode,data=self.png.im_paletteself.palette=ImagePalette.raw(rawmode,data)ifcid==b"fdAT":self.__prepare_idat=length-4else:self.__prepare_idat=length# used by load_prepare()ifself.png.im_n_framesisnotNone:self._close_exclusive_fp_after_loading=Falseself.png.save_rewind()self.__rewind_idat=self.__prepare_idatself.__rewind=self._fp.tell()ifself.default_image:# IDAT chunk contains default image and not first animation frameself.n_frames+=1self._seek(0)self.is_animated=self.n_frames>1@propertydeftext(self)->dict[str,str|iTXt]:# experimentalifself._textisNone:# iTxt, tEXt and zTXt chunks may appear at the end of the file# So load the file to ensure that they are readifself.is_animated:frame=self.__frame# for APNG, seek to the final frame before loadingself.seek(self.n_frames-1)self.load()ifself.is_animated:self.seek(frame)assertself._textisnotNonereturnself._text
[docs]defverify(self)->None:"""Verify PNG file"""ifself.fpisNone:msg="verify must be called directly after open"raiseRuntimeError(msg)# back up to beginning of IDAT blockself.fp.seek(self.tile[0][2]-8)assertself.pngisnotNoneself.png.verify()self.png.close()ifself._exclusive_fp:self.fp.close()self.fp=None
[docs]defseek(self,frame:int)->None:ifnotself._seek_check(frame):returnifframe<self.__frame:self._seek(0,True)last_frame=self.__frameforfinrange(self.__frame+1,frame+1):try:self._seek(f)exceptEOFErrorase:self.seek(last_frame)msg="no more images in APNG file"raiseEOFError(msg)frome
def_seek(self,frame:int,rewind:bool=False)->None:assertself.pngisnotNoneifisinstance(self._fp,DeferredError):raiseself._fp.exself.dispose:_imaging.ImagingCore|Nonedispose_extent=Noneifframe==0:ifrewind:self._fp.seek(self.__rewind)self.png.rewind()self.__prepare_idat=self.__rewind_idatself._im=Noneself.info=self.png.im_infoself.tile=self.png.im_tileself.fp=self._fpself._prev_im=Noneself.dispose=Noneself.default_image=self.info.get("default_image",False)self.dispose_op=self.info.get("disposal")self.blend_op=self.info.get("blend")dispose_extent=self.info.get("bbox")self.__frame=0else:ifframe!=self.__frame+1:msg=f"cannot seek to frame{frame}"raiseValueError(msg)# ensure previous frame was loadedself.load()ifself.dispose:self.im.paste(self.dispose,self.dispose_extent)self._prev_im=self.im.copy()self.fp=self._fp# advance to the next frameifself.__prepare_idat:ImageFile._safe_read(self.fp,self.__prepare_idat)self.__prepare_idat=0frame_start=FalsewhileTrue:self.fp.read(4)# CRCtry:cid,pos,length=self.png.read()except(struct.error,SyntaxError):breakifcid==b"IEND":msg="No more images in APNG file"raiseEOFError(msg)ifcid==b"fcTL":ifframe_start:# there must be at least one fdAT chunk between fcTL chunksmsg="APNG missing frame data"raiseSyntaxError(msg)frame_start=Truetry:self.png.call(cid,pos,length)exceptUnicodeDecodeError:breakexceptEOFError:ifcid==b"fdAT":length-=4ifframe_start:self.__prepare_idat=lengthbreakImageFile._safe_read(self.fp,length)exceptAttributeError:logger.debug("%r%s%s (unknown)",cid,pos,length)ImageFile._safe_read(self.fp,length)self.__frame=frameself.tile=self.png.im_tileself.dispose_op=self.info.get("disposal")self.blend_op=self.info.get("blend")dispose_extent=self.info.get("bbox")ifnotself.tile:msg="image not found in APNG frame"raiseEOFError(msg)ifdispose_extent:self.dispose_extent:tuple[float,float,float,float]=dispose_extent# setup frame disposal (actual disposal done when needed in the next _seek())ifself._prev_imisNoneandself.dispose_op==Disposal.OP_PREVIOUS:self.dispose_op=Disposal.OP_BACKGROUNDself.dispose=Noneifself.dispose_op==Disposal.OP_PREVIOUS:ifself._prev_im:self.dispose=self._prev_im.copy()self.dispose=self._crop(self.dispose,self.dispose_extent)elifself.dispose_op==Disposal.OP_BACKGROUND:self.dispose=Image.core.fill(self.mode,self.size)self.dispose=self._crop(self.dispose,self.dispose_extent)
[docs]deftell(self)->int:returnself.__frame
[docs]defload_prepare(self)->None:"""internal: prepare to read PNG file"""ifself.info.get("interlace"):self.decoderconfig=self.decoderconfig+(1,)self.__idat=self.__prepare_idat# used by load_read()ImageFile.ImageFile.load_prepare(self)
[docs]defload_read(self,read_bytes:int)->bytes:"""internal: read more image data"""assertself.pngisnotNonewhileself.__idat==0:# end of chunk, skip forward to next oneself.fp.read(4)# CRCcid,pos,length=self.png.read()ifcidnotin[b"IDAT",b"DDAT",b"fdAT"]:self.png.push(cid,pos,length)returnb""ifcid==b"fdAT":try:self.png.call(cid,pos,length)exceptEOFError:passself.__idat=length-4# sequence_num has already been readelse:self.__idat=length# empty chunks are allowed# read more data from this chunkifread_bytes<=0:read_bytes=self.__idatelse:read_bytes=min(read_bytes,self.__idat)self.__idat=self.__idat-read_bytesreturnself.fp.read(read_bytes)
[docs]defload_end(self)->None:"""internal: finished reading image data"""assertself.pngisnotNoneifself.__idat!=0:self.fp.read(self.__idat)whileTrue:self.fp.read(4)# CRCtry:cid,pos,length=self.png.read()except(struct.error,SyntaxError):breakifcid==b"IEND":breakelifcid==b"fcTL"andself.is_animated:# start of the next frame, stop readingself.__prepare_idat=0self.png.push(cid,pos,length)breaktry:self.png.call(cid,pos,length)exceptUnicodeDecodeError:breakexceptEOFError:ifcid==b"fdAT":length-=4try:ImageFile._safe_read(self.fp,length)exceptOSErrorase:ifImageFile.LOAD_TRUNCATED_IMAGES:breakelse:raiseeexceptAttributeError:logger.debug("%r%s%s (unknown)",cid,pos,length)s=ImageFile._safe_read(self.fp,length)ifcid[1:2].islower():self.private_chunks.append((cid,s,True))self._text=self.png.im_textifnotself.is_animated:self.png.close()self.png=Noneelse:ifself._prev_imandself.blend_op==Blend.OP_OVER:updated=self._crop(self.im,self.dispose_extent)ifself.im.mode=="RGB"and"transparency"inself.info:mask=updated.convert_transparent("RGBA",self.info["transparency"])else:ifself.im.mode=="P"and"transparency"inself.info:t=self.info["transparency"]ifisinstance(t,bytes):updated.putpalettealphas(t)elifisinstance(t,int):updated.putpalettealpha(t)mask=updated.convert("RGBA")self._prev_im.paste(updated,self.dispose_extent,mask)self.im=self._prev_im
def_getexif(self)->dict[int,Any]|None:if"exif"notinself.info:self.load()if"exif"notinself.infoand"Raw profile type exif"notinself.info:returnNonereturnself.getexif()._get_merged_dict()
[docs]defgetexif(self)->Image.Exif:if"exif"notinself.info:self.load()returnsuper().getexif()
# --------------------------------------------------------------------# PNG writer_OUTMODES={# supported PIL modes, and corresponding rawmode, bit depth and color type"1":("1",b"\x01",b"\x00"),"L;1":("L;1",b"\x01",b"\x00"),"L;2":("L;2",b"\x02",b"\x00"),"L;4":("L;4",b"\x04",b"\x00"),"L":("L",b"\x08",b"\x00"),"LA":("LA",b"\x08",b"\x04"),"I":("I;16B",b"\x10",b"\x00"),"I;16":("I;16B",b"\x10",b"\x00"),"I;16B":("I;16B",b"\x10",b"\x00"),"P;1":("P;1",b"\x01",b"\x03"),"P;2":("P;2",b"\x02",b"\x03"),"P;4":("P;4",b"\x04",b"\x03"),"P":("P",b"\x08",b"\x03"),"RGB":("RGB",b"\x08",b"\x02"),"RGBA":("RGBA",b"\x08",b"\x06"),}
[docs]defputchunk(fp:IO[bytes],cid:bytes,*data:bytes)->None:"""Write a PNG chunk (including CRC field)"""byte_data=b"".join(data)fp.write(o32(len(byte_data))+cid)fp.write(byte_data)crc=_crc32(byte_data,_crc32(cid))fp.write(o32(crc))
class_idat:# wrap output from the encoder in IDAT chunksdef__init__(self,fp:IO[bytes],chunk:Callable[...,None])->None:self.fp=fpself.chunk=chunkdefwrite(self,data:bytes)->None:self.chunk(self.fp,b"IDAT",data)class_fdat:# wrap encoder output in fdAT chunksdef__init__(self,fp:IO[bytes],chunk:Callable[...,None],seq_num:int)->None:self.fp=fpself.chunk=chunkself.seq_num=seq_numdefwrite(self,data:bytes)->None:self.chunk(self.fp,b"fdAT",o32(self.seq_num),data)self.seq_num+=1class_Frame(NamedTuple):im:Image.Imagebbox:tuple[int,int,int,int]|Noneencoderinfo:dict[str,Any]def_write_multiple_frames(im:Image.Image,fp:IO[bytes],chunk:Callable[...,None],mode:str,rawmode:str,default_image:Image.Image|None,append_images:list[Image.Image],)->Image.Image|None:duration=im.encoderinfo.get("duration")loop=im.encoderinfo.get("loop",im.info.get("loop",0))disposal=im.encoderinfo.get("disposal",im.info.get("disposal",Disposal.OP_NONE))blend=im.encoderinfo.get("blend",im.info.get("blend",Blend.OP_SOURCE))ifdefault_image:chain=itertools.chain(append_images)else:chain=itertools.chain([im],append_images)im_frames:list[_Frame]=[]frame_count=0forim_seqinchain:forim_frameinImageSequence.Iterator(im_seq):ifim_frame.mode==mode:im_frame=im_frame.copy()else:im_frame=im_frame.convert(mode)encoderinfo=im.encoderinfo.copy()ifisinstance(duration,(list,tuple)):encoderinfo["duration"]=duration[frame_count]elifdurationisNoneand"duration"inim_frame.info:encoderinfo["duration"]=im_frame.info["duration"]ifisinstance(disposal,(list,tuple)):encoderinfo["disposal"]=disposal[frame_count]ifisinstance(blend,(list,tuple)):encoderinfo["blend"]=blend[frame_count]frame_count+=1ifim_frames:previous=im_frames[-1]prev_disposal=previous.encoderinfo.get("disposal")prev_blend=previous.encoderinfo.get("blend")ifprev_disposal==Disposal.OP_PREVIOUSandlen(im_frames)<2:prev_disposal=Disposal.OP_BACKGROUNDifprev_disposal==Disposal.OP_BACKGROUND:base_im=previous.im.copy()dispose=Image.core.fill("RGBA",im.size,(0,0,0,0))bbox=previous.bboxifbbox:dispose=dispose.crop(bbox)else:bbox=(0,0)+im.sizebase_im.paste(dispose,bbox)elifprev_disposal==Disposal.OP_PREVIOUS:base_im=im_frames[-2].imelse:base_im=previous.imdelta=ImageChops.subtract_modulo(im_frame.convert("RGBA"),base_im.convert("RGBA"))bbox=delta.getbbox(alpha_only=False)if(notbboxandprev_disposal==encoderinfo.get("disposal")andprev_blend==encoderinfo.get("blend")and"duration"inencoderinfo):previous.encoderinfo["duration"]+=encoderinfo["duration"]continueelse:bbox=Noneim_frames.append(_Frame(im_frame,bbox,encoderinfo))iflen(im_frames)==1andnotdefault_image:returnim_frames[0].im# animation controlchunk(fp,b"acTL",o32(len(im_frames)),# 0: num_frameso32(loop),# 4: num_plays)# default image IDAT (if it exists)ifdefault_image:ifim.mode!=mode:im=im.convert(mode)ImageFile._save(im,cast(IO[bytes],_idat(fp,chunk)),[ImageFile._Tile("zip",(0,0)+im.size,0,rawmode)],)seq_num=0forframe,frame_datainenumerate(im_frames):im_frame=frame_data.imifnotframe_data.bbox:bbox=(0,0)+im_frame.sizeelse:bbox=frame_data.bboxim_frame=im_frame.crop(bbox)size=im_frame.sizeencoderinfo=frame_data.encoderinfoframe_duration=int(round(encoderinfo.get("duration",0)))frame_disposal=encoderinfo.get("disposal",disposal)frame_blend=encoderinfo.get("blend",blend)# frame controlchunk(fp,b"fcTL",o32(seq_num),# sequence_numbero32(size[0]),# widtho32(size[1]),# heighto32(bbox[0]),# x_offseto32(bbox[1]),# y_offseto16(frame_duration),# delay_numeratoro16(1000),# delay_denominatoro8(frame_disposal),# dispose_opo8(frame_blend),# blend_op)seq_num+=1# frame dataifframe==0andnotdefault_image:# first frame must be in IDAT chunks for backwards compatibilityImageFile._save(im_frame,cast(IO[bytes],_idat(fp,chunk)),[ImageFile._Tile("zip",(0,0)+im_frame.size,0,rawmode)],)else:fdat_chunks=_fdat(fp,chunk,seq_num)ImageFile._save(im_frame,cast(IO[bytes],fdat_chunks),[ImageFile._Tile("zip",(0,0)+im_frame.size,0,rawmode)],)seq_num=fdat_chunks.seq_numreturnNonedef_save_all(im:Image.Image,fp:IO[bytes],filename:str|bytes)->None:_save(im,fp,filename,save_all=True)def_save(im:Image.Image,fp:IO[bytes],filename:str|bytes,chunk:Callable[...,None]=putchunk,save_all:bool=False,)->None:# save an image to disk (called by the save method)ifsave_all:default_image=im.encoderinfo.get("default_image",im.info.get("default_image"))modes=set()sizes=set()append_images=im.encoderinfo.get("append_images",[])forim_seqinitertools.chain([im],append_images):forim_frameinImageSequence.Iterator(im_seq):modes.add(im_frame.mode)sizes.add(im_frame.size)formodein("RGBA","RGB","P"):ifmodeinmodes:breakelse:mode=modes.pop()size=tuple(max(frame_size[i]forframe_sizeinsizes)foriinrange(2))else:size=im.sizemode=im.modeoutmode=modeifmode=="P":## attempt to minimize storage requirements for palette imagesif"bits"inim.encoderinfo:# number of bits specified by usercolors=min(1<<im.encoderinfo["bits"],256)else:# check palette contentsifim.palette:colors=max(min(len(im.palette.getdata()[1])//3,256),1)else:colors=256ifcolors<=16:ifcolors<=2:bits=1elifcolors<=4:bits=2else:bits=4outmode+=f";{bits}"# encoder optionsim.encoderconfig=(im.encoderinfo.get("optimize",False),im.encoderinfo.get("compress_level",-1),im.encoderinfo.get("compress_type",-1),im.encoderinfo.get("dictionary",b""),)# get the corresponding PNG modetry:rawmode,bit_depth,color_type=_OUTMODES[outmode]exceptKeyErrorase:msg=f"cannot write mode{mode} as PNG"raiseOSError(msg)fromeifoutmode=="I":deprecate("Saving I mode images as PNG",13,stacklevel=4)## write minimal PNG filefp.write(_MAGIC)chunk(fp,b"IHDR",o32(size[0]),# 0: sizeo32(size[1]),bit_depth,color_type,b"\0",# 10: compressionb"\0",# 11: filter categoryb"\0",# 12: interlace flag)chunks=[b"cHRM",b"cICP",b"gAMA",b"sBIT",b"sRGB",b"tIME"]icc=im.encoderinfo.get("icc_profile",im.info.get("icc_profile"))ificc:# ICC profile# according to PNG spec, the iCCP chunk contains:# Profile name 1-79 bytes (character string)# Null separator 1 byte (null character)# Compression method 1 byte (0)# Compressed profile n bytes (zlib with deflate compression)name=b"ICC Profile"data=name+b"\0\0"+zlib.compress(icc)chunk(fp,b"iCCP",data)# You must either have sRGB or iCCP.# Disallow sRGB chunks when an iCCP-chunk has been emitted.chunks.remove(b"sRGB")info=im.encoderinfo.get("pnginfo")ifinfo:chunks_multiple_allowed=[b"sPLT",b"iTXt",b"tEXt",b"zTXt"]forinfo_chunkininfo.chunks:cid,data=info_chunk[:2]ifcidinchunks:chunks.remove(cid)chunk(fp,cid,data)elifcidinchunks_multiple_allowed:chunk(fp,cid,data)elifcid[1:2].islower():# Private chunkafter_idat=len(info_chunk)==3andinfo_chunk[2]ifnotafter_idat:chunk(fp,cid,data)ifim.mode=="P":palette_byte_number=colors*3palette_bytes=im.im.getpalette("RGB")[:palette_byte_number]whilelen(palette_bytes)<palette_byte_number:palette_bytes+=b"\0"chunk(fp,b"PLTE",palette_bytes)transparency=im.encoderinfo.get("transparency",im.info.get("transparency",None))iftransparencyortransparency==0:ifim.mode=="P":# limit to actual palette sizealpha_bytes=colorsifisinstance(transparency,bytes):chunk(fp,b"tRNS",transparency[:alpha_bytes])else:transparency=max(0,min(255,transparency))alpha=b"\xff"*transparency+b"\0"chunk(fp,b"tRNS",alpha[:alpha_bytes])elifim.modein("1","L","I","I;16"):transparency=max(0,min(65535,transparency))chunk(fp,b"tRNS",o16(transparency))elifim.mode=="RGB":red,green,blue=transparencychunk(fp,b"tRNS",o16(red)+o16(green)+o16(blue))else:if"transparency"inim.encoderinfo:# don't bother with transparency if it's an RGBA# and it's in the info dict. It's probably just stale.msg="cannot use transparency for this mode"raiseOSError(msg)else:ifim.mode=="P"andim.im.getpalettemode()=="RGBA":alpha=im.im.getpalette("RGBA","A")alpha_bytes=colorschunk(fp,b"tRNS",alpha[:alpha_bytes])dpi=im.encoderinfo.get("dpi")ifdpi:chunk(fp,b"pHYs",o32(int(dpi[0]/0.0254+0.5)),o32(int(dpi[1]/0.0254+0.5)),b"\x01",)ifinfo:chunks=[b"bKGD",b"hIST"]forinfo_chunkininfo.chunks:cid,data=info_chunk[:2]ifcidinchunks:chunks.remove(cid)chunk(fp,cid,data)exif=im.encoderinfo.get("exif")ifexif:ifisinstance(exif,Image.Exif):exif=exif.tobytes(8)ifexif.startswith(b"Exif\x00\x00"):exif=exif[6:]chunk(fp,b"eXIf",exif)single_im:Image.Image|None=imifsave_all:single_im=_write_multiple_frames(im,fp,chunk,mode,rawmode,default_image,append_images)ifsingle_im:ImageFile._save(single_im,cast(IO[bytes],_idat(fp,chunk)),[ImageFile._Tile("zip",(0,0)+single_im.size,0,rawmode)],)ifinfo:forinfo_chunkininfo.chunks:cid,data=info_chunk[:2]ifcid[1:2].islower():# Private chunkafter_idat=len(info_chunk)==3andinfo_chunk[2]ifafter_idat:chunk(fp,cid,data)chunk(fp,b"IEND",b"")ifhasattr(fp,"flush"):fp.flush()# --------------------------------------------------------------------# PNG chunk converter
[docs]defgetchunks(im:Image.Image,**params:Any)->list[tuple[bytes,bytes,bytes]]:"""Return a list of PNG chunks representing this image."""fromioimportBytesIOchunks=[]defappend(fp:IO[bytes],cid:bytes,*data:bytes)->None:byte_data=b"".join(data)crc=o32(_crc32(byte_data,_crc32(cid)))chunks.append((cid,byte_data,crc))fp=BytesIO()try:im.encoderinfo=params_save(im,fp,"",append)finally:delim.encoderinforeturnchunks
# --------------------------------------------------------------------# RegistryImage.register_open(PngImageFile.format,PngImageFile,_accept)Image.register_save(PngImageFile.format,_save)Image.register_save_all(PngImageFile.format,_save_all)Image.register_extensions(PngImageFile.format,[".png",".apng"])Image.register_mime(PngImageFile.format,"image/png")
Copyright © 1995-2011 Fredrik Lundh and contributors, 2010 Jeffrey A. Clark and contributors.
Made withSphinx and@pradyunsg'sFuro

[8]
ページ先頭

©2009-2025 Movatter.jp