def handle_chunk_PLTE(self, chunk, length): # http://www.w3.org/TR/PNG/#11PLTE if length % 3 != 0: raise PNGReaderError( "PLTE chunk's length must be a multiple of 3.") if length > (2**self.bit_depth) * 3: raise PNGReaderError("PLTE chunk is too long.") if length == 0: raise PNGReaderError("Empty PLTE is not allowed.") # TODO: might be faster if we don't group, but makes the palette # interface harder to use self.plte = chunk
def validate_signature(self): """ Validates the signature, should only ever be called once. """ signature = self.fileobj.read(8) if signature != PNG_SIGNATURE: raise PNGReaderError("PNG file has invalid signature.")
def handle_chunk_tRNS(self, chunk, length): self.trns = chunk if self.colormap: if not self.palette: warnings.warn("PLTE chunk is required before tRNS chunk.") else: if length > len(self.palette): # Was warning, but promoted to Error as it # would otherwise cause pain later on. raise PNGReaderError("tRNS chunk is too long.") else: if self.alpha: raise PNGReaderError( "tRNS chunk is not valid with colour type %d." % self.color_type) try: self.transparent = struct.unpack("!%dH" % self.color_planes, chunk) except struct.error: raise PNGReaderError("tRNS chunk has incorrect length.")
def handle_chunk_bKGD(self, chunk, length): try: if self.colormap: if not self.palette: warnings.warn("PLTE chunk is required before bKGD chunk.") self.background = struct.unpack('B', chunk) else: self.background = struct.unpack("!%dH" % self.color_planes, chunk) except struct.error: raise PNGReaderError("bKGD chunk has incorrect length.")
def iter_chunks(self): while not self.done_reading: # chunk_length raw_chunk_length = self.fileobj.read(4) if not raw_chunk_length: raise NoChunkLength() if len(raw_chunk_length) != 4: raise InvalidChunkLength(raw_chunk_length) chunk_length = struct.unpack('!I', raw_chunk_length)[0] # chunk type raw_chunk_type = self.fileobj.read(4) if not raw_chunk_type: raise NoChunkType() if len(raw_chunk_type) != 4: raise InvalidChunkType(raw_chunk_type) bytes_chunk_type = struct.unpack('!4s', raw_chunk_type)[0] chunk_type = bytestostr(bytes_chunk_type) # sanity check if chunk_length > MAX_CHUNK_LENGTH: raise PNGReaderError('Chunk %s is too large: %d.' % (chunk_type, chunk_length)) # read data data = self.fileobj.read(chunk_length) if len(data) != chunk_length: raise ChunkError('Chunk %s too short for required %i octets.' % (chunk_type, chunk_length)) # read checksum checksum = self.fileobj.read(4) if len(checksum) != 4: raise ChunkError('Chunk %s checksum too short.' % chunk_type) verify = zlib.crc32(bytes_chunk_type) verify = zlib.crc32(data, verify) # Whether the output from zlib.crc32 is signed or not varies # according to hideous implementation details, see # http://bugs.python.org/issue1202 . # We coerce it to be positive here (in a way which works on # Python 2.3 and older). verify &= VERIFY_CONSTANT verify = struct.pack('!I', verify) if checksum != verify: # print repr(checksum) (a, ) = struct.unpack('!I', checksum) (b, ) = struct.unpack('!I', verify) raise ChunkError( "Checksum error in %s chunk: 0x%08X != 0x%08X." % (chunk_type, a, b)) yield chunk_type, chunk_length, data
def handle_chunk_sBIT(self, chunk, length): self.sbit = chunk if (self.colormap and length != 3 or not self.colormap and length != self.planes): raise PNGReaderError("sBIT chunk has incorrect length.")
def handle_chunk_gAMA(self, chunk, length): try: self.gamma = struct.unpack("!L", chunk)[0] / 100000.0 except struct.error: raise PNGReaderError("gAMA chunk has incorrect length.")
def handle_chunk_IHDR(self, chunk, length): # http://www.w3.org/TR/PNG/#11IHDR if length != 13: raise ChunkError( 'IHDR chunk has incorrect length %s, should be 13.' % length) (self.width, self.height, self.bit_depth, self.color_type, self.compression_method, self.filter_method, self.interlace_method) = struct.unpack("!2I5B", chunk) # Check that the header specifies only valid combinations. if self.bit_depth not in ALLOWED_BIT_DEPTHS: raise PNGReaderError("invalid bit depth %d" % self.bit_depth) if self.color_type not in ALLOWED_COLOR_TYPES: raise PNGReaderError("invalid colour type %d" % self.color_type) # Check indexed (palettized) images have 8 or fewer bits # per pixel; check only indexed or greyscale images have # fewer than 8 bits per pixel. if ((self.color_type & 1 and self.bit_depth > 8) or (self.bit_depth < 8 and self.color_type not in (0, 3))): raise PNGReaderError( "Illegal combination of bit depth (%d)" " and colour type (%d)." " See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 ." % (self.bit_depth, self.color_type)) if self.compression_method != 0: raise PNGReaderError("unknown compression method %d" % self.compression_method) if self.filter_method != 0: raise PNGReaderError( "Unknown filter method %d," " see http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters ." % self.filter_method) if self.interlace_method not in (0, 1): raise PNGReaderError( "Unknown interlace method %d," " see http://www.w3.org/TR/2003/REC-PNG-20031110/#8InterlaceMethods ." % self.interlace_method) self.pixelsize = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4, }[self.color_type] # Derived values # http://www.w3.org/TR/PNG/#6Colour-values colormap = bool(self.color_type & 1) greyscale = not (self.color_type & 2) alpha = bool(self.color_type & 4) if greyscale or colormap: color_planes = 1 else: color_planes = 3 planes = color_planes + alpha self.colormap = colormap self.greyscale = greyscale self.alpha = alpha self.mode = RGBA if self.alpha else RGB self.color_planes = color_planes self.planes = planes self.psize = fdiv(self.bit_depth, 8) * planes if int(self.psize) == self.psize: self.psize = int(self.psize) self.filter_unit = max(1, self.psize) self.row_bytes = int(math.ceil(self.width * self.psize)) # scanline stuff self.scanline = array.array('B') if self.bit_depth == 16: array_code = 'H' else: array_code = 'B' data = array.array(array_code, [0] * self.width * self.height * self.pixelsize) self.pixels = get_pixel_array(data, self.width, self.height, self.pixelsize) if self.interlace_method: self.adam7 = Adam7(self) self.scanline_length = self.adam7.get_scanline_length() self._process_scanline = self._process_interlaced_scanline else: self.previous_scanline = None self.scanline_length = self.row_bytes + 1 self.current_y = 0 self._process_scanline = self._process_straightlaced_scanline