def _prepare_slide(self, mols, properties): if mols is None: raise ValueError( 'Expected a list of rdkit molecule instances but got \'None\'') # cut-off mols + properties silently. I think this is better than raising a ValueError mols = mols[:self.max_mols] properties = properties[:self.max_mols] if self.number_of_properties > 0 and len(mols) != len(properties): raise ValueError( 'Number of molecules must match number of properties.') if len(mols) == self.max_mols: slide = Image.new('RGBA', [self.slide_width, self.slide_height], (255, 255, 255, 0)) # less mols -> smaller image elif len(mols) < self.mols_per_row: slide = Image.new('RGBA', [len(mols) * self.image_width, self.row_height], (255, 255, 255, 0)) else: num_rows = (len(mols) // self.mols_per_row) + 1 slide = Image.new('RGBA', [self.slide_width, self.row_height * num_rows], (255, 255, 255, 0)) png_info = PngInfo() png_info.add_text('numProperties', str(self.number_of_properties)) return mols, slide, png_info
def main(): try: opts, args = getopt.getopt(sys.argv[1:], "t:o:s:h", ["text=", "output=", "script=", "help"]) except getopt.GetoptError as err: # print help information and exit: print(err) # will print something like "option -a not recognized" usage() sys.exit(2) output = None text = None script = None for o, a in opts: if o in ("-t", "--text"): text = a elif o in ("-o", "--output"): output = a elif o in ("-s", "--script"): script = a elif o in ("-h", "--help"): usage() sys.exit() else: assert False, "unhandled option" img = scriptList[script](text, method=Image.LANCZOS) metadata = PngInfo() metadata.add_itxt("Content", text) img.save(output, pnginfo=metadata)
def add_png_metadata(self, path, glyph): ''' given the path to a png file and the glyph it contains, write appropriate metadata fields into the file ''' self.metadata['Description'] = self.metadata[ '_Description_tmpl'].format(glyph=glyph) self.metadata['Title'] = self.metadata['_Title_tmpl'].format( glyph=glyph, border=self.args['bordercolor_name'], color=self.args['bgcolor_name']) with Image.open(path) as image: info = PngInfo() for entry in ["Author", "Description", "Title", "Software"]: if not entry.startswith('_'): info.add_itxt(entry, self.metadata[entry], "en", entry) basename, filename = os.path.split(path) newname = os.path.join(basename, 'new-' + filename) image.save(newname, pnginfo=info) if self.args['verbose']: with Image.open(newname) as image: print("new image is", newname) print(image.info) os.unlink(path) os.rename(newname, path)
def print_png(self, path_or_stream, *, dryrun=False, metadata=None, pil_kwargs=None, **kwargs): _check_print_extra_kwargs(**kwargs) img = self._get_fresh_straight_rgba8888() if dryrun: return metadata = { "Software": f"matplotlib version {mpl.__version__}, https://matplotlib.org", **(metadata if metadata is not None else {}), } if pil_kwargs is None: pil_kwargs = {} # Only use the metadata kwarg if pnginfo is not set, because the # semantics of duplicate keys in pnginfo is unclear. if "pnginfo" not in pil_kwargs: pnginfo = PngInfo() for k, v in metadata.items(): pnginfo.add_text(k, v) pil_kwargs["pnginfo"] = pnginfo pil_kwargs.setdefault("dpi", (self.figure.dpi, self.figure.dpi)) Image.fromarray(img).save(path_or_stream, format="png", **pil_kwargs)
def print_png(self, filename_or_obj, *args, metadata=None, pil_kwargs=None, **kwargs): if metadata is None: metadata = {} if pil_kwargs is None: pil_kwargs = {} metadata = { "Software": f"matplotlib version{__version__}, http://matplotlib.org/", **metadata, } if "pnginfo" not in pil_kwargs: pnginfo = PngInfo() for k, v in metadata.items(): pnginfo.add_text(k, v) pil_kwargs["pnginfo"] = pnginfo pil_kwargs.setdefault("dpi", (self.figure.dpi, self.figure.dpi)) data = self.get_pixel_data() (Image.fromarray(data).save(filename_or_obj, format="png", **pil_kwargs))
def test_pil_kwargs_png(): from PIL.PngImagePlugin import PngInfo buf = io.BytesIO() pnginfo = PngInfo() pnginfo.add_text("Software", "test") plt.figure().savefig(buf, format="png", pil_kwargs={"pnginfo": pnginfo}) im = Image.open(buf) assert im.info["Software"] == "test"
def test_imsave_pil_kwargs_png(): from PIL.PngImagePlugin import PngInfo buf = io.BytesIO() pnginfo = PngInfo() pnginfo.add_text("Software", "test") plt.imsave(buf, [[0, 1], [2, 3]], format="png", pil_kwargs={"pnginfo": pnginfo}) im = Image.open(buf) assert im.info["Software"] == "test"
def test_pil_kwargs_png(): Image = pytest.importorskip("PIL.Image") from PIL.PngImagePlugin import PngInfo buf = io.BytesIO() pnginfo = PngInfo() pnginfo.add_text("Software", "test") plt.figure().savefig(buf, format="png", pil_kwargs={"pnginfo": pnginfo}) im = Image.open(buf) assert im.info["Software"] == "test"
def display_pil_image(img): """displayhook function for PIL Images, rendered as PNG""" # pull metadata from the image, if there metadata = PngInfo() for k, v in img.info.items(): metadata.add_text(k, v) bio = BytesIO() img.save(bio, format='PNG', pnginfo=metadata) return bio.getvalue()
def __init__(self, image: ndarray, from_color_space: str = None, to_color_space: str = None): raiseif( not isinstance(image, ndarray), UnearthtimeException(f'Image is type [{type(image)}] and must be type `numpy.ndarray`') ) self.__image, self.__color_space = Image.__resolve_image(image, from_color_space, to_color_space) self.__hash = 0 self.__height, self.__width = image.shape[0], image.shape[1] self.__info = PngInfo()
def patch_image(): # <- image with a text inside can be loaded as a playlist with open("proto_image.png", mode="rb") as i: img = Image.open(i) info = PngInfo() with open("attacking_playlist.m3u") as pl: text = pl.read() info.add_text("attack", text) img.save("vuln_image.png", "PNG", pnginfo=info)
def _include_provenance_png(filename, attributes): pnginfo = PngInfo() exif_tags = { 'provenance': 'ImageHistory', 'caption': 'ImageDescription', 'software': 'Software', } for key, value in attributes.items(): pnginfo.add_text(exif_tags.get(key, key), value, zip=True) with Image.open(filename) as image: image.save(filename, pnginfo=pnginfo)
def add_tags_to_png_file(fpath): try: info = create_file_info(fpath) png_image = PngImageFile(open(fpath, 'rb')) png_info = PngInfo() for k, v in info.items(): png_info.add_text(k, v) png_image.save(fpath, pnginfo=png_info) except (Exception, OSError): print("WARNING: Could not add debug info to file '{}'.".format(fpath)) traceback.print_exc()
def stack_card(image_load_args, fmt, frame_num, role_num, attr_num): global G_CARD_ICON_ATLAS if not frame_num < 4: return (1, None) if not role_num < 5: return (1, None) if not attr_num < 7: return (1, None) if not G_CARD_ICON_ATLAS: try: G_CARD_ICON_ATLAS = Image.open( os.path.join(os.path.dirname(__file__), "assets", "cardicons.png")) except IOError: return (1, None) try: face = load_image(*image_load_args) except IOError: return (1, None) icon = Image.new("RGBA", (128, 128)) icon.paste(face, ((icon.size[0] - face.size[0]) // 2, (icon.size[1] - face.size[1]) // 2)) if frame_num: icon.alpha_composite(G_CARD_ICON_ATLAS, source=frame_coordinate[frame_num]) if attr_num: icon.alpha_composite(G_CARD_ICON_ATLAS, (97, 3), attribute_coordinate[attr_num]) if role_num: icon.alpha_composite(G_CARD_ICON_ATLAS, (2, 98), role_coordinate[role_num]) bio = io.BytesIO() try: if fmt == "jpg": icon.convert("RGB").save(bio, "jpeg", quality=90, exif=make_icon_exif( frame_num, attr_num, role_num)) else: meta = PngInfo() meta.add_text("Skyfarer", f"v:2,f:{frame_num},a:{attr_num},r:{role_num}") icon.save(bio, "png", optimize=True, pnginfo=meta) except IOError: return (3, None) return (0, bio.getvalue())
def capture(): if args is None: raise RuntimeError("Something horribly wrong has happened") if "pullTime" not in request.form: return {"error": "No pull time!"}, 400 pull_time = request.form["pullTime"] if not pull_time.isdigit(): return {"error": "Invalid pull time"}, 400 pull_time = int(pull_time) img = request.form["image"] resp = urlopen(img) machine = request.form["machine"] now = int(time.time()) ext = ".png" name = f"{now}-{machine}{ext}" fs, path = mkstemp(suffix=ext) os.close(fs) try: with open(path, "wb") as ofs: ofs.write(resp.file.read()) img = load_img(path) finally: os.remove(path) # Simple way of storing more information about the image metadata = PngInfo() fields_to_not_attach = {"image"} for key, value in request.form.items(): if key in fields_to_not_attach: continue metadata.add_text(key, value) # Don't do this every request if args.capture_split: split_ratios = [int(x) for x in args.split_ratio.split(",")] ratio_total = sum(split_ratios) rand = random.randint(0, ratio_total) limit = 0 for i, split in enumerate(split_ratios): limit += split if rand <= limit: output_dir = os.path.join(args.capture_dir, OUTPUTS[i]) if not os.path.isdir(output_dir): os.mkdir(output_dir) output_dir = os.path.join(output_dir, str(pull_time)) break else: output_dir = os.path.join(args.capture_dir, str(pull_time)) if not os.path.isdir(output_dir): os.mkdir(output_dir) img.save(os.path.join(output_dir, name), pnginfo=metadata) return {}
def savemeta(*args, **kwargs): path = args[1] if path.endswith(".fig"): import pickle as pkl pkl.dump(args[0], open(path, 'wb')) else: mpl_savefig(*args, **kwargs) #fig = args[0] if path.endswith(".png"): targetImage = PngImageFile(path) metadata = PngInfo() metadata.add_text("Description", str(meta)) targetImage.save(path, pnginfo=metadata)
def write_png_metadata(filename, settings): targetImage = PngImageFile(filename) metadata = PngInfo() for (k, v) in settings.items(): if type(v) == list: value = "" for item in v: value += str(item) + " " v = value if type(v) == bool: v = str(v) if v is None: continue else: metadata.add_text(k, str(v)) targetImage.save(filename, pnginfo=metadata)
def sample_image_path(self): """Copy the sample image to to a unique file name and return the path. Returns: tuple of (the filename, the new sample image path) """ filename = self.generate_alphanumeric() + ".png" new_file = self.tmp_file_path(filename) shutil.copy(self.original_sample_image_path, new_file) # make the image content unique image_file = PngImageFile(open(new_file, "r")) info = PngInfo() info.add_text('Comment', self.generate_alphanumeric(length=30)) image_file.save(new_file, pnginfo=info) self.temp_files.append(new_file) return (filename, new_file)
def segment_image(filename, image, mask, root_max_radius=15, min_dimension=50, smooth=1): #mask = _nd.binary_erosion(mask==mask.max(), iterations= mask = mask == mask.max() # find the bounding box, and crop image and mask bbox = _nd.find_objects(mask)[0] mask = mask[bbox] img = image[bbox] if smooth: smooth_img = _nd.gaussian_filter(img * mask, sigma=smooth) smooth_img /= _np.maximum( _nd.gaussian_filter(mask.astype('f'), sigma=smooth), 2**-10) img[mask] = smooth_img[mask] # background removal _print_state(verbose, 'remove background') img = _remove_background(img, distance=root_max_radius, smooth=1) img *= _nd.binary_erosion(mask, iterations=root_max_radius) # image binary segmentation _print_state(verbose, 'segment binary mask') rmask = _segment_root(img) rmask[-mask] = 0 if min_dimension > 0: cluster = _nd.label(rmask)[0] cluster = _clean_label(cluster, min_dim=min_dimension) rmask = cluster > 0 # save the mask, and bbox from PIL.PngImagePlugin import PngInfo meta = PngInfo() meta.add_text( 'bbox', repr([(bbox[0].start, bbox[0].stop), (bbox[1].start, bbox[1].stop)])) _Image(rmask).save(filename, dtype='uint8', scale=255, pnginfo=meta) return rmask, bbox
def replace_meta(filename, fields): metaname = get_dumpfile(filename) with open(metaname) as json_file: meta = json.load(json_file) if fields and fields[0] != '*': print(f"overwriting metadata[{fields}] in {filename} from {metaname}") newmeta = {} for f in fields: newmeta[f] = meta[f] else: print(f"overwriting metadata in {filename} from {metaname}") newmeta = meta newmeta['Metadata Modification Time'] = f"{datetime.now()}" img = PngImageFile(filename) metadata = PngInfo() for f in newmeta: metadata.add_text(f, newmeta[f]) img.save(filename, pnginfo=metadata)
def on_save_clicked(self, button): current_path = self.entry.get_text() # write metadata metadata = PngInfo() metadata.add_text("screenshat", self.entryy.get_text()) img = PngImageFile(img_path) img.save(img_path, pnginfo=metadata) # Update config file pathlib.Path(config_path).write_text(current_path) # in doubt do mkdir -p new directory pathlib.Path(current_path).mkdir(parents=True, exist_ok=True) # move file shutil.move(img_path, current_path) self.destroy()
def encrypt_png(self, in_path: PathType, out_path: PathType) -> None: image = PngImageFile(in_path) # convert pixels to bytes in order to encrypt them im_bytes = bytearray(self.__get_pixels(image)) # get random IV and calculate MAC iv = get_random_bytes(CryptoAES.block_size) h = HMAC.new(self.__key, digestmod=SHA256) h.update(im_bytes) # create metadata object in order to save IV and MAC to image metadata = PngInfo() metadata.add_text('iv', iv.hex()) metadata.add_text('mac', h.hexdigest()) print(f'writing IV = {iv.hex()} and MAC = {h.hexdigest()} to image metadata') # encrypt image cipher = CryptoAES.new(self.__key, CryptoAES.MODE_ECB) enc_data = cipher.encrypt(im_bytes) # write image to file with metadata image.frombytes(enc_data) image.save(out_path, pnginfo=metadata)
def encode(filename, mode="RGB", verbose=False): if mode == "P": print( "invalid choice: 'P' for PNG (choose from '1', 'L', 'RGB', 'RGBA')" ) sys.exit() if mode not in ["RGB", "L"]: print(mode, "coming soon") sys.exit() try: with open(filename, 'rb') as f: data = f.read() except IOError as e: print(e) sys.exit() output_filename = change_ext(filename, "png") output_size, extra_bytes_lenght = calc_size(len(data), mode) data += (b'\0' * extra_bytes_lenght) metadata = PngInfo() metadata.add_text("filename", str(os.path.basename(filename))) metadata.add_text("extra_bytes", str(extra_bytes_lenght)) if os.path.isfile(output_filename): output_filename = get_unique_filename(output_filename) im = Image.frombytes(mode, output_size, data) im.save(output_filename, pnginfo=metadata) if verbose: print(f"Extra bytes: {extra_bytes_lenght}") print(f"Image size: {output_size}") print(f"Image mode: {im.mode}") print(f"Filename: {output_filename}") sys.exit()
def hide_information(self): """ Method to hide a key into the given image """ #first of all is to change original cover file metadata #because if we do it later, bits will be changed and key # will not be recovered _tmp_image = self.image_object.copy() max_with = _tmp_image.size[0] - 1 (coord_x, coord_y) = (0, 0) for pixel in self.encode_imdata(_tmp_image.getdata(), self.key_to_hide): _tmp_image.putpixel((coord_x, coord_y), pixel) if (coord_x == max_with): coord_x = 0 coord_y += 1 else: coord_x += 1 #save the resulting image #image_format = self.image_where_to_hide.split(".")[1] image_format = "png" f_name = self.image_where_to_hide.split( ".")[0] + "_hide." + image_format metadata = PngInfo() metadata.add_text("url", self.url_metadata) metadata.add_text("language", self.url_language) _tmp_image.save(f_name, image_format.upper(), pnginfo=metadata)
def to_file(self, filename, *, palette=False): # assemble comment comment = self._assemble_comment() # assemble spritesheet W, H = self.width, self.height num_frames = sum(len(state.frames) for state in self.states) sqrt = math.ceil(math.sqrt(num_frames)) output = Image.new('RGBA', (sqrt * W, math.ceil(num_frames / sqrt) * H)) i = 0 for state in self.states: for frame in state.frames: output.paste(frame, ((i % sqrt) * W, (i // sqrt) * H)) i += 1 # save pnginfo = PngInfo() pnginfo.add_text('Description', comment, zip=True) if palette: output = output.convert('P') output.save(filename, 'png', optimize=True, pnginfo=pnginfo)
def encode_to_img(self, secret: str, scale: bool = True) -> PixelImage: """Encodes secret to a colorful png square Parameters ___________ secret: str Secret to be encoded consisting of any utf8 chars including whitespace ones scale: bool = True Whether or not to enlarge output image. Only enourmous secrets will produce image bigger than a couple pixels by a couple pixels which is miserable to look at. Basically a choice of aesthetics over pragmatism""" self.encode(secret) edge, *_ = self.arr.shape metadata = PngInfo() if scale and (scale_up := (self.max_scale_up // edge)) > 1: s = edge * scale_up img = Image.fromarray(self.arr).resize((s, s), Image.NEAREST) metadata.add_text("edge", str(edge)) return PixelImage(img, pnginfo=metadata)
def save_image(cls, img, outfile, info=None): ''' Given an image and an optional metadata dictionary, write the image as .png, include the metadata. :param img: image to save :type img: np_array :param outfile: destination path :type outfile: str :param info: metadata to add :type info: {str : str} ''' # Create metadata to include in the # spectrogram .png file: if info is not None: metadata = PngInfo() for key, val in info.items(): metadata.add_text(key, str(val)) skimage.io.imsave(outfile, img, pnginfo=metadata)
def find_plate(filename, image, plate_width): from ..image.circle import detect_circles mask = image > .6 ## constant cluster = _clean_label(_nd.label(mask)[0], min_dim=100) ## cosntant # find plate mask and hull pmask, phull = mask_fillhull(cluster > 0) pwidth = pmask.sum()**.5 # estimated plate width in pixels border = pwidth * .03 ## constant 3% of plate width pmask = pmask + 2 * _nd.binary_erosion(pmask > 0, iterations=int(border)) px_scale = plate_width / pwidth # detect codebar box as the biggest connex cluster cbmask = _nd.label( _nd.binary_closing((cluster > 0) & (pmask == 3), iterations=5))[0] obj = _nd.find_objects(cbmask) cbbox = _np.argmax([max([o.stop - o.start for o in ob]) for ob in obj]) + 1 # find codebar mask and hull cbmask, cbhull = mask_fillhull(cbmask == cbbox) # stack masks such that # pixels to process = 3, # codebar pixels = 2 # plate border = 1 ##mask = pmask + 1*cbmask + 1*_nd.binary_erosion(pmask, iterations=int(border)) pmask[cbmask > 0] = 2 # save plate mask from PIL.PngImagePlugin import PngInfo meta = PngInfo() meta.add_text('px_scale', repr(px_scale)) _Image(pmask).save(filename, dtype='uint8', scale=85, pnginfo=meta) # 85 = 255/pmask.max() return pmask, px_scale
def saveImg(self, path): """ Save information as an image """ arr = self.grid arr[self.coffset[1], self.coffset[0]] = 0.8 # Save map info meta = PngInfo() meta.add_text("iposx", str(self.ipos[0])) meta.add_text("iposy", str(self.ipos[1])) meta.add_text("tsize", str(self.tsize)) arr = np.flipud(arr) img = Image.fromarray(np.uint8(arr * 255), 'L') img.save(path, "PNG", pnginfo=meta)
def _get_EXIF_DateTimeOriginal(self, file_path): """ try to get the recording date from the EXIF in PNG file """ try: image = PngImageFile(file_path) metadata = PngInfo() exif_array = [] for i in image.text: compile = i, str(image.text[i]) exif_array.append(compile) if len(exif_array) > 0: header = exif_array[0][0] if header.startswith("XML"): xml = exif_array[0][1] for line in xml.splitlines(): if 'DateCreated' in line: idx1 = line.find('>') idx2 = line.rfind('<') if (idx1 != -1) and (idx2 != -1): dt = line[idx1 + 1:idx2] return dt except Exception as err: pass # returns None return None
def decrypt_png(self, in_path: PathType, out_path: PathType) -> None: image = PngImageFile(in_path) iv: Optional[str] = None mac: Optional[str] = None # try to get IV from metadata try: iv = image.text['iv'] print(f'found IV = {iv}') except KeyError: print('IV was not found in file') # try to get MAC from metadata try: mac = image.text['mac'] print(f'found MAC = {mac}') except KeyError: print('MAC was not found in file') # convert pixels to bytes in order to decrypt them im_bytes = bytearray(self.__get_pixels(image)) # decrypt image cipher = CryptoAES.new(self.__key, CryptoAES.MODE_ECB) dec: bytes = cipher.decrypt(im_bytes) # try to verify MAC try: self.__hmac.update(dec) self.__hmac.verify(bytes.fromhex(mac)) print('MAC is valid') except ValueError: print('MAC is invalid') # don't forget about metadata metadata = PngInfo() metadata.add_text('iv', iv) metadata.add_text('mac', mac) # save decrypted image to file image.frombytes(dec) image.save(out_path, pnginfo=metadata)
def print_png(self, filename_or_obj, *args, metadata=None, pil_kwargs=None, **kwargs): """ Write the figure to a PNG file. Parameters ---------- filename_or_obj : str or PathLike or file-like object The file to write to. metadata : dict, optional Metadata in the PNG file as key-value pairs of bytes or latin-1 encodable strings. According to the PNG specification, keys must be shorter than 79 chars. The `PNG specification`_ defines some common keywords that may be used as appropriate: - Title: Short (one line) title or caption for image. - Author: Name of image's creator. - Description: Description of image (possibly long). - Copyright: Copyright notice. - Creation Time: Time of original image creation (usually RFC 1123 format). - Software: Software used to create the image. - Disclaimer: Legal disclaimer. - Warning: Warning of nature of content. - Source: Device used to create the image. - Comment: Miscellaneous comment; conversion from other image format. Other keywords may be invented for other purposes. If 'Software' is not given, an autogenerated value for matplotlib will be used. For more details see the `PNG specification`_. .. _PNG specification: \ https://www.w3.org/TR/2003/REC-PNG-20031110/#11keywords pil_kwargs : dict, optional If set to a non-None value, use Pillow to save the figure instead of Matplotlib's builtin PNG support, and pass these keyword arguments to `PIL.Image.save`. If the 'pnginfo' key is present, it completely overrides *metadata*, including the default 'Software' key. """ from matplotlib import _png if metadata is None: metadata = {} metadata = { "Software": f"matplotlib version{__version__}, http://matplotlib.org/", **metadata, } if pil_kwargs is not None: from PIL import Image from PIL.PngImagePlugin import PngInfo buf, size = self.print_to_buffer() # Only use the metadata kwarg if pnginfo is not set, because the # semantics of duplicate keys in pnginfo is unclear. if "pnginfo" not in pil_kwargs: pnginfo = PngInfo() for k, v in metadata.items(): pnginfo.add_text(k, v) pil_kwargs["pnginfo"] = pnginfo pil_kwargs.setdefault("dpi", (self.figure.dpi, self.figure.dpi)) (Image.frombuffer("RGBA", size, buf, "raw", "RGBA", 0, 1) .save(filename_or_obj, format="png", **pil_kwargs)) else: FigureCanvasAgg.draw(self) renderer = self.get_renderer() with cbook._setattr_cm(renderer, dpi=self.figure.dpi), \ cbook.open_file_cm(filename_or_obj, "wb") as fh: _png.write_png(renderer._renderer, fh, self.figure.dpi, metadata=metadata)
def generateComics( self, instance ): image = Image.new( mode="1", size=(0, 0) ) for self.generatedComicNumber in range( self.numberOfComics ): try: if self.commandLineComicID is None: wordBubbleFileName = random.choice( os.listdir( self.wordBubblesDir ) ) else: wordBubbleFileName = os.path.join( self.wordBubblesDir, self.commandLineComicID + ".tsv" ) except IndexError as error: six.print_( error, file=sys.stderr ) exit( EX_NOINPUT ) if not self.silence: six.print_( "wordBubbleFileName:", wordBubbleFileName ) if self.commandLineComicID is None: comicID = os.path.splitext( wordBubbleFileName )[ 0 ] else: comicID = self.commandLineComicID wordBubbleFileName = os.path.join( self.wordBubblesDir, wordBubbleFileName ) if not self.silence: six.print_( "Loading word bubbles from", wordBubbleFileName ) try: wordBubbleFile = open( wordBubbleFileName, mode="rt" ) except OSError as error: six.print_( error, file=sys.stderr ) exit( EX_NOINPUT ) if not idChecker.checkFile( wordBubbleFile, wordBubbleFileName, self.commentMark ): six.print_( "Error: Word bubble file", wordBubbleFileName, "is not in the correct format." ) exit( EX_DATAERR ) lookForSpeakers = True speakers = [] while lookForSpeakers: line = wordBubbleFile.readline() if len( line ) > 0: line = line.partition( self.commentMark )[0].strip() if len( line ) > 0: speakers = line.upper().split( "\t" ) if len( speakers ) > 0: lookForSpeakers = False else: lookForSpeakers = False; #End of file reached, no speakers found if len( speakers ) == 0: six.print_( "Error: Word bubble file", wordBubbleFileName, "contains no speakers." ) exit( EX_DATAERR ) if not self.silence: six.print_( "These characters speak:", speakers ) for speaker in speakers: if speaker not in self.generators: if not self.silence: six.print_( "Now building a Markov graph for character", speaker, "..." ) newGenerator = Generator( charLabel = speaker, cm = self.commentMark, randomizeCapitals = self.randomizeCapitals ) newGenerator.buildGraph( self.inDir ) if not self.silence: newGenerator.showStats() self.generators[ speaker ] = newGenerator if not self.silence: six.print_( comicID ) inImageFileName = os.path.join( self.imageDir, comicID + ".png" ) try: image = Image.open( inImageFileName ).convert() #Text rendering looks better if we ensure the image's mode is not palette-based. Calling convert() with no mode argument does this. except IOError as error: six.print_( error, file=sys.stderr ) exit( EX_NOINPUT ) transcript = str( comicID ) + "\n" previousBox = ( int( -1 ), int( -1 ), int( -1 ), int( -1 ) ) #For detecting when two characters share a speech bubble; don't generate text twice. for line in wordBubbleFile: line = line.partition( self.commentMark )[ 0 ].strip() if len( line ) > 0: line = line.split( "\t" ) character = line[ 0 ].rstrip( ":" ).strip().upper() try: generator = self.generators[ character ] except: six.print_( "Error: Word bubble file", wordBubbleFileName, "does not list", character, "in its list of speakers.", file=sys.stderr ) exit( EX_DATAERR ) topLeftX = int( line[ 1 ] ) topLeftY = int( line[ 2 ] ) bottomRightX = int( line[ 3 ] ) bottomRightY = int( line[ 4 ] ) box = ( topLeftX, topLeftY, bottomRightX, bottomRightY ) if box != previousBox: previousBox = box text = "" nodeList = generator.generateSentences( 1 )[ 0 ] for node in nodeList: text += node.word + " " text.rstrip() oneCharacterTranscript = character + ": " oneCharacterTranscript += self.stringFromNodes( nodeList ) if not self.silence: six.print_( oneCharacterTranscript ) oneCharacterTranscript += "\n" transcript += oneCharacterTranscript wordBubble = image.crop( box ) draw = ImageDraw.Draw( wordBubble ) width = bottomRightX - topLeftX if width <= 0: #Width must be positive width = 1 height = bottomRightY - topLeftY if height <= 0: height = 1 size = int( height * 1.2 ) #Contrary to the claim by PIL's documentation, font sizes are apparently in pixels, not points. The size being requested is the height of a generic character; the actual height of any particular character will be approximately (not exactly) the requested size. We will try smaller and smaller sizes in the while loop below. The 1.2, used to account for the fact that real character sizes aren't exactly the same as the requested size, I just guessed an appropriate value. normalFont = ImageFont.truetype( self.normalFontFile, size = size ) boldFont = ImageFont.truetype( self.boldFontFile, size = size ) listoflists = self.rewrap_nodelistlist( nodeList, normalFont, boldFont, width, fontSize = size ) margin = 0 offset = originalOffset = 0 goodSizeFound = False while not goodSizeFound: goodSizeFound = True totalHeight = 0 for line in listoflists: lineWidth = 0 lineHeight = 0 for node in line: wordSize = normalFont.getsize( node.word + " " ) lineWidth += wordSize[ 0 ] lineHeight = max( lineHeight, wordSize[ 1 ] ) lineWidth -= normalFont.getsize( " " )[ 0 ] totalHeight += lineHeight if lineWidth > width: goodSizeFound = False if totalHeight > height: goodSizeFound = False if not goodSizeFound: size -= 1 try: normalFont = ImageFont.truetype( self.normalFontFile, size = size ) boldFont = ImageFont.truetype( self.boldFontFile, size = size ) except IOError as error: six.print_( error, "\nUsing default font instead.", file=sys.stderr ) normalFont = ImageFont.load_default() boldFont = ImageFont.load_default() listoflists = self.rewrap_nodelistlist( nodeList, normalFont, boldFont, width, fontSize = size ) midX = int( wordBubble.size[ 0 ] / 2 ) midY = int( wordBubble.size[ 1 ] / 2 ) try: #Choose a text color that will be visible against the background backgroundColor = ImageStat.Stat( wordBubble ).mean #wordBubble.getpixel( ( midX, midY ) ) textColorList = [] useIntegers = False useFloats = False if wordBubble.mode.startswith( "1" ): bandMax = 1 useIntegers = True elif wordBubble.mode.startswith( "L" ) or wordBubble.mode.startswith( "P" ) or wordBubble.mode.startswith( "RGB" ) or wordBubble.mode.startswith( "CMYK" ) or wordBubble.mode.startswith( "YCbCr" ) or wordBubble.mode.startswith( "LAB" ) or wordBubble.mode.startswith( "HSV" ): bandMax = 255 useIntegers = True elif wordBubble.mode.startswith( "I" ): bandMax = 2147483647 #max for a 32-bit signed integer useIntegers = True elif wordBubble.mode.startswith( "F" ): bandMax = float( "infinity" ) useFloats = True else: #I've added all modes currently supported according to Pillow documentation; this is for future compatibility bandMax = max( ImageStat.Stat( image ).extrema ) for c in backgroundColor: d = bandMax - ( c * 1.5 ) if d < 0: d = 0 if useIntegers: d = int( d ) elif useFloats: d = float( d ) textColorList.append( d ) if wordBubble.mode.endswith( "A" ): #Pillow supports two modes with alpha channels textColorList[ -1 ] = bandMax textColor = tuple( textColorList ) except ValueError: textColor = "black" offset = originalOffset for line in listoflists: xOffset = 0 yOffsetAdditional = 0 for node in line: usedFont = node.font draw.text( ( margin + xOffset, offset ), node.word + " ", font = usedFont, fill = textColor ) tempSize = usedFont.getsize( node.word + " " ) xOffset += tempSize[ 0 ] yOffsetAdditional = max( yOffsetAdditional, tempSize[ 1 ] ) node.unselectStyle() offset += yOffsetAdditional image.paste( wordBubble, box ) wordBubbleFile.close() if self.numberOfComics > 1: oldOutTextFileName = self.outTextFileName temp = os.path.splitext(self.outTextFileName ) self.outTextFileName = temp[ 0 ] + str( self.generatedComicNumber ) + temp[ 1 ] #---------------------------Split into separate function try: #os.makedirs( os.path.dirname( outTextFileName ), exist_ok = True ) outFile = open( self.outTextFileName, mode="wt" ) except OSError as error: six.print_( error, "\nUsing standard output instead", file=sys.stderr ) outFile = sys.stdout if self.numberOfComics > 1: self.outTextFileName = oldOutTextFileName six.print_( transcript, file=outFile ) outFile.close() if self.numberOfComics > 1: oldOutImageFileName = self.outImageFileName temp = os.path.splitext( self.outImageFileName ) outImageFileName = temp[ 0 ] + str( self.generatedComicNumber ) + temp[ 1 ] if self.topImageFileName != None: try: topImage = Image.open( self.topImageFileName ).convert( mode=image.mode ) except IOError as error: six.print_( error, file=sys.stderr ) exit( EX_NOINPUT ) oldSize = topImage.size size = ( max( topImage.size[ 0 ], image.size[ 0 ] ), topImage.size[ 1 ] + image.size[ 1 ] ) newImage = Image.new( mode=image.mode, size=size ) newImage.paste( im=topImage, box=( 0, 0 ) ) newImage.paste( im=image, box=( 0, oldSize[ 1 ] ) ) image = newImage originalURL = None URLFile = open( os.path.join( self.inDir, "sources.tsv" ), "rt" ) for line in URLFile: line = line.partition( self.commentMark )[ 0 ].strip() if len( line ) > 0: line = line.split( "\t" ) if comicID == line[ 0 ]: originalURL = line[ 1 ] break; URLFile.close() transcriptWithURL = transcript + "\n" + originalURL #The transcript that gets embedded into the image file should include the URL. The transcript that gets uploaded to blogs doesn't need it, as the URL gets sent anyway. infoToSave = PngInfo() encodingErrors = "backslashreplace" #If we encounter errors during text encoding, I feel it best to replace unencodable text with escape sequences; that way it may be possible for reader programs to recover the original unencodable text. #According to the Pillow documentation, key names should be "latin-1 encodable". I take this to mean that we ourselves don't need to encode it in latin-1. key = "transcript" keyUTF8 = key.encode( "utf-8", errors=encodingErrors ) #uncomment the following if using Python 3 #transcriptISO = transcriptWithURL.encode( "iso-8859-1", errors=encodingErrors ) #transcriptUTF8 = transcriptWithURL.encode( "utf-8", errors=encodingErrors ) #python 2: tempencode = transcriptWithURL.decode( 'ascii', errors='replace' ) # I really don't like using this ascii-encoded intermediary called tempencode, but i couldn't get the program to work when encoding directly to latin-1 transcriptISO = tempencode.encode( "iso-8859-1", errors='replace' ) transcriptUTF8 = tempencode.encode( "utf-8", errors='replace' ) infoToSave.add_itxt( key=key, value=transcriptUTF8, tkey=keyUTF8 ) infoToSave.add_text( key=key, value=transcriptISO ) #GIMP only recognizes comments key = "Comment" keyUTF8 = key.encode( "utf-8", errors=encodingErrors ) infoToSave.add_text( key=key, value=transcriptISO ) infoToSave.add_itxt( key=key, value=transcriptUTF8, tkey=keyUTF8 ) try: #os.makedirs( os.path.dirname( outImageFileName ), exist_ok = True ) if self.saveForWeb: image = image.convert( mode = "P", palette="ADAPTIVE", dither=False ) #Try turning dithering on or off. image.save( self.outImageFileName, format="PNG", optimize=True, pnginfo=infoToSave ) else: image.save( self.outImageFileName, format="PNG", pnginfo=infoToSave ) except IOError as error: six.print_( error, file = sys.stderr ) exit( EX_CANTCREAT ) except OSError as error: six.print_( error, file = sys.stderr ) exit( EX_CANTCREAT ) if not self.silence: six.print_( "Original comic URL:", originalURL ) for blog in self.blogUploaders: blog.upload( postStatus = "publish", inputFileName = outImageFileName, shortComicTitle = self.shortName, longComicTitle = self.longName, transcript = transcript, originalURL = originalURL, silence = self.silence ) if self.numberOfComics > 1: outImageFileName = oldOutImageFileName #end of loop: for generatedComicNumber in range( numberOfComics ): #---------------------------It's display time! if image.mode != "RGB": image = image.convert( mode = "RGB" ) self.gui.comicArea.texture = Texture.create( size = image.size, colorfmt = 'rgb' ) self.gui.comicArea.texture.blit_buffer( pbuffer = image.transpose( Image.FLIP_TOP_BOTTOM ).tobytes(), colorfmt = 'rgb' )
import sys from PIL import Image from PIL.PngImagePlugin import PngInfo png = Image.open('original.png') pixels = png.load() new_pixels = [] with open(sys.argv[1], "r") as kg: new_pixels = kg.read() print "setting %d new pixels" % len(new_pixels) k = 0 for i in range(png.size[0]): # for every pixel: if k >= len(new_pixels): break for j in range(png.size[1]): r, g, b = list(pixels[i, j]) b = new_pixels[k] # change the blue pixel ;) pixels[i, j] = r, g, ord(b) k += 1 if k >= len(new_pixels): break info = PngInfo() info.add_text("flag", "not here ...but john loves that song ;)") png.save('Ub7XL8T.png', 'PNG', compress_level=9, pnginfo=info)