def putpalette(self, data, rawmode="RGB"): """ Attaches a palette to this image. The image must be a "P" or "L" image, and the palette sequence must contain 768 integer values, where each group of three values represent the red, green, and blue values for the corresponding pixel index. Instead of an integer sequence, you can use an 8-bit string. :param data: A palette sequence (either a list or a string). """ from homework import Sjn_palette if self.mode not in ("L", "P"): raise ValueError("illegal image mode") self.load() if isinstance(data, ImagePalette.ImagePalette): palette = ImagePalette.raw(data.rawmode, data.palette) else: if not isinstance(data, bytes): if bytes is str: data = "".join(chr(x) for x in data) else: data = bytes(data) palette = ImagePalette.raw(rawmode, data) self.mode = "P" self.palette = palette self.palette.mode = "RGB" self.load() # install new palette
def _open(self): # header s = self.fp.read(128) if not _accept(s): raise SyntaxError("not a PCX file") # image bbox = i16(s, 4), i16(s, 6), i16(s, 8)+1, i16(s, 10)+1 if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]: raise SyntaxError("bad PCX image size") logger.debug("BBox: %s %s %s %s", *bbox) # format version = i8(s[1]) bits = i8(s[3]) planes = i8(s[65]) stride = i16(s, 66) logger.debug("PCX version %s, bits %s, planes %s, stride %s", version, bits, planes, stride) self.info["dpi"] = i16(s, 12), i16(s, 14) if bits == 1 and planes == 1: mode = rawmode = "1" elif bits == 1 and planes in (2, 4): mode = "P" rawmode = "P;%dL" % planes self.palette = ImagePalette.raw("RGB", s[16:64]) elif version == 5 and bits == 8 and planes == 1: mode = rawmode = "L" # FIXME: hey, this doesn't work with the incremental loader !!! self.fp.seek(-769, 2) s = self.fp.read(769) if len(s) == 769 and i8(s[0]) == 12: # check if the palette is linear greyscale for i in range(256): if s[i*3+1:i*3+4] != o8(i)*3: mode = rawmode = "P" break if mode == "P": self.palette = ImagePalette.raw("RGB", s[1:]) self.fp.seek(128) elif version == 5 and bits == 8 and planes == 3: mode = "RGB" rawmode = "RGB;L" else: raise IOError("unknown PCX mode") self.mode = mode self.size = bbox[2]-bbox[0], bbox[3]-bbox[1] bbox = (0, 0) + self.size logger.debug("size: %sx%s", *self.size) self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]
def seek(self, pos): if pos >= self._n_frames: raise EOFError('seek beyond the end of the sequence') if pos < 0: raise EOFError("negative frame index is not valid") self._pos = pos curimg = self._images[self._pos] mode = curimg.mode self.mode = 'RGB' if mode == 'RGBX' else mode self._size = curimg.width, curimg.height self.rawmode = mode self.info['timestamp'] = curimg.delay self.info['duration'] = 0 if self._pos: self.info['duration'] = self.info['timestamp'] - self._images[ self._pos - 1].delay self.info['depth'] = curimg.depth if self.fp is not None: self.fp.close() self.fp = io.BytesIO(curimg.raw) if self.mode == 'P': # TODO: alpha is lost here. self.palette = ImagePalette.raw('RGBX', curimg.palette) self.tile.clear() self.tile.append(('raw', (0, 0, *self.size), 0, self.rawmode)) if curimg.exif is None: self.info.pop('exif', None) else: self.info['exif'] = curimg.exif return self.load()
def _open(self): # check magic if self.fp.read(6) != _MAGIC: raise SyntaxError("not an XV thumbnail file") # Skip to beginning of next line self.fp.readline() # skip info comments while True: s = self.fp.readline() if not s: raise SyntaxError("Unexpected EOF reading XV thumbnail file") if s[0] != b'#': break # parse header line (already read) s = s.strip().split() self.mode = "P" self.size = int(s[0:1]), int(s[1:2]) self.palette = ImagePalette.raw("RGB", PALETTE) self.tile = [ ("raw", (0, 0)+self.size, self.fp.tell(), (self.mode, 0, 1) )]
def _open(self): # check magic if self.fp.read(6) != _MAGIC: raise SyntaxError("not an XV thumbnail file") # Skip to beginning of next line self.fp.readline() # skip info comments while True: s = self.fp.readline() if not s: raise SyntaxError("Unexpected EOF reading XV thumbnail file") if s[0] != b'#': break # parse header line (already read) s = s.strip().split() self.mode = "P" self.size = int(s[0:1]), int(s[1:2]) self.palette = ImagePalette.raw("RGB", PALETTE) self.tile = [("raw", (0, 0) + self.size, self.fp.tell(), (self.mode, 0, 1))]
def chunk_PLTE(self, offset, bytes): "PLTE -- palette data" s = self.fp.read(bytes) if self.mode == "P": self.palette = ImagePalette.raw("RGB", s) return s
def _open(self): # Screen s = self.fp.read(13) if s[:6] not in [b"GIF87a", b"GIF89a"]: raise SyntaxError("not a GIF file") self.info["version"] = s[:6] self.size = i16(s[6:]), i16(s[8:]) self.tile = [] flags = i8(s[10]) bits = (flags & 7) + 1 if flags & 128: # get global palette self.info["background"] = i8(s[11]) # check if palette contains colour indices p = self.fp.read(3 << bits) for i in range(0, len(p), 3): if not (i // 3 == i8(p[i]) == i8(p[i + 1]) == i8(p[i + 2])): p = ImagePalette.raw("RGB", p) self.global_palette = self.palette = p break self.__fp = self.fp # FIXME: hack self.__rewind = self.fp.tell() self.seek(0) # get ready to read first frame
def _open(self): # Screen s = self.fp.read(13) if s[:6] not in [b"GIF87a", b"GIF89a"]: raise SyntaxError("not a GIF file") self.info["version"] = s[:6] self.size = i16(s[6:]), i16(s[8:]) self.tile = [] flags = i8(s[10]) bits = (flags & 7) + 1 if flags & 128: # get global palette self.info["background"] = i8(s[11]) # check if palette contains colour indices p = self.fp.read(3 << bits) for i in range(0, len(p), 3): if not (i//3 == i8(p[i]) == i8(p[i+1]) == i8(p[i+2])): p = ImagePalette.raw("RGB", p) self.global_palette = self.palette = p break self.__fp = self.fp # FIXME: hack self.__rewind = self.fp.tell() self._n_frames = None self._is_animated = None self._seek(0) # get ready to read first frame
def _open(self): # check magic s = self.fp.read(6) if s != "P7 332": raise SyntaxError, "not an XV thumbnail file" # Skip to beginning of next line self.fp.readline() # skip info comments while 1: s = self.fp.readline() if not s: raise SyntaxError, "Unexpected EOF reading XV thumbnail file" if s[0] != '#': break # parse header line (already read) s = string.split(s.strip()) self.mode = "P" self.size = int(s[0]), int(s[1]) self.palette = ImagePalette.raw("RGB", PALETTE) self.tile = [ ("raw", (0, 0)+self.size, self.fp.tell(), (self.mode, 0, 1) )]
def test_rawmode_valueerrors(self): # Arrange palette = ImagePalette.raw("RGB", list(range(256))*3) # Act / Assert self.assertRaises(ValueError, palette.tobytes) self.assertRaises(ValueError, palette.getcolor, (1, 2, 3)) f = self.tempfile("temp.lut") self.assertRaises(ValueError, palette.save, f)
def test_rawmode_getdata(self): # Arrange data_in = list(range(256))*3 palette = ImagePalette.raw("RGB", data_in) # Act rawmode, data_out = palette.getdata() # Assert self.assertEqual(rawmode, "RGB") self.assertEqual(data_in, data_out)
def test_rawmode_getdata(): # Arrange data_in = list(range(256)) * 3 palette = ImagePalette.raw("RGB", data_in) # Act rawmode, data_out = palette.getdata() # Assert assert rawmode == "RGB" assert data_in == data_out
def test_rawmode_valueerrors(tmp_path): # Arrange palette = ImagePalette.raw("RGB", list(range(256)) * 3) # Act / Assert with pytest.raises(ValueError): palette.tobytes() with pytest.raises(ValueError): palette.getcolor((1, 2, 3)) f = str(tmp_path / "temp.lut") with pytest.raises(ValueError): palette.save(f)
def _open(self): # HEAD s = self.fp.read(128) magic = i16(s[4:6]) if not (magic in [0xAF11, 0xAF12] and i16(s[14:16]) in [0, 3] and # flags s[20:22] == b"\x00\x00"): # reserved raise SyntaxError("not an FLI/FLC file") # image characteristics self.mode = "P" self.size = i16(s[8:10]), i16(s[10:12]) # animation speed duration = i32(s[16:20]) if magic == 0xAF11: duration = (duration * 1000) / 70 self.info["duration"] = duration # look for palette palette = [(a, a, a) for a in range(256)] s = self.fp.read(16) self.__offset = 128 if i16(s[4:6]) == 0xF100: # prefix chunk; ignore it self.__offset = self.__offset + i32(s) s = self.fp.read(16) if i16(s[4:6]) == 0xF1FA: # look for palette chunk s = self.fp.read(6) if i16(s[4:6]) == 11: self._palette(palette, 2) elif i16(s[4:6]) == 4: self._palette(palette, 0) palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette] self.palette = ImagePalette.raw("RGB", b"".join(palette)) # set things up to decode first frame self.__frame = -1 self.__fp = self.fp self.__rewind = self.fp.tell() self._n_frames = None self._is_animated = None self.seek(0)
def _open(self): # HEAD s = self.fp.read(128) magic = i16(s[4:6]) if not (magic in [0xAF11, 0xAF12] and i16(s[14:16]) in [0, 3] and # flags s[20:22] == b"\x00\x00"): # reserved raise SyntaxError("not an FLI/FLC file") # image characteristics self.mode = "P" self.size = i16(s[8:10]), i16(s[10:12]) # animation speed duration = i32(s[16:20]) if magic == 0xAF11: duration = (duration * 1000) / 70 self.info["duration"] = duration # look for palette palette = [(a, a, a) for a in range(256)] s = self.fp.read(16) self.__offset = 128 if i16(s[4:6]) == 0xF100: # prefix chunk; ignore it self.__offset = self.__offset + i32(s) s = self.fp.read(16) if i16(s[4:6]) == 0xF1FA: # look for palette chunk s = self.fp.read(6) if i16(s[4:6]) == 11: self._palette(palette, 2) elif i16(s[4:6]) == 4: self._palette(palette, 0) palette = [o8(r)+o8(g)+o8(b) for (r, g, b) in palette] self.palette = ImagePalette.raw("RGB", b"".join(palette)) # set things up to decode first frame self.__frame = -1 self.__fp = self.fp self.__rewind = self.fp.tell() self._n_frames = None self._is_animated = None self.seek(0)
def _open(self): # Header s = self.fp.read(775) self.mode = "L" # FIXME: "P" self.size = i16(s[0:2]), i16(s[2:4]) # transparency index tindex = i16(s[5:7]) if tindex < 256: self.info["transparent"] = tindex self.palette = ImagePalette.raw("RGB", s[7:]) self.tile = [("raw", (0, 0) + self.size, 775, ("L", 0, -1))]
def test_file(self): palette = ImagePalette.ImagePalette("RGB", list(range(256))*3) f = self.tempfile("temp.lut") palette.save(f) p = ImagePalette.load(f) # load returns raw palette information self.assertEqual(len(p[0]), 768) self.assertEqual(p[1], "RGB") p = ImagePalette.raw(p[1], p[0]) self.assertIsInstance(p, ImagePalette.ImagePalette) self.assertEqual(p.palette, palette.tobytes())
def test_file(tmp_path): palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) f = str(tmp_path / "temp.lut") palette.save(f) p = ImagePalette.load(f) # load returns raw palette information assert len(p[0]) == 768 assert p[1] == "RGB" p = ImagePalette.raw(p[1], p[0]) assert isinstance(p, ImagePalette.ImagePalette) assert p.palette == palette.tobytes()
def _open(self): if self.fp.read(8) != _MAGIC: raise SyntaxError("not a PNG file") # # Parse headers up to the first IDAT chunk self.png = PngStream(self.fp) while True: # # get next chunk cid, pos, length = self.png.read() try: s = self.png.call(cid, pos, length) except EOFError: break except AttributeError: if Image.DEBUG: print(cid, pos, length, "(unknown)") s = ImageFile._safe_read(self.fp, length) 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_mode self.size = self.png.im_size self.info = self.png.im_info self.text = self.png.im_text # experimental self.tile = self.png.im_tile if self.png.im_palette: rawmode, data = self.png.im_palette self.palette = ImagePalette.raw(rawmode, data) self.__idat = length # used by load_read()
def _open(self): if self.fp.read(8) != _MAGIC: raise SyntaxError("not a PNG file") # # Parse headers up to the first IDAT chunk self.png = PngStream(self.fp) while True: # # get next chunk cid, pos, len = self.png.read() try: s = self.png.call(cid, pos, len) except EOFError: break except AttributeError: if Image.DEBUG: print(cid, pos, len, "(unknown)") s = ImageFile._safe_read(self.fp, len) 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_mode self.size = self.png.im_size self.info = self.png.im_info self.text = self.png.im_text # experimental self.tile = self.png.im_tile if self.png.im_palette: rawmode, data = self.png.im_palette self.palette = ImagePalette.raw(rawmode, data) self.__idat = len # used by load_read()
def _open(self): # HEAD s = self.fp.read(128) magic = i16(s[4:6]) if magic not in [0xAF11, 0xAF12]: raise SyntaxError, "not an FLI/FLC file" # image characteristics self.mode = "P" self.size = i16(s[8:10]), i16(s[10:12]) # animation speed duration = i32(s[16:20]) if magic == 0xAF11: duration = (duration * 1000) / 70 self.info["duration"] = duration # look for palette palette = map(lambda a: (a,a,a), range(256)) s = self.fp.read(16) self.__offset = 128 if i16(s[4:6]) == 0xF100: # prefix chunk; ignore it self.__offset = self.__offset + i32(s) s = self.fp.read(16) if i16(s[4:6]) == 0xF1FA: # look for palette chunk s = self.fp.read(6) if i16(s[4:6]) == 11: self._palette(palette, 2) elif i16(s[4:6]) == 4: self._palette(palette, 0) palette = map(lambda (r,g,b): chr(r)+chr(g)+chr(b), palette) self.palette = ImagePalette.raw("RGB", string.join(palette, "")) # set things up to decode first frame self.frame = -1 self.__fp = self.fp self.seek(0)
def test_write_with_raw_palette(self): palette = ImagePalette.raw('RGB', b'\x01\x02\x03\x04\x05\x06') img = SubImage8Bit(Image.new('P', (2, 2), color=1)) img.image.putpalette(palette) imgs = Images8Bit([img], palette=palette, width=9, height=8) buffer = BytesIO() save_8bit_sti(imgs, buffer) self.assertEqual(buffer.getvalue(), # Header b'STCI\x48\x00\x00\x00\x08\x00\x00\x00' + b'\x00\x00\x00\x00' + b'\x28\x00\x00\x00' + b'\x08\x00\x09\x00' + b'\x00\x01\x00\x00\x01\x00\x08\x08\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x08' + (3 * b'\x00') + b'\x00\x00\x00\x00' + (12 * b'\x00') + # Palette b'\x01\x02\x03' + b'\x04\x05\x06' + (254 * b'\x00\x00\x00') + # Sub Image Header b'\x00\x00\x00\x00' + b'\x08\x00\x00\x00' + b'\x00\x00' + b'\x00\x00' + b'\x02\x00' + b'\x02\x00' + # Data b'\x02\x01\x01\x00\x02\x01\x01\x00')
def _open(self): # HEAD s = self.fp.read(32) if i32(s) != 0x59a66a95: raise SyntaxError("not an SUN raster file") offset = 32 self.size = i32(s[4:8]), i32(s[8:12]) depth = i32(s[12:16]) if depth == 1: self.mode, rawmode = "1", "1;I" elif depth == 8: self.mode = rawmode = "L" elif depth == 24: self.mode, rawmode = "RGB", "BGR" else: raise SyntaxError("unsupported mode") compression = i32(s[20:24]) if i32(s[24:28]) != 0: length = i32(s[28:32]) offset = offset + length self.palette = ImagePalette.raw("RGB;L", self.fp.read(length)) if self.mode == "L": self.mode = rawmode = "P" stride = (((self.size[0] * depth + 7) // 8) + 3) & (~3) if compression == 1: self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride)) ] elif compression == 2: self.tile = [("sun_rle", (0, 0) + self.size, offset, rawmode)]
def _open(self): # HEAD s = self.fp.read(32) if i32(s) != 0x59a66a95: raise SyntaxError("not an SUN raster file") offset = 32 self.size = i32(s[4:8]), i32(s[8:12]) depth = i32(s[12:16]) if depth == 1: self.mode, rawmode = "1", "1;I" elif depth == 8: self.mode = rawmode = "L" elif depth == 24: self.mode, rawmode = "RGB", "BGR" else: raise SyntaxError("unsupported mode") compression = i32(s[20:24]) if i32(s[24:28]) != 0: length = i32(s[28:32]) offset = offset + length self.palette = ImagePalette.raw("RGB;L", self.fp.read(length)) if self.mode == "L": self.mode = rawmode = "P" stride = (((self.size[0] * depth + 7) // 8) + 3) & (~3) if compression == 1: self.tile = [ ("raw", (0, 0) + self.size, offset, (rawmode, stride))] elif compression == 2: self.tile = [("sun_rle", (0, 0) + self.size, offset, rawmode)]
def _open(self): # Quick rejection: if there's not an LF among the first # 100 bytes, this is (probably) not a text header. if b"\n" not in self.fp.read(100): raise InvalidFileType("not an IM file") self.fp.seek(0) n = 0 # Default values self.info[MODE] = "L" self.info[SIZE] = (512, 512) self.info[FRAMES] = 1 self.rawmode = "L" while True: s = self.fp.read(1) # Some versions of IFUNC uses \n\r instead of \r\n... if s == b"\r": continue if not s or s == b'\0' or s == b'\x1A': break # FIXME: this may read whole file if not a text file s = s + self.fp.readline() if len(s) > 100: raise InvalidFileType("not an IM file") if s[-2:] == b'\r\n': s = s[:-2] elif s[-1:] == b'\n': s = s[:-1] try: m = split.match(s) except re.error as v: raise InvalidFileType("not an IM file") if m: k, v = m.group(1, 2) # Don't know if this is the correct encoding, # but a decent guess (I guess) k = k.decode('latin-1', 'replace') v = v.decode('latin-1', 'replace') # Convert value as appropriate if k in [FRAMES, SCALE, SIZE]: v = v.replace("*", ",") v = tuple(map(number, v.split(","))) if len(v) == 1: v = v[0] elif k == MODE and v in OPEN: v, self.rawmode = OPEN[v] # Add to dictionary. Note that COMMENT tags are # combined into a list of strings. if k == COMMENT: if k in self.info: self.info[k].append(v) else: self.info[k] = [v] else: self.info[k] = v if k in TAGS: n += 1 else: s = s.decode("ascii", "replace") raise PILReadError("Bad IM header: %r" % (s)) if not n: raise InvalidFileType("Not an IM file") # Basic attributes self.size = self.info[SIZE] self.mode = self.info[MODE] # Skip forward to start of image data while s and s[0:1] != b'\x1A': s = self.fp.read(1) if not s: raise PILReadError("File truncated") if LUT in self.info: # convert lookup table to palette or lut attribute palette = self.fp.read(768) greyscale = 1 # greyscale palette linear = 1 # linear greyscale palette for i in range(256): if palette[i] == palette[i + 256] == palette[i + 512]: if i8(palette[i]) != i: linear = 0 else: greyscale = 0 if self.mode == "L" or self.mode == "LA": if greyscale: if not linear: self.lut = [i8(c) for c in palette[:256]] else: if self.mode == "L": self.mode = self.rawmode = "P" elif self.mode == "LA": self.mode = self.rawmode = "PA" self.palette = ImagePalette.raw("RGB;L", palette) elif self.mode == "RGB": if not greyscale or not linear: self.lut = [i8(c) for c in palette] self.frame = 0 self.__offset = offs = self.fp.tell() self.__fp = self.fp # FIXME: hack if self.rawmode[:2] == "F;": # ifunc95 formats try: # use bit decoder (if necessary) bits = int(self.rawmode[2:]) if bits not in [8, 16, 32]: self.tile = [("bit", (0, 0) + self.size, offs, (bits, 8, 3, 0, -1))] return except ValueError: pass if self.rawmode in ["RGB;T", "RYB;T"]: # Old LabEye/3PC files. Would be very surprised if anyone # ever stumbled upon such a file ;-) size = self.size[0] * self.size[1] self.tile = [ ("raw", (0, 0) + self.size, offs, ("G", 0, -1)), ("raw", (0, 0) + self.size, offs + size, ("R", 0, -1)), ("raw", (0, 0) + self.size, offs + 2 * size, ("B", 0, -1)) ] else: # LabEye/IFUNC files self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
def _setup(self): "Setup this image object based on current tags" if self.tag.has_key(0xBC01): raise IOError, "Windows Media Photo files not yet supported" getscalar = self.tag.getscalar # extract relevant tags self._compression = COMPRESSION_INFO[getscalar(COMPRESSION, 1)] self._planar_configuration = getscalar(PLANAR_CONFIGURATION, 1) # photometric is a required tag, but not everyone is reading # the specification photo = getscalar(PHOTOMETRIC_INTERPRETATION, 0) fillorder = getscalar(FILLORDER, 1) if Image.DEBUG: print "*** Summary ***" print "- compression:", self._compression print "- photometric_interpretation:", photo print "- planar_configuration:", self._planar_configuration print "- fill_order:", fillorder # size xsize = getscalar(IMAGEWIDTH) ysize = getscalar(IMAGELENGTH) self.size = xsize, ysize if Image.DEBUG: print "- size:", self.size format = getscalar(SAMPLEFORMAT, 1) # mode: check photometric interpretation and bits per pixel key = (photo, format, fillorder, self.tag.get(BITSPERSAMPLE, (1, )), self.tag.get(EXTRASAMPLES, ())) if Image.DEBUG: print "format key:", key try: self.mode, rawmode = OPEN_INFO[key] except KeyError: if Image.DEBUG: print "- unsupported format" raise SyntaxError, "unknown pixel mode" if Image.DEBUG: print "- raw mode:", rawmode print "- pil mode:", self.mode self.info["compression"] = self._compression # BEGIN PATCH xres = getscalar(X_RESOLUTION, (1, 1)) yres = getscalar(Y_RESOLUTION, (1, 1)) if xres and yres: xres = xres[0] / (xres[1] or 1) yres = yres[0] / (yres[1] or 1) resunit = getscalar(RESOLUTION_UNIT, 1) if resunit == 2: # Inches self.info["dpi"] = xres, yres elif resunit == 3: # Centimeters self.info["dpi"] = xres * 2.54, yres * 2.54 else: # No absolute unit of measurement. self.info["resolution"] = xres, yres # END PATCH # build tile descriptors x = y = l = 0 self.tile = [] if self.tag.has_key(STRIPOFFSETS): # striped image h = getscalar(ROWSPERSTRIP, ysize) w = self.size[0] a = None for o in self.tag[STRIPOFFSETS]: if not a: a = self._decoder(rawmode, l) self.tile.append( (self._compression, (0, min(y, ysize), w, min(y + h, ysize)), o, a)) y = y + h if y >= self.size[1]: x = y = 0 l = l + 1 a = None elif self.tag.has_key(324): # tiled image w = getscalar(322) h = getscalar(323) a = None for o in self.tag[324]: if not a: a = self._decoder(rawmode, l) # FIXME: this doesn't work if the image size # is not a multiple of the tile size... self.tile.append( (self._compression, (x, y, x + w, y + h), o, a)) x = x + w if x >= self.size[0]: x, y = 0, y + h if y >= self.size[1]: x = y = 0 l = l + 1 a = None else: if Image.DEBUG: print "- unsupported data organization" raise SyntaxError("unknown data organization") # fixup palette descriptor if self.mode == "P": palette = map(lambda a: chr(a / 256), self.tag[COLORMAP]) self.palette = ImagePalette.raw("RGB;L", string.join(palette, ""))
def _galx_info(self, header): """LiveMaker GAL/X multiframe (multi-layer) image.""" read = self.fp.read header += read(3) info = {} info["version"] = header[4:] if info["version"] == b"X200": header_size = i32le(read(4)) xml = zlib.decompress(read(header_size)) try: # Note: LiveMaker's code for generating GAL/X images sometimes # creates invalid XML, but setting recover=False should let # lxml deal with most of these cases. root = etree.fromstring( xml, parser=etree.XMLParser(encoding="shift-jis", recover=True)) except etree.LxmlError as e: raise GalImageError( "Could not parse GAL/X image XML metadata: {}".format(e)) info["width"] = int(root.get("Width", 0)) info["height"] = int(root.get("Height", 0)) info["bpp"] = int(root.get("Bpp", 0)) info["frame_count"] = int(root.get("Count", 0)) info["compression"] = int(root.get("CompType", 0)) info["compression_level"] = int(root.get("CompLevel", 0)) info["randomized"] = root.get("Randomized") != "0" info["bg_color"] = int(root.get("BGColor", 0)) info["block_width"] = int(root.get("BlockWidth", 0)) info["block_height"] = int(root.get("BlockHeight", 0)) info["offset"] = header_size + 12 info["root"] = root else: raise GalImageError("Unsupported GAL/X version {}".format(header)) if info["frame_count"] != len(root): print("Warning: frame count mismatch") info["frames"] = [] for frame in root: frame_info = {} if len(frame) > 1: print("Warning: Frame contained multiple Layers tags") frame_info["name"] = frame.get("Name", "") # TODO: figure out what this bounding box is actually for # left = int(frame.get('L0', 0)) # top = int(frame.get('T0', 0)) # right = int(frame.get('R0', info['width'])) # bottom = int(frame.get('B0', info['height'])) # frame_info['box'] = (left, top, right, bottom) frame_info["box"] = (0, 0, info["width"], info["height"]) for layers in frame: frame_info["width"] = int(layers.get("Width", info["width"])) frame_info["height"] = int(layers.get("Height", info["height"])) frame_info["bpp"] = int(layers.get("bpp", info["bpp"])) stride = (frame_info["width"] * frame_info["bpp"] + 7) // 8 if frame_info["bpp"] >= 8: # align to 4 byte boundary stride = (stride + 3) & ~3 if frame_info["bpp"] <= 8: for rgb in layers.iter("RGB"): palette = unhexlify(rgb.text) frame_info["palette"] = ImagePalette.raw( "BGR", palette) else: frame_info["palette"] = None frame_info["stride"] = stride frame_info["alpha_stride"] = (frame_info["width"] + 3) & ~3 frame_info["layers"] = [] for layer in layers.iter("Layer"): layer_info = {} left = int(layer.get("Left", 0)) top = int(layer.get("Top", 0)) layer_info["origin"] = (left, top) layer_info["trans_color"] = int(layer.get( "TransColor", -1)) layer_info["visible"] = int(layer.get("Visible", 1)) layer_info["alpha"] = int(layer.get("Alpha", 255)) layer_info["alpha_on"] = int(layer.get("AlphaOn", 0)) frame_info["layers"].append(layer_info) info["frames"].append(frame_info) return info
def _open(self): read = self.fp.read # # header s = read(26) if s[:4] != b"8BPS" or i16(s[4:]) != 1: raise SyntaxError("not a PSD file") psd_bits = i16(s[22:]) psd_channels = i16(s[12:]) psd_mode = i16(s[24:]) mode, channels = MODES[(psd_mode, psd_bits)] if channels > psd_channels: raise IOError("not enough channels") self.mode = mode self.size = i32(s[18:]), i32(s[14:]) # # color mode data size = i32(read(4)) if size: data = read(size) if mode == "P" and size == 768: self.palette = ImagePalette.raw("RGB;L", data) # # image resources self.resources = [] size = i32(read(4)) if size: # load resources end = self.fp.tell() + size while self.fp.tell() < end: signature = read(4) id = i16(read(2)) name = read(i8(read(1))) if not (len(name) & 1): read(1) # padding data = read(i32(read(4))) if (len(data) & 1): read(1) # padding self.resources.append((id, name, data)) if id == 1039: # ICC profile self.info["icc_profile"] = data # # layer and mask information self.layers = [] size = i32(read(4)) if size: end = self.fp.tell() + size size = i32(read(4)) if size: self.layers = _layerinfo(self.fp) self.fp.seek(end) # # image descriptor self.tile = _maketile(self.fp, mode, (0, 0) + self.size, channels) # keep the file open self._fp = self.fp self.frame = 0
def convert(self, mode=None, matrix=None, dither=None, palette=WEB, colors=256): """ The current version supports all possible conversions between "L", "RGB" and "CMYK." The **matrix** argument only supports "L" and "RGB". :param mode: The requested mode. :param matrix: An optional conversion matrix. If given, this should be 4- or 12-tuple containing floating point values. :param dither: Dithering method, used when converting from mode "RGB" to "P" or from "RGB" or "L" to "1". Available methods are NONE or FLOYDSTEINBERG (default). :param palette: Palette to use when converting from mode "RGB" to "P". Available palettes are WEB or ADAPTIVE. :param colors: Number of colors to use for the ADAPTIVE palette. Defaults to 256. """ if not mode: # determine default mode if self.mode == "P": self.load() if self.palette: mode = self.palette.mode else: mode = "RGB" else: return self.copy() self.load() if matrix: # matrix conversion if mode not in ("L", "RGB"): raise ValueError("illegal conversion") im = self.im.convert_matrix(mode, matrix) return self._new(im) if mode == "P" and self.mode == "RGBA": return self.quantize(colors) trns = None delete_trns = False # transparency handling if "transparency" in self.info and \ self.info['transparency'] is not None: if self.mode in ('L', 'RGB') and mode == 'RGBA': # Use transparent conversion to promote from transparent # color to an alpha channel. return self._new(self.im.convert_transparent( mode, self.info['transparency'])) elif self.mode in ('L', 'RGB', 'P') and mode in ('L', 'RGB', 'P'): t = self.info['transparency'] if isinstance(t, bytes): # Dragons. This can't be represented by a single color warnings.warn('Palette images with Transparency ' + ' expressed in bytes should be converted ' + 'to RGBA images') delete_trns = True else: # get the new transparency color. # use existing conversions trns_im = Image()._new(core.new(self.mode, (1, 1))) if self.mode == 'P': trns_im.putpalette(self.palette) if type(t) == tuple: try: t = trns_im.palette.getcolor(t) except: raise ValueError("Couldn't allocate a palette "+ "color for transparency") trns_im.putpixel((0, 0), t) if mode in ('L', 'RGB'): trns_im = trns_im.convert(mode) else: # can't just retrieve the palette number, got to do it # after quantization. trns_im = trns_im.convert('RGB') trns = trns_im.getpixel((0, 0)) elif self.mode == 'P' and mode == 'RGBA': t = self.info['transparency'] delete_trns = True if isinstance(t, bytes): self.im.putpalettealphas(t) elif isinstance(t, int): self.im.putpalettealpha(t, 0) else: raise ValueError("Transparency for P mode should" + " be bytes or int") if mode == "P" and palette == ADAPTIVE: im = self.im.quantize(colors) new = self._new(im) from homework import Sjn_palette new.palette = ImagePalette.raw("RGB", new.im.getpalette("RGB")) if delete_trns: # This could possibly happen if we requantize to fewer colors. # The transparency would be totally off in that case. del(new.info['transparency']) if trns is not None: try: new.info['transparency'] = new.palette.getcolor(trns) except: # if we can't make a transparent color, don't leave the old # transparency hanging around to mess us up. del(new.info['transparency']) warnings.warn("Couldn't allocate palette entry " + "for transparency") return new # colorspace conversion if dither is None: dither = FLOYDSTEINBERG try: im = self.im.convert(mode, dither) except ValueError: try: # normalize source image and try again im = self.im.convert(getmodebase(self.mode)) im = im.convert(mode, dither) except KeyError: raise ValueError("illegal conversion") new_im = self._new(im) if delete_trns: # crash fail if we leave a bytes transparency in an rgb/l mode. del(new_im.info['transparency']) if trns is not None: if new_im.mode == 'P': try: new_im.info['transparency'] = new_im.palette.getcolor(trns) except: del(new_im.info['transparency']) warnings.warn("Couldn't allocate palette entry " + "for transparency") else: new_im.info['transparency'] = trns return new_im
def _bitmap(self, header=0, offset=0): """ Read relevant info about the BMP """ read, seek = self.fp.read, self.fp.seek if header: seek(header) file_info = dict() file_info['header_size'] = i32( read(4) ) # read bmp header size @offset 14 (this is part of the header size) file_info['direction'] = -1 # --------------------- If requested, read header at a specific position header_data = ImageFile._safe_read( self.fp, file_info['header_size'] - 4) # read the rest of the bmp header, without its size # --------------------------------------------------- IBM OS/2 Bitmap v1 # ------ This format has different offsets because of width/height types if file_info['header_size'] == 12: file_info['width'] = i16(header_data[0:2]) file_info['height'] = i16(header_data[2:4]) file_info['planes'] = i16(header_data[4:6]) file_info['bits'] = i16(header_data[6:8]) file_info['compression'] = self.RAW file_info['palette_padding'] = 3 # ---------------------------------------------- Windows Bitmap v2 to v5 elif file_info['header_size'] in (40, 64, 108, 124): # v3, OS/2 v2, v4, v5 if file_info['header_size'] >= 40: # v3 and OS/2 file_info['y_flip'] = i8(header_data[7]) == 0xff file_info['direction'] = 1 if file_info['y_flip'] else -1 file_info['width'] = i32(header_data[0:4]) file_info['height'] = i32( header_data[4:8] ) if not file_info['y_flip'] else 2**32 - i32(header_data[4:8]) file_info['planes'] = i16(header_data[8:10]) file_info['bits'] = i16(header_data[10:12]) file_info['compression'] = i32(header_data[12:16]) file_info['data_size'] = i32( header_data[16:20]) # byte size of pixel data file_info['pixels_per_meter'] = (i32(header_data[20:24]), i32(header_data[24:28])) file_info['colors'] = i32(header_data[28:32]) file_info['palette_padding'] = 4 self.info["dpi"] = tuple( map(lambda x: int(math.ceil(x / 39.3701)), file_info['pixels_per_meter'])) if file_info['compression'] == self.BITFIELDS: if len(header_data) >= 52: for idx, mask in enumerate( ['r_mask', 'g_mask', 'b_mask', 'a_mask']): file_info[mask] = i32(header_data[36 + idx * 4:40 + idx * 4]) else: for mask in ['r_mask', 'g_mask', 'b_mask', 'a_mask']: file_info[mask] = i32(read(4)) file_info['rgb_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask']) file_info['rgba_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'], file_info['a_mask']) else: raise IOError("Unsupported BMP header type (%d)" % file_info['header_size']) # ------------------ Special case : header is reported 40, which # ---------------------- is shorter than real size for bpp >= 16 self.size = file_info['width'], file_info['height'] # -------- If color count was not found in the header, compute from bits file_info['colors'] = file_info['colors'] if file_info.get( 'colors', 0) else (1 << file_info['bits']) # -------------------------------- Check abnormal values for DOS attacks if file_info['width'] * file_info['height'] > 2**31: raise IOError("Unsupported BMP Size: (%dx%d)" % self.size) # ----------------------- Check bit depth for unusual unsupported values self.mode, raw_mode = BIT2MODE.get(file_info['bits'], (None, None)) if self.mode is None: raise IOError("Unsupported BMP pixel depth (%d)" % file_info['bits']) # ----------------- Process BMP with Bitfields compression (not palette) if file_info['compression'] == self.BITFIELDS: SUPPORTED = { 32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0)], 24: [(0xff0000, 0xff00, 0xff)], 16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)] } MASK_MODES = { (32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX", (32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA", (32, (0x0, 0x0, 0x0, 0x0)): "BGRA", (24, (0xff0000, 0xff00, 0xff)): "BGR", (16, (0xf800, 0x7e0, 0x1f)): "BGR;16", (16, (0x7c00, 0x3e0, 0x1f)): "BGR;15" } if file_info['bits'] in SUPPORTED: if file_info['bits'] == 32 and file_info[ 'rgba_mask'] in SUPPORTED[file_info['bits']]: raw_mode = MASK_MODES[(file_info['bits'], file_info['rgba_mask'])] self.mode = "RGBA" if raw_mode in ("BGRA", ) else self.mode elif file_info['bits'] in ( 24, 16 ) and file_info['rgb_mask'] in SUPPORTED[file_info['bits']]: raw_mode = MASK_MODES[(file_info['bits'], file_info['rgb_mask'])] else: raise IOError("Unsupported BMP bitfields layout") else: raise IOError("Unsupported BMP bitfields layout") elif file_info['compression'] == self.RAW: if file_info['bits'] == 32 and header == 22: # 32-bit .cur offset raw_mode, self.mode = "BGRA", "RGBA" else: raise IOError("Unsupported BMP compression (%d)" % file_info['compression']) # ---------------- Once the header is processed, process the palette/LUT if self.mode == "P": # Paletted for 1, 4 and 8 bit images # ----------------------------------------------------- 1-bit images if not (0 < file_info['colors'] <= 65536): raise IOError("Unsupported BMP Palette size (%d)" % file_info['colors']) else: padding = file_info['palette_padding'] palette = read(padding * file_info['colors']) greyscale = True indices = (0, 255) if file_info['colors'] == 2 else list( range(file_info['colors'])) # ------------------ Check if greyscale and ignore palette if so for ind, val in enumerate(indices): rgb = palette[ind * padding:ind * padding + 3] if rgb != o8(val) * 3: greyscale = False # -------- If all colors are grey, white or black, ditch palette if greyscale: self.mode = "1" if file_info['colors'] == 2 else "L" raw_mode = self.mode else: self.mode = "P" self.palette = ImagePalette.raw( "BGRX" if padding == 4 else "BGR", palette) # ----------------------------- Finally set the tile data for the plugin self.info['compression'] = file_info['compression'] self.tile = [ ('raw', (0, 0, file_info['width'], file_info['height']), offset or self.fp.tell(), (raw_mode, ((file_info['width'] * file_info['bits'] + 31) >> 3) & (~3), file_info['direction'])) ]
def seek(self, frame): if frame == 0: # rewind self.__offset = 0 self.dispose = None self.__frame = -1 self.__fp.seek(self.__rewind) if frame != self.__frame + 1: raise ValueError("cannot seek to frame %d" % frame) self.__frame = frame self.tile = [] self.fp = self.__fp if self.__offset: # backup to last frame self.fp.seek(self.__offset) while self.data(): pass self.__offset = 0 if self.dispose: self.im = self.dispose self.dispose = None self.palette = self.global_palette while True: s = self.fp.read(1) if not s or s == b";": break elif s == b"!": # # extensions # s = self.fp.read(1) block = self.data() if i8(s) == 249: # # graphic control extension # flags = i8(block[0]) if flags & 1: self.info["transparency"] = i8(block[3]) self.info["duration"] = i16(block[1:3]) * 10 try: # disposal methods if flags & 8: # replace with background colour self.dispose = Image.core.fill( "P", self.size, self.info["background"]) elif flags & 16: # replace with previous contents self.dispose = self.im.copy() except (AttributeError, KeyError): pass elif i8(s) == 255: # # application extension # self.info["extension"] = block, self.fp.tell() if block[:11] == b"NETSCAPE2.0": block = self.data() if len(block) >= 3 and i8(block[0]) == 1: self.info["loop"] = i16(block[1:3]) while self.data(): pass elif s == b",": # # local image # s = self.fp.read(9) # extent x0, y0 = i16(s[0:]), i16(s[2:]) x1, y1 = x0 + i16(s[4:]), y0 + i16(s[6:]) flags = i8(s[8]) interlace = (flags & 64) != 0 if flags & 128: bits = (flags & 7) + 1 self.palette =\ ImagePalette.raw("RGB", self.fp.read(3<<bits)) # image data bits = i8(self.fp.read(1)) self.__offset = self.fp.tell() self.tile = [("gif", (x0, y0, x1, y1), self.__offset, (bits, interlace))] break else: pass # raise IOError, "illegal GIF tag `%x`" % i8(s) if not self.tile: # self.__fp = None raise EOFError("no more images in GIF file") self.mode = "L" if self.palette: self.mode = "P"
def _gal_frames(self, info): read = self.fp.read seek = self.fp.seek frames = [] offsets = [] seek(info["offset"]) info["frames"] = [] for i in range(info["frame_count"]): frame_info = {} name_len = i32le(read(4)) frame_info["name"] = read(name_len).decode("cp932") mask = i32le(read(4)) seek(9, 1) layer_count = i32le(read(4)) if layer_count < 1: raise GalImageError("Invalid GAL frame") frame_info["width"] = si32le(read(4)) frame_info["height"] = si32le(read(4)) bpp = i32le(read(4)) if bpp not in _GAL_MODE or bpp > 32: print(layer_count) print(frame_info, mask) print(bpp) raise GalImageError("Unsupported GAL pixel format") frame_info["bpp"] = bpp if bpp <= 8: palette_size = 1 << bpp frame_info["palette"] = ImagePalette.raw( "BGRX", read(palette_size * 4)) else: frame_info["palette"] = None mode, rawmode = _GAL_MODE[bpp] layermode = mode stride = (frame_info["width"] * bpp + 7) // 8 if bpp >= 8: # align to 4 byte boundary stride = (stride + 3) & ~3 frame_info["stride"] = stride frame_info["alpha_stride"] = (frame_info["width"] + 3) & ~3 frame_info["layers"] = [] for j in range(layer_count): layer_info = {} left = si32le(read(4)) top = si32le(read(4)) layer_info["origin"] = (left, top) layer_info["visible"] = read(1)[0] layer_info["trans_color"] = si32le(read(4)) layer_info["alpha"] = si32le(read(4)) layer_info["alpha_on"] = read(1)[0] name_len = i32le(read(4)) seek(name_len, 1) if int(info["version"]) >= 107: layer_info["lock"] = read(1)[0] if j == 0: offsets.append(self.fp.tell()) else: print( "Warning: multilayer Gale images not fully supported") layer_size = si32le(read(4)) seek(layer_size, 1) alpha_size = si32le(read(4)) if layer_info["alpha_on"] and alpha_size > 0: if mode == "RGB": mode = "RGBA" elif mode == "P": mode = "PA" else: raise GalImageError("unsupported GAL alpha mode") seek(alpha_size, 1) frame_info["layers"].append(layer_info) info["frames"].append(frame_info) box = (0, 0, frame_info["width"], frame_info["height"]) frames.append((frame_info["name"], layer_count, mode, layermode, rawmode, box, frame_info["palette"])) # TODO: handle multi-frame images break return frames, offsets
def _bitmap(self, header=0, offset=0): if header: self.fp.seek(header) read = self.fp.read # CORE/INFO s = read(4) s = s + ImageFile._safe_read(self.fp, i32(s) - 4) if len(s) == 12: # OS/2 1.0 CORE bits = i16(s[10:]) self.size = i16(s[4:]), i16(s[6:]) compression = 0 lutsize = 3 colors = 0 direction = -1 elif len(s) in [40, 64]: # WIN 3.1 or OS/2 2.0 INFO bits = i16(s[14:]) self.size = i32(s[4:]), i32(s[8:]) compression = i32(s[16:]) lutsize = 4 colors = i32(s[32:]) direction = -1 if i8(s[11]) == 0xff: # upside-down storage self.size = self.size[0], 2**32 - self.size[1] direction = 0 else: raise IOError("Unsupported BMP header type (%d)" % len(s)) if not colors: colors = 1 << bits # MODE try: self.mode, rawmode = BIT2MODE[bits] except KeyError: raise IOError("Unsupported BMP pixel depth (%d)" % bits) if compression == 3: # BI_BITFIELDS compression mask = i32(read(4)), i32(read(4)), i32(read(4)) if bits == 32 and mask == (0xff0000, 0x00ff00, 0x0000ff): rawmode = "BGRX" elif bits == 16 and mask == (0x00f800, 0x0007e0, 0x00001f): rawmode = "BGR;16" elif bits == 16 and mask == (0x007c00, 0x0003e0, 0x00001f): rawmode = "BGR;15" else: # print bits, map(hex, mask) raise IOError("Unsupported BMP bitfields layout") elif compression != 0: raise IOError("Unsupported BMP compression (%d)" % compression) # LUT if self.mode == "P": palette = [] greyscale = 1 if colors == 2: indices = (0, 255) else: indices = list(range(colors)) for i in indices: rgb = read(lutsize)[:3] if rgb != o8(i) * 3: greyscale = 0 palette.append(rgb) if greyscale: if colors == 2: self.mode = rawmode = "1" else: self.mode = rawmode = "L" else: self.mode = "P" self.palette = ImagePalette.raw("BGR", b"".join(palette)) if not offset: offset = self.fp.tell() self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, ((self.size[0] * bits + 31) >> 3) & (~3), direction))] self.info["compression"] = compression
def _bitmap(self, header = 0, offset = 0): if header: self.fp.seek(header) read = self.fp.read # CORE/INFO s = read(4) s = s + ImageFile._safe_read(self.fp, i32(s)-4) if len(s) == 12: # OS/2 1.0 CORE bits = i16(s[10:]) self.size = i16(s[4:]), i16(s[6:]) compression = 0 lutsize = 3 colors = 0 direction = -1 elif len(s) in [40, 64]: # WIN 3.1 or OS/2 2.0 INFO bits = i16(s[14:]) self.size = i32(s[4:]), i32(s[8:]) compression = i32(s[16:]) lutsize = 4 colors = i32(s[32:]) direction = -1 if i8(s[11]) == 0xff: # upside-down storage self.size = self.size[0], 2**32 - self.size[1] direction = 0 else: raise IOError("Unsupported BMP header type (%d)" % len(s)) if not colors: colors = 1 << bits # MODE try: self.mode, rawmode = BIT2MODE[bits] except KeyError: raise IOError("Unsupported BMP pixel depth (%d)" % bits) if compression == 3: # BI_BITFIELDS compression mask = i32(read(4)), i32(read(4)), i32(read(4)) if bits == 32 and mask == (0xff0000, 0x00ff00, 0x0000ff): rawmode = "BGRX" elif bits == 16 and mask == (0x00f800, 0x0007e0, 0x00001f): rawmode = "BGR;16" elif bits == 16 and mask == (0x007c00, 0x0003e0, 0x00001f): rawmode = "BGR;15" else: # print bits, map(hex, mask) raise IOError("Unsupported BMP bitfields layout") elif compression != 0: raise IOError("Unsupported BMP compression (%d)" % compression) # LUT if self.mode == "P": palette = [] greyscale = 1 if colors == 2: indices = (0, 255) else: indices = list(range(colors)) for i in indices: rgb = read(lutsize)[:3] if rgb != o8(i)*3: greyscale = 0 palette.append(rgb) if greyscale: if colors == 2: self.mode = rawmode = "1" else: self.mode = rawmode = "L" else: self.mode = "P" self.palette = ImagePalette.raw( "BGR", b"".join(palette) ) if not offset: offset = self.fp.tell() self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, ((self.size[0]*bits+31)>>3)&(~3), direction))] self.info["compression"] = compression
def _bitmap(self, header=0, offset=0): """ Read relevant info about the BMP """ read, seek = self.fp.read, self.fp.seek if header: seek(header) file_info = dict() file_info["header_size"] = i32(read(4)) # read bmp header size @offset 14 (this is part of the header size) file_info["direction"] = -1 # --------------------- If requested, read header at a specific position header_data = ImageFile._safe_read( self.fp, file_info["header_size"] - 4 ) # read the rest of the bmp header, without its size # --------------------------------------------------- IBM OS/2 Bitmap v1 # ------ This format has different offsets because of width/height types if file_info["header_size"] == 12: file_info["width"] = i16(header_data[0:2]) file_info["height"] = i16(header_data[2:4]) file_info["planes"] = i16(header_data[4:6]) file_info["bits"] = i16(header_data[6:8]) file_info["compression"] = self.RAW file_info["palette_padding"] = 3 # ---------------------------------------------- Windows Bitmap v2 to v5 elif file_info["header_size"] in (40, 64, 108, 124): # v3, OS/2 v2, v4, v5 if file_info["header_size"] >= 40: # v3 and OS/2 file_info["y_flip"] = i8(header_data[7]) == 0xFF file_info["direction"] = 1 if file_info["y_flip"] else -1 file_info["width"] = i32(header_data[0:4]) file_info["height"] = ( i32(header_data[4:8]) if not file_info["y_flip"] else 2 ** 32 - i32(header_data[4:8]) ) file_info["planes"] = i16(header_data[8:10]) file_info["bits"] = i16(header_data[10:12]) file_info["compression"] = i32(header_data[12:16]) file_info["data_size"] = i32(header_data[16:20]) # byte size of pixel data file_info["pixels_per_meter"] = (i32(header_data[20:24]), i32(header_data[24:28])) file_info["colors"] = i32(header_data[28:32]) file_info["palette_padding"] = 4 self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), file_info["pixels_per_meter"])) if file_info["compression"] == self.BITFIELDS: if len(header_data) >= 52: for idx, mask in enumerate(["r_mask", "g_mask", "b_mask", "a_mask"]): file_info[mask] = i32(header_data[36 + idx * 4 : 40 + idx * 4]) else: for mask in ["r_mask", "g_mask", "b_mask", "a_mask"]: file_info[mask] = i32(read(4)) file_info["rgb_mask"] = (file_info["r_mask"], file_info["g_mask"], file_info["b_mask"]) file_info["rgba_mask"] = ( file_info["r_mask"], file_info["g_mask"], file_info["b_mask"], file_info["a_mask"], ) else: raise IOError("Unsupported BMP header type (%d)" % file_info["header_size"]) # ------------------ Special case : header is reported 40, which # ---------------------- is shorter than real size for bpp >= 16 self.size = file_info["width"], file_info["height"] # -------- If color count was not found in the header, compute from bits file_info["colors"] = file_info["colors"] if file_info.get("colors", 0) else (1 << file_info["bits"]) # -------------------------------- Check abnormal values for DOS attacks if file_info["width"] * file_info["height"] > 2 ** 31: raise IOError("Unsupported BMP Size: (%dx%d)" % self.size) # ----------------------- Check bit depth for unusual unsupported values self.mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None)) if self.mode is None: raise IOError("Unsupported BMP pixel depth (%d)" % file_info["bits"]) # ----------------- Process BMP with Bitfields compression (not palette) if file_info["compression"] == self.BITFIELDS: SUPPORTED = { 32: [(0xFF0000, 0xFF00, 0xFF, 0x0), (0xFF0000, 0xFF00, 0xFF, 0xFF000000), (0x0, 0x0, 0x0, 0x0)], 24: [(0xFF0000, 0xFF00, 0xFF)], 16: [(0xF800, 0x7E0, 0x1F), (0x7C00, 0x3E0, 0x1F)], } MASK_MODES = { (32, (0xFF0000, 0xFF00, 0xFF, 0x0)): "BGRX", (32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)): "BGRA", (32, (0x0, 0x0, 0x0, 0x0)): "BGRA", (24, (0xFF0000, 0xFF00, 0xFF)): "BGR", (16, (0xF800, 0x7E0, 0x1F)): "BGR;16", (16, (0x7C00, 0x3E0, 0x1F)): "BGR;15", } if file_info["bits"] in SUPPORTED: if file_info["bits"] == 32 and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]]: raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])] self.mode = "RGBA" if raw_mode in ("BGRA",) else self.mode elif file_info["bits"] in (24, 16) and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]]: raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])] else: raise IOError("Unsupported BMP bitfields layout") else: raise IOError("Unsupported BMP bitfields layout") elif file_info["compression"] == self.RAW: if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset raw_mode, self.mode = "BGRA", "RGBA" else: raise IOError("Unsupported BMP compression (%d)" % file_info["compression"]) # ---------------- Once the header is processed, process the palette/LUT if self.mode == "P": # Paletted for 1, 4 and 8 bit images # ----------------------------------------------------- 1-bit images if not (0 < file_info["colors"] <= 65536): raise IOError("Unsupported BMP Palette size (%d)" % file_info["colors"]) else: padding = file_info["palette_padding"] palette = read(padding * file_info["colors"]) greyscale = True indices = (0, 255) if file_info["colors"] == 2 else list(range(file_info["colors"])) # ------------------ Check if greyscale and ignore palette if so for ind, val in enumerate(indices): rgb = palette[ind * padding : ind * padding + 3] if rgb != o8(val) * 3: greyscale = False # -------- If all colors are grey, white or black, ditch palette if greyscale: self.mode = "1" if file_info["colors"] == 2 else "L" raw_mode = self.mode else: self.mode = "P" self.palette = ImagePalette.raw("BGRX" if padding == 4 else "BGR", palette) # ----------------------------- Finally set the tile data for the plugin self.info["compression"] = file_info["compression"] self.tile = [ ( "raw", (0, 0, file_info["width"], file_info["height"]), offset or self.fp.tell(), (raw_mode, ((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3), file_info["direction"]), ) ]
def _setup(self): "Setup this image object based on current tags" if 0xBC01 in self.tag: raise IOError("Windows Media Photo files not yet supported") getscalar = self.tag.getscalar # extract relevant tags self._compression = COMPRESSION_INFO[getscalar(COMPRESSION, 1)] self._planar_configuration = getscalar(PLANAR_CONFIGURATION, 1) # photometric is a required tag, but not everyone is reading # the specification photo = getscalar(PHOTOMETRIC_INTERPRETATION, 0) fillorder = getscalar(FILLORDER, 1) if Image.DEBUG: print("*** Summary ***") print("- compression:", self._compression) print("- photometric_interpretation:", photo) print("- planar_configuration:", self._planar_configuration) print("- fill_order:", fillorder) # size xsize = getscalar(IMAGEWIDTH) ysize = getscalar(IMAGELENGTH) self.size = xsize, ysize if Image.DEBUG: print("- size:", self.size) format = getscalar(SAMPLEFORMAT, 1) # mode: check photometric interpretation and bits per pixel key = ( self.tag.prefix, photo, format, fillorder, self.tag.get(BITSPERSAMPLE, (1,)), self.tag.get(EXTRASAMPLES, ()) ) if Image.DEBUG: print("format key:", key) try: self.mode, rawmode = OPEN_INFO[key] except KeyError: if Image.DEBUG: print("- unsupported format") raise SyntaxError("unknown pixel mode") if Image.DEBUG: print("- raw mode:", rawmode) print("- pil mode:", self.mode) self.info["compression"] = self._compression xres = getscalar(X_RESOLUTION, (1, 1)) yres = getscalar(Y_RESOLUTION, (1, 1)) if xres and not isinstance(xres, tuple): xres = (xres, 1.) if yres and not isinstance(yres, tuple): yres = (yres, 1.) if xres and yres: xres = xres[0] / (xres[1] or 1) yres = yres[0] / (yres[1] or 1) resunit = getscalar(RESOLUTION_UNIT, 1) if resunit == 2: # dots per inch self.info["dpi"] = xres, yres elif resunit == 3: # dots per centimeter. convert to dpi self.info["dpi"] = xres * 2.54, yres * 2.54 else: # No absolute unit of measurement self.info["resolution"] = xres, yres # build tile descriptors x = y = l = 0 self.tile = [] if STRIPOFFSETS in self.tag: # striped image offsets = self.tag[STRIPOFFSETS] h = getscalar(ROWSPERSTRIP, ysize) w = self.size[0] if self._compression in ["tiff_ccitt", "group3", "group4", "tiff_raw_16"]: ## if Image.DEBUG: ## print "Activating g4 compression for whole file" # Decoder expects entire file as one tile. # There's a buffer size limit in load (64k) # so large g4 images will fail if we use that # function. # # Setup the one tile for the whole image, then # replace the existing load function with our # _load_libtiff function. self.load = self._load_libtiff # To be nice on memory footprint, if there's a # file descriptor, use that instead of reading # into a string in python. # libtiff closes the file descriptor, so pass in a dup. try: fp = hasattr(self.fp, "fileno") and os.dup(self.fp.fileno()) except IOError: # io.BytesIO have a fileno, but returns an IOError if # it doesn't use a file descriptor. fp = False # Offset in the tile tuple is 0, we go from 0,0 to # w,h, and we only do this once -- eds a = (rawmode, self._compression, fp ) self.tile.append( (self._compression, (0, 0, w, ysize), 0, a)) a = None else: for i in range(len(offsets)): a = self._decoder(rawmode, l, i) self.tile.append( (self._compression, (0, min(y, ysize), w, min(y+h, ysize)), offsets[i], a)) if Image.DEBUG: print ("tiles: ", self.tile) y = y + h if y >= self.size[1]: x = y = 0 l = l + 1 a = None elif TILEOFFSETS in self.tag: # tiled image w = getscalar(322) h = getscalar(323) a = None for o in self.tag[TILEOFFSETS]: if not a: a = self._decoder(rawmode, l) # FIXME: this doesn't work if the image size # is not a multiple of the tile size... self.tile.append( (self._compression, (x, y, x+w, y+h), o, a)) x = x + w if x >= self.size[0]: x, y = 0, y + h if y >= self.size[1]: x = y = 0 l = l + 1 a = None else: if Image.DEBUG: print("- unsupported data organization") raise SyntaxError("unknown data organization") # fixup palette descriptor if self.mode == "P": palette = [o8(a // 256) for a in self.tag[COLORMAP]] self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
def _open(self): if not _accept(self.fp.read(9)): raise SyntaxError("not an XPM file") # skip forward to next string while True: s = self.fp.readline() if not s: raise SyntaxError("broken XPM file") m = xpm_head.match(s) if m: break self.size = int(m.group(1)), int(m.group(2)) pal = int(m.group(3)) bpp = int(m.group(4)) if pal > 256 or bpp != 1: raise ValueError("cannot read this XPM file") # # load palette description palette = [b"\0\0\0"] * 256 for i in range(pal): s = self.fp.readline() if s[-2:] == b'\r\n': s = s[:-2] elif s[-1:] in b'\r\n': s = s[:-1] c = i8(s[1]) s = s[2:-2].split() for i in range(0, len(s), 2): if s[i] == b"c": # process colour key rgb = s[i+1] if rgb == b"None": self.info["transparency"] = c elif rgb[0:1] == b"#": # FIXME: handle colour names (see ImagePalette.py) rgb = int(rgb[1:], 16) palette[c] = o8((rgb >> 16) & 255) +\ o8((rgb >> 8) & 255) +\ o8(rgb & 255) else: # unknown colour raise ValueError("cannot read this XPM file") break else: # missing colour key raise ValueError("cannot read this XPM file") self.mode = "P" self.palette = ImagePalette.raw("RGB", b"".join(palette)) self.tile = [("raw", (0, 0)+self.size, self.fp.tell(), ("P", 0, 1))]
def _setup(self): "Setup this image object based on current tags" if 0xBC01 in self.tag: raise IOError("Windows Media Photo files not yet supported") getscalar = self.tag.getscalar # extract relevant tags self._compression = COMPRESSION_INFO[getscalar(COMPRESSION, 1)] self._planar_configuration = getscalar(PLANAR_CONFIGURATION, 1) # photometric is a required tag, but not everyone is reading # the specification photo = getscalar(PHOTOMETRIC_INTERPRETATION, 0) fillorder = getscalar(FILLORDER, 1) if Image.DEBUG: print("*** Summary ***") print("- compression:", self._compression) print("- photometric_interpretation:", photo) print("- planar_configuration:", self._planar_configuration) print("- fill_order:", fillorder) # size xsize = getscalar(IMAGEWIDTH) ysize = getscalar(IMAGELENGTH) self.size = xsize, ysize if Image.DEBUG: print("- size:", self.size) format = getscalar(SAMPLEFORMAT, 1) # mode: check photometric interpretation and bits per pixel key = (self.tag.prefix, photo, format, fillorder, self.tag.get(BITSPERSAMPLE, (1, )), self.tag.get(EXTRASAMPLES, ())) if Image.DEBUG: print("format key:", key) try: self.mode, rawmode = OPEN_INFO[key] except KeyError: if Image.DEBUG: print("- unsupported format") raise SyntaxError("unknown pixel mode") if Image.DEBUG: print("- raw mode:", rawmode) print("- pil mode:", self.mode) self.info["compression"] = self._compression xres = getscalar(X_RESOLUTION, (1, 1)) yres = getscalar(Y_RESOLUTION, (1, 1)) if xres and not isinstance(xres, tuple): xres = (xres, 1.) if yres and not isinstance(yres, tuple): yres = (yres, 1.) if xres and yres: xres = xres[0] / (xres[1] or 1) yres = yres[0] / (yres[1] or 1) resunit = getscalar(RESOLUTION_UNIT, 1) if resunit == 2: # dots per inch self.info["dpi"] = xres, yres elif resunit == 3: # dots per centimeter. convert to dpi self.info["dpi"] = xres * 2.54, yres * 2.54 else: # No absolute unit of measurement self.info["resolution"] = xres, yres # build tile descriptors x = y = l = 0 self.tile = [] if STRIPOFFSETS in self.tag: # striped image offsets = self.tag[STRIPOFFSETS] h = getscalar(ROWSPERSTRIP, ysize) w = self.size[0] if READ_LIBTIFF or self._compression in [ "tiff_ccitt", "group3", "group4", "tiff_jpeg", "tiff_adobe_deflate", "tiff_thunderscan", "tiff_deflate", "tiff_sgilog", "tiff_sgilog24", "tiff_raw_16" ]: # if Image.DEBUG: # print "Activating g4 compression for whole file" # Decoder expects entire file as one tile. # There's a buffer size limit in load (64k) # so large g4 images will fail if we use that # function. # # Setup the one tile for the whole image, then # replace the existing load function with our # _load_libtiff function. self.load = self._load_libtiff # To be nice on memory footprint, if there's a # file descriptor, use that instead of reading # into a string in python. # libtiff closes the file descriptor, so pass in a dup. try: fp = hasattr(self.fp, "fileno") and \ os.dup(self.fp.fileno()) except IOError: # io.BytesIO have a fileno, but returns an IOError if # it doesn't use a file descriptor. fp = False # libtiff handles the fillmode for us, so 1;IR should # actually be 1;I. Including the R double reverses the # bits, so stripes of the image are reversed. See # https://github.com/python-pillow/Pillow/issues/279 if fillorder == 2: key = (self.tag.prefix, photo, format, 1, self.tag.get(BITSPERSAMPLE, (1, )), self.tag.get(EXTRASAMPLES, ())) if Image.DEBUG: print("format key:", key) # this should always work, since all the # fillorder==2 modes have a corresponding # fillorder=1 mode self.mode, rawmode = OPEN_INFO[key] # libtiff always returns the bytes in native order. # we're expecting image byte order. So, if the rawmode # contains I;16, we need to convert from native to image # byte order. if self.mode in ('I;16B', 'I;16') and 'I;16' in rawmode: rawmode = 'I;16N' # Offset in the tile tuple is 0, we go from 0,0 to # w,h, and we only do this once -- eds a = (rawmode, self._compression, fp) self.tile.append((self._compression, (0, 0, w, ysize), 0, a)) a = None else: for i in range(len(offsets)): a = self._decoder(rawmode, l, i) self.tile.append( (self._compression, (0, min(y, ysize), w, min(y + h, ysize)), offsets[i], a)) if Image.DEBUG: print("tiles: ", self.tile) y = y + h if y >= self.size[1]: x = y = 0 l += 1 a = None elif TILEOFFSETS in self.tag: # tiled image w = getscalar(322) h = getscalar(323) a = None for o in self.tag[TILEOFFSETS]: if not a: a = self._decoder(rawmode, l) # FIXME: this doesn't work if the image size # is not a multiple of the tile size... self.tile.append( (self._compression, (x, y, x + w, y + h), o, a)) x = x + w if x >= self.size[0]: x, y = 0, y + h if y >= self.size[1]: x = y = 0 l += 1 a = None else: if Image.DEBUG: print("- unsupported data organization") raise SyntaxError("unknown data organization") # fixup palette descriptor if self.mode == "P": palette = [o8(a // 256) for a in self.tag[COLORMAP]] self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
def _setup(self): "Setup this image object based on current tags" if 0xBC01 in self.tag_v2: raise IOError("Windows Media Photo files not yet supported") # extract relevant tags self._compression = COMPRESSION_INFO[self.tag_v2.get(COMPRESSION, 1)] self._planar_configuration = self.tag_v2.get(PLANAR_CONFIGURATION, 1) # photometric is a required tag, but not everyone is reading # the specification photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0) fillorder = self.tag_v2.get(FILLORDER, 1) if DEBUG: print("*** Summary ***") print("- compression:", self._compression) print("- photometric_interpretation:", photo) print("- planar_configuration:", self._planar_configuration) print("- fill_order:", fillorder) # size xsize = self.tag_v2.get(IMAGEWIDTH) ysize = self.tag_v2.get(IMAGELENGTH) self.size = xsize, ysize if DEBUG: print("- size:", self.size) format = self.tag_v2.get(SAMPLEFORMAT, (1,)) if len(format) > 1 and max(format) == min(format) == 1: # SAMPLEFORMAT is properly per band, so an RGB image will # be (1,1,1). But, we don't support per band pixel types, # and anything more than one band is a uint8. So, just # take the first element. Revisit this if adding support # for more exotic images. format = (1,) # mode: check photometric interpretation and bits per pixel key = ( self.tag_v2.prefix, photo, format, fillorder, self.tag_v2.get(BITSPERSAMPLE, (1,)), self.tag_v2.get(EXTRASAMPLES, ()) ) if DEBUG: print("format key:", key) try: self.mode, rawmode = OPEN_INFO[key] except KeyError: if DEBUG: print("- unsupported format") raise SyntaxError("unknown pixel mode") if DEBUG: print("- raw mode:", rawmode) print("- pil mode:", self.mode) self.info["compression"] = self._compression xres = self.tag_v2.get(X_RESOLUTION, (1, 1)) yres = self.tag_v2.get(Y_RESOLUTION, (1, 1)) if xres and not isinstance(xres, tuple): xres = (xres, 1.) if yres and not isinstance(yres, tuple): yres = (yres, 1.) if xres and yres: xres = xres[0] / (xres[1] or 1) yres = yres[0] / (yres[1] or 1) resunit = self.tag_v2.get(RESOLUTION_UNIT, 1) if resunit == 2: # dots per inch self.info["dpi"] = xres, yres elif resunit == 3: # dots per centimeter. convert to dpi self.info["dpi"] = xres * 2.54, yres * 2.54 else: # No absolute unit of measurement self.info["resolution"] = xres, yres # build tile descriptors x = y = l = 0 self.tile = [] if STRIPOFFSETS in self.tag_v2: # striped image offsets = self.tag_v2[STRIPOFFSETS] h = self.tag_v2.get(ROWSPERSTRIP, ysize) w = self.size[0] if READ_LIBTIFF or self._compression in ["tiff_ccitt", "group3", "group4", "tiff_jpeg", "tiff_adobe_deflate", "tiff_thunderscan", "tiff_deflate", "tiff_sgilog", "tiff_sgilog24", "tiff_raw_16"]: # if DEBUG: # print "Activating g4 compression for whole file" # Decoder expects entire file as one tile. # There's a buffer size limit in load (64k) # so large g4 images will fail if we use that # function. # # Setup the one tile for the whole image, then # replace the existing load function with our # _load_libtiff function. self.load = self._load_libtiff # To be nice on memory footprint, if there's a # file descriptor, use that instead of reading # into a string in python. # libtiff closes the file descriptor, so pass in a dup. try: fp = hasattr(self.fp, "fileno") and \ os.dup(self.fp.fileno()) # flush the file descriptor, prevents error on pypy 2.4+ # should also eliminate the need for fp.tell for py3 # in _seek if hasattr(self.fp, "flush"): self.fp.flush() except IOError: # io.BytesIO have a fileno, but returns an IOError if # it doesn't use a file descriptor. fp = False # libtiff handles the fillmode for us, so 1;IR should # actually be 1;I. Including the R double reverses the # bits, so stripes of the image are reversed. See # https://github.com/python-pillow/Pillow/issues/279 if fillorder == 2: key = ( self.tag_v2.prefix, photo, format, 1, self.tag_v2.get(BITSPERSAMPLE, (1,)), self.tag_v2.get(EXTRASAMPLES, ()) ) if DEBUG: print("format key:", key) # this should always work, since all the # fillorder==2 modes have a corresponding # fillorder=1 mode self.mode, rawmode = OPEN_INFO[key] # libtiff always returns the bytes in native order. # we're expecting image byte order. So, if the rawmode # contains I;16, we need to convert from native to image # byte order. if self.mode in ('I;16B', 'I;16') and 'I;16' in rawmode: rawmode = 'I;16N' # Offset in the tile tuple is 0, we go from 0,0 to # w,h, and we only do this once -- eds a = (rawmode, self._compression, fp) self.tile.append( (self._compression, (0, 0, w, ysize), 0, a)) a = None else: for i in range(len(offsets)): a = self._decoder(rawmode, l, i) self.tile.append( (self._compression, (0, min(y, ysize), w, min(y+h, ysize)), offsets[i], a)) if DEBUG: print("tiles: ", self.tile) y = y + h if y >= self.size[1]: x = y = 0 l += 1 a = None elif TILEOFFSETS in self.tag_v2: # tiled image w = self.tag_v2.get(322) h = self.tag_v2.get(323) a = None for o in self.tag_v2[TILEOFFSETS]: if not a: a = self._decoder(rawmode, l) # FIXME: this doesn't work if the image size # is not a multiple of the tile size... self.tile.append( (self._compression, (x, y, x+w, y+h), o, a)) x = x + w if x >= self.size[0]: x, y = 0, y + h if y >= self.size[1]: x = y = 0 l += 1 a = None else: if DEBUG: print("- unsupported data organization") raise SyntaxError("unknown data organization") # fixup palette descriptor if self.mode == "P": palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]] self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
def seek(self, frame): if frame == 0: # rewind self.__offset = 0 self.dispose = None self.__frame = -1 self.__fp.seek(self.__rewind) if frame != self.__frame + 1: raise ValueError("cannot seek to frame %d" % frame) self.__frame = frame self.tile = [] self.fp = self.__fp if self.__offset: # backup to last frame self.fp.seek(self.__offset) while self.data(): pass self.__offset = 0 if self.dispose: self.im = self.dispose self.dispose = None from copy import copy self.palette = copy(self.global_palette) while True: s = self.fp.read(1) if not s or s == b";": break elif s == b"!": # # extensions # s = self.fp.read(1) block = self.data() if i8(s) == 249: # # graphic control extension # flags = i8(block[0]) if flags & 1: self.info["transparency"] = i8(block[3]) self.info["duration"] = i16(block[1:3]) * 10 try: # disposal methods if flags & 8: # replace with background colour self.dispose = Image.core.fill("P", self.size, self.info["background"]) elif flags & 16: # replace with previous contents self.dispose = self.im.copy() except (AttributeError, KeyError): pass elif i8(s) == 255: # # application extension # self.info["extension"] = block, self.fp.tell() if block[:11] == b"NETSCAPE2.0": block = self.data() if len(block) >= 3 and i8(block[0]) == 1: self.info["loop"] = i16(block[1:3]) while self.data(): pass elif s == b",": # # local image # s = self.fp.read(9) # extent x0, y0 = i16(s[0:]), i16(s[2:]) x1, y1 = x0 + i16(s[4:]), y0 + i16(s[6:]) flags = i8(s[8]) interlace = (flags & 64) != 0 if flags & 128: bits = (flags & 7) + 1 self.palette = ImagePalette.raw("RGB", self.fp.read(3 << bits)) # image data bits = i8(self.fp.read(1)) self.__offset = self.fp.tell() self.tile = [("gif", (x0, y0, x1, y1), self.__offset, (bits, interlace))] break else: pass # raise IOError, "illegal GIF tag `%x`" % i8(s) if not self.tile: # self.__fp = None raise EOFError("no more images in GIF file") self.mode = "L" if self.palette: self.mode = "P"
def _open(self): # process header s = self.fp.read(18) id = i8(s[0]) colormaptype = i8(s[1]) imagetype = i8(s[2]) depth = i8(s[16]) flags = i8(s[17]) self.size = i16(s[12:]), i16(s[14:]) # validate header fields if id != 0 or colormaptype not in (0, 1) or\ self.size[0] <= 0 or self.size[1] <= 0 or\ depth not in (1, 8, 16, 24, 32): raise SyntaxError("not a TGA file") # image mode if imagetype in (3, 11): self.mode = "L" if depth == 1: self.mode = "1" # ??? elif imagetype in (1, 9): self.mode = "P" elif imagetype in (2, 10): self.mode = "RGB" if depth == 32: self.mode = "RGBA" else: raise SyntaxError("unknown TGA mode") # orientation orientation = flags & 0x30 if orientation == 0x20: orientation = 1 elif not orientation: orientation = -1 else: raise SyntaxError("unknown TGA orientation") self.info["orientation"] = orientation if imagetype & 8: self.info["compression"] = "tga_rle" if colormaptype: # read palette start, size, mapdepth = i16(s[3:]), i16(s[5:]), i16(s[7:]) if mapdepth == 16: self.palette = ImagePalette.raw( "BGR;16", b"\0" * 2 * start + self.fp.read(2 * size)) elif mapdepth == 24: self.palette = ImagePalette.raw( "BGR", b"\0" * 3 * start + self.fp.read(3 * size)) elif mapdepth == 32: self.palette = ImagePalette.raw( "BGRA", b"\0" * 4 * start + self.fp.read(4 * size)) # setup tile descriptor try: rawmode = MODES[(imagetype & 7, depth)] if imagetype & 8: # compressed self.tile = [("tga_rle", (0, 0) + self.size, self.fp.tell(), (rawmode, orientation, depth))] else: self.tile = [("raw", (0, 0) + self.size, self.fp.tell(), (rawmode, 0, orientation))] except KeyError: pass # cannot decode
def _seek(self, frame): if frame == 0: # rewind self.__offset = 0 self.dispose = None self.dispose_extent = [0, 0, 0, 0] # x0, y0, x1, y1 self.__frame = -1 self.__fp.seek(self.__rewind) self._prev_im = None self.disposal_method = 0 else: # ensure that the previous frame was loaded if not self.im: self.load() if frame != self.__frame + 1: raise ValueError("cannot seek to frame %d" % frame) self.__frame = frame self.tile = [] self.fp = self.__fp if self.__offset: # backup to last frame self.fp.seek(self.__offset) while self.data(): pass self.__offset = 0 if self.dispose: self.im.paste(self.dispose, self.dispose_extent) from copy import copy self.palette = copy(self.global_palette) while True: s = self.fp.read(1) if not s or s == b";": break elif s == b"!": # # extensions # s = self.fp.read(1) block = self.data() if i8(s) == 249: # # graphic control extension # flags = i8(block[0]) if flags & 1: self.info["transparency"] = i8(block[3]) self.info["duration"] = i16(block[1:3]) * 10 # disposal method - find the value of bits 4 - 6 dispose_bits = 0b00011100 & flags dispose_bits = dispose_bits >> 2 if dispose_bits: # only set the dispose if it is not # unspecified. I'm not sure if this is # correct, but it seems to prevent the last # frame from looking odd for some animations self.disposal_method = dispose_bits elif i8(s) == 254: # # comment extension # self.info["comment"] = block elif i8(s) == 255: # # application extension # self.info["extension"] = block, self.fp.tell() if block[:11] == b"NETSCAPE2.0": block = self.data() if len(block) >= 3 and i8(block[0]) == 1: self.info["loop"] = i16(block[1:3]) while self.data(): pass elif s == b",": # # local image # s = self.fp.read(9) # extent x0, y0 = i16(s[0:]), i16(s[2:]) x1, y1 = x0 + i16(s[4:]), y0 + i16(s[6:]) self.dispose_extent = x0, y0, x1, y1 flags = i8(s[8]) interlace = (flags & 64) != 0 if flags & 128: bits = (flags & 7) + 1 self.palette =\ ImagePalette.raw("RGB", self.fp.read(3 << bits)) # image data bits = i8(self.fp.read(1)) self.__offset = self.fp.tell() self.tile = [("gif", (x0, y0, x1, y1), self.__offset, (bits, interlace))] break else: pass # raise IOError, "illegal GIF tag `%x`" % i8(s) try: if self.disposal_method < 2: # do not dispose or none specified self.dispose = None elif self.disposal_method == 2: # replace with background colour self.dispose = Image.core.fill("P", self.size, self.info["background"]) else: # replace with previous contents if self.im: self.dispose = self.im.copy() # only dispose the extent in this frame if self.dispose: self.dispose = self.dispose.crop(self.dispose_extent) except (AttributeError, KeyError): pass if not self.tile: # self.__fp = None raise EOFError self.mode = "L" if self.palette: self.mode = "P"
def _bitmap(self, header=0, offset=0): if header: self.fp.seek(header) read = self.fp.read # CORE/INFO s = read(4) s = s + ImageFile._safe_read(self.fp, i32(s) - 4) if len(s) == 12: # OS/2 1.0 CORE bits = i16(s[10:]) self.size = i16(s[4:]), i16(s[6:]) compression = 0 lutsize = 3 colors = 0 direction = -1 elif len(s) in [40, 64, 108, 124]: # WIN 3.1 or OS/2 2.0 INFO bits = i16(s[14:]) self.size = i32(s[4:]), i32(s[8:]) compression = i32(s[16:]) pxperm = (i32(s[24:]), i32(s[28:])) # Pixels per meter lutsize = 4 colors = i32(s[32:]) direction = -1 if i8(s[11]) == 0xFF: # upside-down storage self.size = self.size[0], 2 ** 32 - self.size[1] direction = 0 self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), pxperm)) else: raise IOError("Unsupported BMP header type (%d)" % len(s)) if (self.size[0] * self.size[1]) > 2 ** 31: # Prevent DOS for > 2gb images raise IOError("Unsupported BMP Size: (%dx%d)" % self.size) if not colors: colors = 1 << bits # MODE try: self.mode, rawmode = BIT2MODE[bits] except KeyError: raise IOError("Unsupported BMP pixel depth (%d)" % bits) if compression == 3: # BI_BITFIELDS compression mask = i32(read(4)), i32(read(4)), i32(read(4)) if bits == 32 and mask == (0xFF0000, 0x00FF00, 0x0000FF): rawmode = "BGRX" elif bits == 16 and mask == (0x00F800, 0x0007E0, 0x00001F): rawmode = "BGR;16" elif bits == 16 and mask == (0x007C00, 0x0003E0, 0x00001F): rawmode = "BGR;15" else: # print bits, map(hex, mask) raise IOError("Unsupported BMP bitfields layout") elif compression != 0: raise IOError("Unsupported BMP compression (%d)" % compression) # LUT if self.mode == "P": palette = [] greyscale = 1 if colors == 2: indices = (0, 255) elif colors > 2 ** 16 or colors <= 0: # We're reading a i32. raise IOError("Unsupported BMP Palette size (%d)" % colors) else: indices = list(range(colors)) for i in indices: rgb = read(lutsize)[:3] if rgb != o8(i) * 3: greyscale = 0 palette.append(rgb) if greyscale: if colors == 2: self.mode = rawmode = "1" else: self.mode = rawmode = "L" else: self.mode = "P" self.palette = ImagePalette.raw("BGR", b"".join(palette)) if not offset: offset = self.fp.tell() self.tile = [ ("raw", (0, 0) + self.size, offset, (rawmode, ((self.size[0] * bits + 31) >> 3) & (~3), direction)) ] self.info["compression"] = compression
def _open(self): # The Sun Raster file header is 32 bytes in length and has the following format: # typedef struct _SunRaster # { # DWORD MagicNumber; /* Magic (identification) number */ # DWORD Width; /* Width of image in pixels */ # DWORD Height; /* Height of image in pixels */ # DWORD Depth; /* Number of bits per pixel */ # DWORD Length; /* Size of image data in bytes */ # DWORD Type; /* Type of raster file */ # DWORD ColorMapType; /* Type of color map */ # DWORD ColorMapLength; /* Size of the color map in bytes */ # } SUNRASTER; # HEAD s = self.fp.read(32) if i32(s) != 0x59a66a95: raise InvalidFileType("not a SUN raster file") offset = 32 self.size = i32(s[4:8]), i32(s[8:12]) depth = i32(s[12:16]) data_length = i32(s[16:20]) # unreliable, ignore. file_type = i32(s[20:24]) palette_type = i32(s[24:28]) # 0: None, 1: RGB, 2: Raw/arbitrary palette_length = i32(s[28:32]) if depth == 1: self.mode, rawmode = "1", "1;I" elif depth == 4: self.mode, rawmode = "L", "L;4" elif depth == 8: self.mode = rawmode = "L" elif depth == 24: if file_type == 3: self.mode, rawmode = "RGB", "RGB" else: self.mode, rawmode = "RGB", "BGR" elif depth == 32: if file_type == 3: self.mode, rawmode = 'RGB', 'RGBX' else: self.mode, rawmode = 'RGB', 'BGRX' else: raise NotImplementedError("Unsupported Mode/Bit Depth") if palette_length: if palette_length > 1024: raise NotImplementedError("Unsupported Color Palette Length") if palette_type != 1: raise NotImplementedError("Unsupported Palette Type") offset = offset + palette_length self.palette = ImagePalette.raw("RGB;L", self.fp.read(palette_length)) if self.mode == "L": self.mode = "P" rawmode = rawmode.replace('L', 'P') # 16 bit boundaries on stride stride = ((self.size[0] * depth + 15) // 16) * 2 # file type: Type is the version (or flavor) of the bitmap # file. The following values are typically found in the Type # field: # 0000h Old # 0001h Standard # 0002h Byte-encoded # 0003h RGB format # 0004h TIFF format # 0005h IFF format # FFFFh Experimental # Old and standard are the same, except for the length tag. # byte-encoded is run-length-encoded # RGB looks similar to standard, but RGB byte order # TIFF and IFF mean that they were converted from T/IFF # Experimental means that it's something else. # (http://www.fileformat.info/format/sunraster/egff.htm) if file_type in (0, 1, 3, 4, 5): self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride)) ] elif file_type == 2: self.tile = [("sun_rle", (0, 0) + self.size, offset, rawmode)] else: raise NotImplementedError('Unsupported Sun Raster file type')
def _setup(self): "Setup this image object based on current tags" if 0xBC01 in self.tag: raise IOError("Windows Media Photo files not yet supported") getscalar = self.tag.getscalar # extract relevant tags self._compression = COMPRESSION_INFO[getscalar(COMPRESSION, 1)] self._planar_configuration = getscalar(PLANAR_CONFIGURATION, 1) # photometric is a required tag, but not everyone is reading # the specification photo = getscalar(PHOTOMETRIC_INTERPRETATION, 0) fillorder = getscalar(FILLORDER, 1) if Image.DEBUG: print("*** Summary ***") print("- compression:", self._compression) print("- photometric_interpretation:", photo) print("- planar_configuration:", self._planar_configuration) print("- fill_order:", fillorder) # size xsize = getscalar(IMAGEWIDTH) ysize = getscalar(IMAGELENGTH) self.size = xsize, ysize if Image.DEBUG: print("- size:", self.size) format = getscalar(SAMPLEFORMAT, 1) # mode: check photometric interpretation and bits per pixel key = ( self.tag.prefix, photo, format, fillorder, self.tag.get(BITSPERSAMPLE, (1,)), self.tag.get(EXTRASAMPLES, ()) ) if Image.DEBUG: print("format key:", key) try: self.mode, rawmode = OPEN_INFO[key] except KeyError: if Image.DEBUG: print("- unsupported format") raise SyntaxError("unknown pixel mode") if Image.DEBUG: print("- raw mode:", rawmode) print("- pil mode:", self.mode) self.info["compression"] = self._compression xres = getscalar(X_RESOLUTION, (1, 1)) yres = getscalar(Y_RESOLUTION, (1, 1)) if xres and yres: xres = xres[0] / (xres[1] or 1) yres = yres[0] / (yres[1] or 1) resunit = getscalar(RESOLUTION_UNIT, 1) if resunit == 2: # dots per inch self.info["dpi"] = xres, yres elif resunit == 3: # dots per centimeter. convert to dpi self.info["dpi"] = xres * 2.54, yres * 2.54 else: # No absolute unit of measurement self.info["resolution"] = xres, yres # build tile descriptors x = y = l = 0 self.tile = [] if STRIPOFFSETS in self.tag: # striped image h = getscalar(ROWSPERSTRIP, ysize) w = self.size[0] a = None for o in self.tag[STRIPOFFSETS]: if not a: a = self._decoder(rawmode, l) self.tile.append( (self._compression, (0, min(y, ysize), w, min(y+h, ysize)), o, a)) y = y + h if y >= self.size[1]: x = y = 0 l = l + 1 a = None elif TILEOFFSETS in self.tag: # tiled image w = getscalar(322) h = getscalar(323) a = None for o in self.tag[TILEOFFSETS]: if not a: a = self._decoder(rawmode, l) # FIXME: this doesn't work if the image size # is not a multiple of the tile size... self.tile.append( (self._compression, (x, y, x+w, y+h), o, a)) x = x + w if x >= self.size[0]: x, y = 0, y + h if y >= self.size[1]: x = y = 0 l = l + 1 a = None else: if Image.DEBUG: print("- unsupported data organization") raise SyntaxError("unknown data organization") # fixup palette descriptor if self.mode == "P": palette = [o8(a // 256) for a in self.tag[COLORMAP]] self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
def _bitmap(self, header=0, offset=0): """ Read relevant info about the BMP """ read, seek = self.fp.read, self.fp.seek if header: seek(header) file_info = {} file_info['header_size'] = i32(read(4)) # read bmp header size @offset 14 (this is part of the header size) file_info['direction'] = -1 # --------------------- If requested, read header at a specific position header_data = ImageFile._safe_read(self.fp, file_info['header_size'] - 4) # read the rest of the bmp header, without its size # --------------------------------------------------- IBM OS/2 Bitmap v1 # ------ This format has different offsets because of width/height types if file_info['header_size'] == 12: file_info['width'] = i16(header_data[0:2]) file_info['height'] = i16(header_data[2:4]) file_info['planes'] = i16(header_data[4:6]) file_info['bits'] = i16(header_data[6:8]) file_info['compression'] = self.RAW file_info['palette_padding'] = 3 # ---------------------------------------------- Windows Bitmap v2 to v5 elif file_info['header_size'] in (40, 64, 108, 124): # v3, OS/2 v2, v4, v5 if file_info['header_size'] >= 40: # v3 and OS/2 file_info['y_flip'] = i8(header_data[7]) == 0xff file_info['direction'] = 1 if file_info['y_flip'] else -1 file_info['width'] = i32(header_data[0:4]) file_info['height'] = i32(header_data[4:8]) if not file_info['y_flip'] else 2**32 - i32(header_data[4:8]) file_info['planes'] = i16(header_data[8:10]) file_info['bits'] = i16(header_data[10:12]) file_info['compression'] = i32(header_data[12:16]) file_info['data_size'] = i32(header_data[16:20]) # byte size of pixel data file_info['pixels_per_meter'] = (i32(header_data[20:24]), i32(header_data[24:28])) file_info['colors'] = i32(header_data[28:32]) file_info['palette_padding'] = 4 self.info["dpi"] = tuple( map(lambda x: int(math.ceil(x / 39.3701)), file_info['pixels_per_meter'])) if file_info['compression'] == self.BITFIELDS: if len(header_data) >= 52: for idx, mask in enumerate(['r_mask', 'g_mask', 'b_mask', 'a_mask']): file_info[mask] = i32(header_data[36+idx*4:40+idx*4]) else: # 40 byte headers only have the three components in the bitfields masks, # ref: https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx # See also https://github.com/python-pillow/Pillow/issues/1293 # There is a 4th component in the RGBQuad, in the alpha location, but it # is listed as a reserved component, and it is not generally an alpha channel file_info['a_mask'] = 0x0 for mask in ['r_mask', 'g_mask', 'b_mask']: file_info[mask] = i32(read(4)) file_info['rgb_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask']) file_info['rgba_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'], file_info['a_mask']) else: raise IOError("Unsupported BMP header type (%d)" % file_info['header_size']) # ------------------ Special case : header is reported 40, which # ---------------------- is shorter than real size for bpp >= 16 self.size = file_info['width'], file_info['height'] # -------- If color count was not found in the header, compute from bits file_info['colors'] = file_info['colors'] if file_info.get('colors', 0) else (1 << file_info['bits']) # -------------------------------- Check abnormal values for DOS attacks if file_info['width'] * file_info['height'] > 2**31: raise IOError("Unsupported BMP Size: (%dx%d)" % self.size) # ----------------------- Check bit depth for unusual unsupported values self.mode, raw_mode = BIT2MODE.get(file_info['bits'], (None, None)) if self.mode is None: raise IOError("Unsupported BMP pixel depth (%d)" % file_info['bits']) # ----------------- Process BMP with Bitfields compression (not palette) if file_info['compression'] == self.BITFIELDS: SUPPORTED = { 32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0), (0xff000000, 0xff0000, 0xff00, 0x0) ], 24: [(0xff0000, 0xff00, 0xff)], 16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)] } MASK_MODES = { (32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX", (32, (0xff000000, 0xff0000, 0xff00, 0x0)): "XBGR", (32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA", (32, (0x0, 0x0, 0x0, 0x0)): "BGRA", (24, (0xff0000, 0xff00, 0xff)): "BGR", (16, (0xf800, 0x7e0, 0x1f)): "BGR;16", (16, (0x7c00, 0x3e0, 0x1f)): "BGR;15" } if file_info['bits'] in SUPPORTED: if file_info['bits'] == 32 and file_info['rgba_mask'] in SUPPORTED[file_info['bits']]: raw_mode = MASK_MODES[(file_info['bits'], file_info['rgba_mask'])] self.mode = "RGBA" if raw_mode in ("BGRA",) else self.mode elif file_info['bits'] in (24, 16) and file_info['rgb_mask'] in SUPPORTED[file_info['bits']]: raw_mode = MASK_MODES[(file_info['bits'], file_info['rgb_mask'])] else: raise IOError("Unsupported BMP bitfields layout") else: raise IOError("Unsupported BMP bitfields layout") elif file_info['compression'] == self.RAW: if file_info['bits'] == 32 and header == 22: # 32-bit .cur offset raw_mode, self.mode = "BGRA", "RGBA" else: raise IOError("Unsupported BMP compression (%d)" % file_info['compression']) # ---------------- Once the header is processed, process the palette/LUT if self.mode == "P": # Paletted for 1, 4 and 8 bit images # ----------------------------------------------------- 1-bit images if not (0 < file_info['colors'] <= 65536): raise IOError("Unsupported BMP Palette size (%d)" % file_info['colors']) else: padding = file_info['palette_padding'] palette = read(padding * file_info['colors']) greyscale = True indices = (0, 255) if file_info['colors'] == 2 else list(range(file_info['colors'])) # ------------------ Check if greyscale and ignore palette if so for ind, val in enumerate(indices): rgb = palette[ind*padding:ind*padding + 3] if rgb != o8(val) * 3: greyscale = False # -------- If all colors are grey, white or black, ditch palette if greyscale: self.mode = "1" if file_info['colors'] == 2 else "L" raw_mode = self.mode else: self.mode = "P" self.palette = ImagePalette.raw("BGRX" if padding == 4 else "BGR", palette) # ----------------------------- Finally set the tile data for the plugin self.info['compression'] = file_info['compression'] self.tile = [('raw', (0, 0, file_info['width'], file_info['height']), offset or self.fp.tell(), (raw_mode, ((file_info['width'] * file_info['bits'] + 31) >> 3) & (~3), file_info['direction']) )]
def _open(self): # process header s = self.fp.read(18) idlen = i8(s[0]) colormaptype = i8(s[1]) imagetype = i8(s[2]) depth = i8(s[16]) flags = i8(s[17]) self.size = i16(s[12:]), i16(s[14:]) # validate header fields if colormaptype not in (0, 1) or\ self.size[0] <= 0 or self.size[1] <= 0 or\ depth not in (1, 8, 16, 24, 32): raise SyntaxError("not a TGA file") # image mode if imagetype in (3, 11): self.mode = "L" if depth == 1: self.mode = "1" # ??? elif imagetype in (1, 9): self.mode = "P" elif imagetype in (2, 10): self.mode = "RGB" if depth == 32: self.mode = "RGBA" else: raise SyntaxError("unknown TGA mode") # orientation orientation = flags & 0x30 if orientation == 0x20: orientation = 1 elif not orientation: orientation = -1 else: raise SyntaxError("unknown TGA orientation") self.info["orientation"] = orientation if imagetype & 8: self.info["compression"] = "tga_rle" if idlen: self.info["id_section"] = self.fp.read(idlen) if colormaptype: # read palette start, size, mapdepth = i16(s[3:]), i16(s[5:]), i16(s[7:]) if mapdepth == 16: self.palette = ImagePalette.raw( "BGR;16", b"\0"*2*start + self.fp.read(2*size)) elif mapdepth == 24: self.palette = ImagePalette.raw( "BGR", b"\0"*3*start + self.fp.read(3*size)) elif mapdepth == 32: self.palette = ImagePalette.raw( "BGRA", b"\0"*4*start + self.fp.read(4*size)) # setup tile descriptor try: rawmode = MODES[(imagetype & 7, depth)] if imagetype & 8: # compressed self.tile = [("tga_rle", (0, 0)+self.size, self.fp.tell(), (rawmode, orientation, depth))] else: self.tile = [("raw", (0, 0)+self.size, self.fp.tell(), (rawmode, 0, orientation))] except KeyError: pass # cannot decode
def _open(self): # Quick rejection: if there's not an LF among the first # 100 bytes, this is (probably) not a text header. if not b"\n" in self.fp.read(100): raise SyntaxError("not an IM file") self.fp.seek(0) n = 0 # Default values self.info[MODE] = "L" self.info[SIZE] = (512, 512) self.info[FRAMES] = 1 self.rawmode = "L" while True: s = self.fp.read(1) # Some versions of IFUNC uses \n\r instead of \r\n... if s == b"\r": continue if not s or s == b'\0' or s == b'\x1A': break # FIXME: this may read whole file if not a text file s = s + self.fp.readline() if len(s) > 100: raise SyntaxError("not an IM file") if s[-2:] == b'\r\n': s = s[:-2] elif s[-1:] == b'\n': s = s[:-1] try: m = split.match(s) except re.error as v: raise SyntaxError("not an IM file") if m: k, v = m.group(1,2) # Don't know if this is the correct encoding, but a decent guess # (I guess) k = k.decode('latin-1', 'replace') v = v.decode('latin-1', 'replace') # Convert value as appropriate if k in [FRAMES, SCALE, SIZE]: v = v.replace("*", ",") v = tuple(map(number, v.split(","))) if len(v) == 1: v = v[0] elif k == MODE and v in OPEN: v, self.rawmode = OPEN[v] # Add to dictionary. Note that COMMENT tags are # combined into a list of strings. if k == COMMENT: if k in self.info: self.info[k].append(v) else: self.info[k] = [v] else: self.info[k] = v if k in TAGS: n = n + 1 else: raise SyntaxError("Syntax error in IM header: " + s.decode('ascii', 'replace')) if not n: raise SyntaxError("Not an IM file") # Basic attributes self.size = self.info[SIZE] self.mode = self.info[MODE] # Skip forward to start of image data while s and s[0:1] != b'\x1A': s = self.fp.read(1) if not s: raise SyntaxError("File truncated") if LUT in self.info: # convert lookup table to palette or lut attribute palette = self.fp.read(768) greyscale = 1 # greyscale palette linear = 1 # linear greyscale palette for i in range(256): if palette[i] == palette[i+256] == palette[i+512]: if i8(palette[i]) != i: linear = 0 else: greyscale = 0 if self.mode == "L" or self.mode == "LA": if greyscale: if not linear: self.lut = [i8(c) for c in palette[:256]] else: if self.mode == "L": self.mode = self.rawmode = "P" elif self.mode == "LA": self.mode = self.rawmode = "PA" self.palette = ImagePalette.raw("RGB;L", palette) elif self.mode == "RGB": if not greyscale or not linear: self.lut = [i8(c) for c in palette] self.frame = 0 self.__offset = offs = self.fp.tell() self.__fp = self.fp # FIXME: hack if self.rawmode[:2] == "F;": # ifunc95 formats try: # use bit decoder (if necessary) bits = int(self.rawmode[2:]) if bits not in [8, 16, 32]: self.tile = [("bit", (0,0)+self.size, offs, (bits, 8, 3, 0, -1))] return except ValueError: pass if self.rawmode in ["RGB;T", "RYB;T"]: # Old LabEye/3PC files. Would be very surprised if anyone # ever stumbled upon such a file ;-) size = self.size[0] * self.size[1] self.tile = [("raw", (0,0)+self.size, offs, ("G", 0, -1)), ("raw", (0,0)+self.size, offs+size, ("R", 0, -1)), ("raw", (0,0)+self.size, offs+2*size, ("B", 0, -1))] else: # LabEye/IFUNC files self.tile = [("raw", (0,0)+self.size, offs, (self.rawmode, 0, -1))]
def _open(self): if not _accept(self.fp.read(9)): raise SyntaxError("not an XPM file") # skip forward to next string while True: s = self.fp.readline() if not s: raise SyntaxError("broken XPM file") m = xpm_head.match(s) if m: break self.size = int(m.group(1)), int(m.group(2)) pal = int(m.group(3)) bpp = int(m.group(4)) if pal > 256 or bpp != 1: raise ValueError("cannot read this XPM file") # # load palette description palette = [b"\0\0\0"] * 256 for i in range(pal): s = self.fp.readline() if s[-2:] == b'\r\n': s = s[:-2] elif s[-1:] in b'\r\n': s = s[:-1] c = i8(s[1]) s = s[2:-2].split() for i in range(0, len(s), 2): if s[i] == b"c": # process colour key rgb = s[i + 1] if rgb == b"None": self.info["transparency"] = c elif rgb[0:1] == b"#": # FIXME: handle colour names (see ImagePalette.py) rgb = int(rgb[1:], 16) palette[c] = (o8((rgb >> 16) & 255) + o8((rgb >> 8) & 255) + o8(rgb & 255)) else: # unknown colour raise ValueError("cannot read this XPM file") break else: # missing colour key raise ValueError("cannot read this XPM file") self.mode = "P" self.palette = ImagePalette.raw("RGB", b"".join(palette)) self.tile = [("raw", (0, 0) + self.size, self.fp.tell(), ("P", 0, 1))]
def _open(self): # The Sun Raster file header is 32 bytes in length and has the following format: # typedef struct _SunRaster # { # DWORD MagicNumber; /* Magic (identification) number */ # DWORD Width; /* Width of image in pixels */ # DWORD Height; /* Height of image in pixels */ # DWORD Depth; /* Number of bits per pixel */ # DWORD Length; /* Size of image data in bytes */ # DWORD Type; /* Type of raster file */ # DWORD ColorMapType; /* Type of color map */ # DWORD ColorMapLength; /* Size of the color map in bytes */ # } SUNRASTER; # HEAD s = self.fp.read(32) if i32(s) != 0x59a66a95: raise SyntaxError("not an SUN raster file") offset = 32 self.size = i32(s[4:8]), i32(s[8:12]) depth = i32(s[12:16]) data_length = i32(s[16:20]) # unreliable, ignore. file_type = i32(s[20:24]) palette_type = i32(s[24:28]) # 0: None, 1: RGB, 2: Raw/arbitrary palette_length = i32(s[28:32]) if depth == 1: self.mode, rawmode = "1", "1;I" elif depth == 4: self.mode, rawmode = "L", "L;4" elif depth == 8: self.mode = rawmode = "L" elif depth == 24: if file_type == 3: self.mode, rawmode = "RGB", "RGB" else: self.mode, rawmode = "RGB", "BGR" elif depth == 32: if file_type == 3: self.mode, rawmode = 'RGB', 'RGBX' else: self.mode, rawmode = 'RGB', 'BGRX' else: raise SyntaxError("Unsupported Mode/Bit Depth") if palette_length: if palette_length > 1024: raise SyntaxError("Unsupported Color Palette Length") if palette_type != 1: raise SyntaxError("Unsupported Palette Type") offset = offset + palette_length self.palette = ImagePalette.raw("RGB;L", self.fp.read(palette_length)) if self.mode == "L": self.mode = "P" rawmode = rawmode.replace('L', 'P') # 16 bit boundaries on stride stride = ((self.size[0] * depth + 15) // 16) * 2 # file type: Type is the version (or flavor) of the bitmap # file. The following values are typically found in the Type # field: # 0000h Old # 0001h Standard # 0002h Byte-encoded # 0003h RGB format # 0004h TIFF format # 0005h IFF format # FFFFh Experimental # Old and standard are the same, except for the length tag. # byte-encoded is run-length-encoded # RGB looks similar to standard, but RGB byte order # TIFF and IFF mean that they were converted from T/IFF # Experimental means that it's something else. # (http://www.fileformat.info/format/sunraster/egff.htm) if file_type in (0, 1, 3, 4, 5): self.tile = [("raw", (0, 0)+self.size, offset, (rawmode, stride))] elif file_type == 2: self.tile = [("sun_rle", (0, 0)+self.size, offset, rawmode)] else: raise SyntaxError('Unsupported Sun Raster file type')