def create_pixel_img( self, scale, text, color, ): '''Creats a new canvas and draws text onto it if overridin''' font = Font(self.new_txt_color(color), self.font, self.font_size) bg_color = self.new_bg_color(color) img = Draw('RGB', scale, bg_color) img.text((0, 7), text, font) return img
def create_pixel_img(self, scale, text, color): '''Creats a new canvas and draws text onto it if overridin''' #define the font and the blank canvas font = Font(self.new_txt_color(color), self.font, self.font_size) #defines the background color of the canvas bg_color = self.new_bg_color(color) img = Draw('RGB', scale, bg_color) #draw text on to the canvas by default only one line of text is drawn #this method can be overriden to draw as much text as you want #Note that the if the scale is not large enough not all the text will be displayed img.text((0, 0), text, font) return img
def draw_text(frame, x, y, text, size=16, color='white'): im = Image.fromarray(frame) draw = Draw(im) _, fh = draw.size font = Font(color, os.path.join(os.path.dirname(__file__), './Reith.ttf'), DRAW_SCALE_FACTOR) text_w, text_h = draw.textsize(text, font) y -= text_h draw.text((x + 2, y + 4), text, font) draw.flush() return np.array(im)
def create_pixel_img( self, scale, text, color, ): '''Creats a new canvas and draws text onto it if overridin''' font = Font(self.new_txt_color(color), self.font, self.font_size) bg_color = self.new_bg_color(color) img = Draw('RGB', scale, bg_color) img.text((8, 0), text[:5], font) img.text((8, 10), text[5:10], font) img.text((8, 20), text[10:15], font) img.text((8, 30), text[15:20], font) img.text((8, 40), text[20:], font) return img
def addCanvasText(self, text, pos, font, color=(0, 0, 0), **kwargs): orientation = kwargs.get('orientation', 'E') color = convertColor(color) aggFont = Font(color, faceMap[font.face], size=font.size * self.fontScale) blocks = list(re.finditer(r'\<(.+?)\>(.+?)\</\1\>', text)) w, h = 0, 0 supH = 0 subH = 0 if not len(blocks): w, h = self.draw.textsize(text, aggFont) tw, th = w, h offset = w * pos[2] dPos = pos[0] - w / 2. + offset, pos[1] - h / 2. self.draw.text(dPos, text, aggFont) else: dblocks = [] idx = 0 for block in blocks: blockStart, blockEnd = block.span(0) if blockStart != idx: # untagged text: tblock = text[idx:blockStart] tw, th = self.draw.textsize(tblock, aggFont) w += tw h = max(h, th) dblocks.append((tblock, '', tw, th)) fmt = block.groups()[0] tblock = block.groups()[1] if fmt in ('sub', 'sup'): lFont = Font(color, faceMap[font.face], size=0.8 * font.size * self.fontScale) else: lFont = aggFont tw, th = self.draw.textsize(tblock, lFont) w += tw if fmt == 'sub': subH = max(subH, th) elif fmt == 'sup': supH = max(supH, th) else: h = max(h, th) dblocks.append((tblock, fmt, tw, th)) idx = blockEnd if idx != len(text): # untagged text: tblock = text[idx:] tw, th = self.draw.textsize(tblock, aggFont) w += tw h = max(h, th) dblocks.append((tblock, '', tw, th)) supH *= 0.5 subH *= 0.5 h += supH + subH offset = w * pos[2] if orientation == 'W': dPos = [pos[0] - w + offset, pos[1] - h / 2.] elif orientation == 'E': dPos = [pos[0] + offset, pos[1] - h / 2.] else: dPos = [pos[0] - w / 2. + offset, pos[1] - h / 2.] if supH: dPos[1] += supH for txt, fmt, tw, th in dblocks: tPos = dPos.copy() if fmt == 'sub': tPos[1] += subH elif fmt == 'sup': tPos[1] -= supH if fmt in ('sub', 'sup'): lFont = Font(color, faceMap[font.face], size=0.8 * font.size * self.fontScale) else: lFont = aggFont self.draw.text(tPos, txt, lFont) dPos[0] += tw return (tw + th * .4, th + th * .4, offset)
def main(): parser = get_parser() args = parser.parse_args() levels = [logging.ERROR, logging.WARN, logging.INFO, logging.DEBUG] logging.basicConfig(level=levels[min(3, args.verbosity)]) if args.output_filename is None: args.output_filename = [x[:-3] + "png" for x in args.input_tiff] else: assert len(args.output_filename) == len( args.input_tiff ), "Output filenames must be equal to number of input tiffs" if not (args.add_borders or args.add_coastlines or args.add_grid or args.add_rivers or args.add_colorbar): LOG.error( "Please specify one of the '--add-X' options to modify the image") return -1 # we may be dealing with large images that look like decompression bombs # let's turn off the check for the image size in PIL/Pillow Image.MAX_IMAGE_PIXELS = None for input_tiff, output_filename in zip(args.input_tiff, args.output_filename): LOG.info("Creating {} from {}".format(output_filename, input_tiff)) gtiff = gdal.Open(input_tiff) proj4_str = osr.SpatialReference(gtiff.GetProjection()).ExportToProj4() ul_x, res_x, _, ul_y, _, res_y = gtiff.GetGeoTransform() half_pixel_x = res_x / 2. half_pixel_y = res_y / 2. area_extent = ( ul_x - half_pixel_x, # lower-left X ul_y + res_y * gtiff.RasterYSize - half_pixel_y, # lower-left Y ul_x + res_x * gtiff.RasterXSize + half_pixel_x, # upper-right X ul_y + half_pixel_y, # upper-right Y ) img = Image.open(input_tiff).convert('RGBA' if gtiff.RasterCount in ( 2, 4) else 'RGB') area_def = (proj4_str, area_extent) cw = ContourWriterAGG(args.shapes_dir) if args.add_coastlines: outline = args.coastlines_outline[0] if len( args.coastlines_outline) == 1 else tuple( int(x) for x in args.coastlines_outline) if args.coastlines_fill: fill = args.coastlines_fill[0] if len( args.coastlines_fill) == 1 else tuple( int(x) for x in args.coastlines_fill) else: fill = None cw.add_coastlines(img, area_def, resolution=args.coastlines_resolution, level=args.coastlines_level, width=args.coastlines_width, outline=outline, fill=fill) if args.add_rivers: outline = args.rivers_outline[0] if len( args.rivers_outline) == 1 else tuple( int(x) for x in args.rivers_outline) cw.add_rivers(img, area_def, resolution=args.rivers_resolution, level=args.rivers_level, width=args.rivers_width, outline=outline) if args.add_borders: outline = args.borders_outline[0] if len( args.borders_outline) == 1 else tuple( int(x) for x in args.borders_outline) cw.add_borders(img, area_def, resolution=args.borders_resolution, level=args.borders_level, outline=outline, width=args.borders_width) if args.add_grid: outline = args.grid_outline[0] if len( args.grid_outline) == 1 else tuple( int(x) for x in args.grid_outline) minor_outline = args.grid_minor_outline[0] if len( args.grid_minor_outline) == 1 else tuple( int(x) for x in args.grid_minor_outline) fill = args.grid_fill[0] if len(args.grid_fill) == 1 else tuple( int(x) for x in args.grid_fill) font_path = find_font(args.grid_font, args.grid_text_size) font = Font(outline, font_path, size=args.grid_text_size) cw.add_grid(img, area_def, args.grid_D, args.grid_d, font, fill=fill, outline=outline, minor_outline=minor_outline, write_text=args.grid_text, width=args.grid_width, lon_placement=args.grid_lon_placement, lat_placement=args.grid_lat_placement) if args.add_colorbar: from pydecorate import DecoratorAGG font_color = args.colorbar_text_color font_color = font_color[0] if len(font_color) == 1 else tuple( int(x) for x in font_color) font_path = find_font(args.colorbar_font, args.colorbar_text_size) # this actually needs an aggdraw font font = Font(font_color, font_path, size=args.colorbar_text_size) band_count = gtiff.RasterCount if band_count not in [1, 2]: raise ValueError("Can't add colorbar to RGB/RGBA image") # figure out what colormap we are dealing with band = gtiff.GetRasterBand(1) cmap = get_colormap(band, band_count) # figure out our limits vmin = args.colorbar_min vmax = args.colorbar_max metadata = gtiff.GetMetadata_Dict() vmin = vmin or metadata.get('min_in') vmax = vmax or metadata.get('max_in') if isinstance(vmin, str): vmin = float(vmin) if isinstance(vmax, str): vmax = float(vmax) if vmin is None or vmax is None: data = gtiff.GetRasterBand(1).ReadAsArray() vmin = vmin or np.iinfo(data.dtype).min vmax = vmax or np.iinfo(data.dtype).max cmap.set_range(vmin, vmax) dc = DecoratorAGG(img) if args.colorbar_align == 'top': dc.align_top() elif args.colorbar_align == 'bottom': dc.align_bottom() elif args.colorbar_align == 'left': dc.align_left() elif args.colorbar_align == 'right': dc.align_right() if args.colorbar_vertical: dc.write_vertically() else: dc.write_horizontally() if args.colorbar_width is None or args.colorbar_height is None: LOG.warning("'--colorbar-width' or '--colorbar-height' were " "not specified. Forcing '--colorbar-extend'.") args.colorbar_extend = True kwargs = {} if args.colorbar_width: kwargs['width'] = args.colorbar_width if args.colorbar_height: kwargs['height'] = args.colorbar_height dc.add_scale(cmap, extend=args.colorbar_extend, font=font, line=font_color, tick_marks=args.colorbar_tick_marks, title=args.colorbar_title, unit=args.colorbar_units, **kwargs) img.save(output_filename)
def add_overlay_from_dict(self, overlays, area_def, cache_epoch=None, background=None): """Create and return a transparent image adding all the overlays contained in the `overlays` dict. :Parameters: overlays : dict overlays configuration area_def : object Area Definition of the creating image cache_epoch: seconds since epoch The latest time allowed for cache the cache file. If the cache file is older than this (mtime), the cache should be regenerated. background: pillow image instance The image on which to write the overlays on. If it's None (default), a new image is created, otherwise the provide background is use an change *in place*. The keys in `overlays` that will be taken into account are: cache, coasts, rivers, borders, cities, grid For all of them except `cache`, the items are the same as the corresponding functions in pycoast, so refer to the docstrings of these functions (add_coastlines, add_rivers, add_borders, add_grid, add_cities). For cache, two parameters are configurable: `file` which specifies the directory and the prefix of the file to save the caches decoration to (for example /var/run/black_coasts_red_borders), and `regenerate` that can be True or False (default) to force the overwriting of an already cached file. """ # Cache management cache_file = None if 'cache' in overlays: cache_file = (overlays['cache']['file'] + '_' + area_def.area_id + '.png') try: config_time = cache_epoch cache_time = os.path.getmtime(cache_file) # Cache file will be used only if it's newer than config file if ((config_time is not None and config_time < cache_time) and not overlays['cache'].get('regenerate', False)): foreground = Image.open(cache_file) logger.info('Using image in cache %s', cache_file) if background is not None: background.paste(foreground, mask=foreground.split()[-1]) return foreground else: logger.info("Regenerating cache file.") except OSError: logger.info("No overlay image found, new overlay image will be saved in cache.") x_size = area_def.width y_size = area_def.height if cache_file is None and background is not None: foreground = background else: foreground = Image.new('RGBA', (x_size, y_size), (0, 0, 0, 0)) default_resolution = get_resolution_from_area(area_def) DEFAULT = {'level': 1, 'outline': 'white', 'width': 1, 'fill': None, 'fill_opacity': 255, 'outline_opacity': 255, 'x_offset': 0, 'y_offset': 0, 'resolution': default_resolution} is_agg = self._draw_module == "AGG" # Coasts, rivers, borders for section, fun in zip(['coasts', 'rivers', 'borders'], [self.add_coastlines, self.add_rivers, self.add_borders]): if section in overlays: params = DEFAULT.copy() params.update(overlays[section]) params['level'] = int(params['level']) params['x_offset'] = float(params['x_offset']) params['y_offset'] = float(params['y_offset']) params['width'] = float(params['width']) params['outline_opacity'] = int(params['outline_opacity']) params['fill_opacity'] = int(params['fill_opacity']) if section != "coasts": params.pop('fill_opacity', None) params.pop('fill', None) if not is_agg: for key in ['width', 'outline_opacity', 'fill_opacity']: params.pop(key, None) fun(foreground, area_def, **params) logger.info("%s added", section.capitalize()) # Cities management if 'cities' in overlays: DEFAULT_FONT_SIZE = 12 DEFAULT_OUTLINE = "yellow" citylist = [s.lstrip() for s in overlays['cities']['list'].split(',')] font_file = overlays['cities']['font'] font_size = int(overlays['cities'].get('font_size', DEFAULT_FONT_SIZE)) outline = overlays['cities'].get('outline', DEFAULT_OUTLINE) pt_size = int(overlays['cities'].get('pt_size', None)) box_outline = overlays['cities'].get('box_outline', None) box_opacity = int(overlays['cities'].get('box_opacity', 255)) self.add_cities(foreground, area_def, citylist, font_file, font_size, pt_size, outline, box_outline, box_opacity) if 'grid' in overlays: lon_major = float(overlays['grid'].get('lon_major', 10.0)) lat_major = float(overlays['grid'].get('lat_major', 10.0)) lon_minor = float(overlays['grid'].get('lon_minor', 2.0)) lat_minor = float(overlays['grid'].get('lat_minor', 2.0)) font = overlays['grid'].get('font', None) font_size = int(overlays['grid'].get('font_size', 10)) write_text = overlays['grid'].get('write_text', True) if isinstance(write_text, str): write_text = write_text.lower() in ['true', 'yes', '1', 'on'] outline = overlays['grid'].get('outline', 'white') if isinstance(font, str): if is_agg: from aggdraw import Font font = Font(outline, font, size=font_size) else: from PIL.ImageFont import truetype font = truetype(font, font_size) fill = overlays['grid'].get('fill', None) minor_outline = overlays['grid'].get('minor_outline', 'white') minor_is_tick = overlays['grid'].get('minor_is_tick', True) if isinstance(minor_is_tick, str): minor_is_tick = minor_is_tick.lower() in ['true', 'yes', '1'] lon_placement = overlays['grid'].get('lon_placement', 'tb') lat_placement = overlays['grid'].get('lat_placement', 'lr') self.add_grid(foreground, area_def, (lon_major, lat_major), (lon_minor, lat_minor), font=font, write_text=write_text, fill=fill, outline=outline, minor_outline=minor_outline, minor_is_tick=minor_is_tick, lon_placement=lon_placement, lat_placement=lat_placement) if cache_file is not None: try: foreground.save(cache_file) except IOError as e: logger.error("Can't save cache: %s", str(e)) if background is not None: background.paste(foreground, mask=foreground.split()[-1]) return foreground
def main(): parser = get_parser() args = parser.parse_args() levels = [logging.ERROR, logging.WARN, logging.INFO, logging.DEBUG] logging.basicConfig(level=levels[min(3, args.verbosity)]) if args.output_filename is None: args.output_filename = [x[:-3] + "png" for x in args.input_tiff] else: assert len(args.output_filename) == len( args.input_tiff ), "Output filenames must be equal to number of input tiffs" if not (args.add_borders or args.add_coastlines or args.add_grid or args.add_rivers or args.add_colorbar): LOG.error("Please specify one of the '--add-X' options to modify the image") return -1 if args.cache_dir and not os.path.isdir(args.cache_dir): LOG.info(f"Creating cache directory: {args.cache_dir}") os.makedirs(args.cache_dir, exist_ok=True) # we may be dealing with large images that look like decompression bombs # let's turn off the check for the image size in PIL/Pillow Image.MAX_IMAGE_PIXELS = None # gather all options into a single dictionary that we can pass to pycoast pycoast_options = _args_to_pycoast_dict(args) for input_tiff, output_filename in zip(args.input_tiff, args.output_filename): LOG.info("Creating {} from {}".format(output_filename, input_tiff)) img = Image.open(input_tiff) img_bands = img.getbands() num_bands = len(img_bands) # P = palette which we assume to be an RGBA colormap img = img.convert("RGBA" if num_bands in (2, 4) or "P" in img_bands else "RGB") if pycoast_options: area_id = os.path.splitext(input_tiff[0])[0] area_def = get_area_def_from_raster(input_tiff, area_id=area_id) cw = ContourWriterAGG(args.shapes_dir) cw.add_overlay_from_dict(pycoast_options, area_def, background=img) if args.add_colorbar: from pydecorate import DecoratorAGG font_color = args.colorbar_text_color font_color = font_color[0] if len(font_color) == 1 else tuple(int(x) for x in font_color) font_path = find_font(args.colorbar_font, args.colorbar_text_size) # this actually needs an aggdraw font font = Font(font_color, font_path, size=args.colorbar_text_size) if num_bands not in (1, 2): raise ValueError("Can't add colorbar to RGB/RGBA image") # figure out what colormap we are dealing with rio_ds = rasterio.open(input_tiff) input_dtype = np.dtype(rio_ds.meta["dtype"]) rio_ct = _get_rio_colormap(rio_ds, 1) cmap = get_colormap(input_dtype, rio_ct, num_bands) # figure out our limits vmin = args.colorbar_min vmax = args.colorbar_max metadata = rio_ds.tags() vmin = vmin or metadata.get("min_in") vmax = vmax or metadata.get("max_in") if isinstance(vmin, str): vmin = float(vmin) if isinstance(vmax, str): vmax = float(vmax) if vmin is None or vmax is None: vmin = vmin or np.iinfo(input_dtype).min vmax = vmax or np.iinfo(input_dtype).max cmap.set_range(vmin, vmax) dc = DecoratorAGG(img) if args.colorbar_align == "top": dc.align_top() elif args.colorbar_align == "bottom": dc.align_bottom() elif args.colorbar_align == "left": dc.align_left() elif args.colorbar_align == "right": dc.align_right() if args.colorbar_vertical: dc.write_vertically() else: dc.write_horizontally() if args.colorbar_width is None or args.colorbar_height is None: LOG.warning( "'--colorbar-width' or '--colorbar-height' were " "not specified. Forcing '--colorbar-extend'." ) args.colorbar_extend = True kwargs = {} if args.colorbar_width: kwargs["width"] = args.colorbar_width if args.colorbar_height: kwargs["height"] = args.colorbar_height dc.add_scale( cmap, extend=args.colorbar_extend, font=font, line=font_color, tick_marks=args.colorbar_tick_marks, title=args.colorbar_title, unit=args.colorbar_units, **kwargs, ) img.save(output_filename)
def _args_to_pycoast_dict(args): opts = {} if args.add_coastlines: outline = ( args.coastlines_outline[0] if len(args.coastlines_outline) == 1 else tuple(int(x) for x in args.coastlines_outline) ) if args.coastlines_fill: fill = ( args.coastlines_fill[0] if len(args.coastlines_fill) == 1 else tuple(int(x) for x in args.coastlines_fill) ) else: fill = None opts["coasts"] = { "resolution": args.coastlines_resolution, "level": args.coastlines_level, "width": args.coastlines_width, "outline": outline, "fill": fill, } if args.add_rivers: outline = ( args.rivers_outline[0] if len(args.rivers_outline) == 1 else tuple(int(x) for x in args.rivers_outline) ) opts["rivers"] = { "resolution": args.rivers_resolution, "level": args.rivers_level, "width": args.rivers_width, "outline": outline, } if args.add_borders: outline = ( args.borders_outline[0] if len(args.borders_outline) == 1 else tuple(int(x) for x in args.borders_outline) ) opts["borders"] = { "resolution": args.borders_resolution, "level": args.borders_level, "width": args.borders_width, "outline": outline, } if args.add_grid: outline = args.grid_outline[0] if len(args.grid_outline) == 1 else tuple(int(x) for x in args.grid_outline) minor_outline = ( args.grid_minor_outline[0] if len(args.grid_minor_outline) == 1 else tuple(int(x) for x in args.grid_minor_outline) ) fill = args.grid_fill[0] if len(args.grid_fill) == 1 else tuple(int(x) for x in args.grid_fill) font_path = find_font(args.grid_font, args.grid_text_size) font = Font(outline, font_path, size=args.grid_text_size) opts["grid"] = { "lon_major": args.grid_D[0], "lat_major": args.grid_D[1], "lon_minor": args.grid_d[0], "lat_minor": args.grid_d[1], "font": font, "fill": fill, "outline": outline, "minor_outline": minor_outline, "write_text": args.grid_text, "width": args.grid_width, "lon_placement": args.grid_lon_placement, "lat_placement": args.grid_lat_placement, } if args.cache_dir: opts["cache"] = { # add "add_coastlines" prefix to cached image name "file": os.path.join(args.cache_dir, "add_coastlines"), "regenerate": args.cache_regenerate, } return opts
def addCanvasText(self, text, pos, font, color=(0, 0, 0), **kwargs): orientation = kwargs.get('orientation', 'E') color = convertColor(color) aggFont = Font(color, faceMap[font.face], size=font.size * self.fontScale) blocks = list(re.finditer(r'\<(.+?)\>(.+?)\</\1\>', text)) w, h = 0, 0 supH = 0 subH = 0 if not len(blocks): w, h = self.draw.textsize(text, aggFont) bw, bh = w * 1.1, h * 1.1 dPos = pos[0] - bw / 2., pos[1] - bh / 2. bgColor = kwargs.get('bgColor', (1, 1, 1)) bgColor = convertColor(bgColor) self.draw.rectangle((dPos[0], dPos[1], dPos[0] + bw, dPos[1] + bh), None, Brush(bgColor)) dPos = pos[0] - w / 2., pos[1] - h / 2. self.draw.text(dPos, text, aggFont) else: dblocks = [] idx = 0 for block in blocks: blockStart, blockEnd = block.span(0) if blockStart != idx: # untagged text: tblock = text[idx:blockStart] tw, th = self.draw.textsize(tblock, aggFont) w += tw h = max(h, th) dblocks.append((tblock, '', tw, th)) fmt = block.groups()[0] tblock = block.groups()[1] if fmt in ('sub', 'sup'): lFont = Font(color, faceMap[font.face], size=0.8 * font.size * self.fontScale) else: lFont = aggFont tw, th = self.draw.textsize(tblock, lFont) w += tw if fmt == 'sub': subH = max(subH, th) elif fmt == 'sup': supH = max(supH, th) else: h = max(h, th) dblocks.append((tblock, fmt, tw, th)) idx = blockEnd if idx != len(text): # untagged text: tblock = text[idx:] tw, th = self.draw.textsize(tblock, aggFont) w += tw h = max(h, th) dblocks.append((tblock, '', tw, th)) supH *= 0.25 subH *= 0.25 h += supH + subH bw, bh = w * 1.1, h #dPos = pos[0]-bw/2.,pos[1]-bh/2. dPos = [pos[0] - w / 2., pos[1] - h / 2.] if orientation == 'W': dPos = [pos[0] - w, pos[1] - h / 2.] elif orientation == 'E': dPos = [pos[0], pos[1] - h / 2.] else: dPos = [pos[0] - w / 2, pos[1] - h / 2.] bgColor = kwargs.get('bgColor', (1, 1, 1)) bgColor = convertColor(bgColor) self.draw.rectangle((dPos[0], dPos[1], dPos[0] + bw, dPos[1] + bh), None, Brush(bgColor)) if supH: dPos[1] += supH for txt, fmt, tw, th in dblocks: tPos = dPos[:] if fmt == 'sub': tPos[1] += subH elif fmt == 'sup': tPos[1] -= supH if fmt in ('sub', 'sup'): lFont = Font(color, faceMap[font.face], size=0.8 * font.size * self.fontScale) else: lFont = aggFont self.draw.text(tPos, txt, lFont) dPos[0] += tw