def generate_image(self, date: str, item_shop: dict) -> bool: """ Generate the Item Shop image using the provided Item Shop. Return True if image sucessfully saved. """ if item_shop["featured"] is not None: featured = item_shop["featured"]["entries"] else: featured = [] if item_shop["daily"] is not None: daily = item_shop["daily"]["entries"] else: daily = [] # Determine the max amount of rows required for the current # Item Shop when there are 3 columns for both Featured and Daily. # This allows us to determine the image height. rows = max(ceil(len(featured) / 3), ceil(len(daily) / 3)) shop_image = Image.new("RGBA", (1920, ((545 * rows) + 340))) try: background = ImageUtil().open_image("background.png") background = ImageUtil().resize_ratio(background, shop_image.width, shop_image.height) shop_image.paste( background, ImageUtil().align_center(shop_image.width, background.width)) except FileNotFoundError: log.warning( "Failed to open background.png, defaulting to dark gray") shop_image.paste((18, 18, 18), [0, 0, shop_image.size[0], shop_image.size[1]]) logo = ImageUtil().open_image("logo.png") logo = ImageUtil().resize_ratio(logo, 0, 210) shop_image.paste( logo, ImageUtil().align_center(shop_image.width, logo.width, 20), logo) canvas = ImageDraw.Draw(shop_image) font = ImageUtil().get_font(48) text_width, _ = font.getsize(date) canvas.text( ImageUtil().align_center(shop_image.width, text_width, 255), date, (255, 255, 255), font=font, ) featured_title = Translator().translate("Featured", str='en', dest=self.language).text daily_title = Translator().translate("Daily", str='en', dest=self.language).text canvas.text((20, 255), featured_title, (255, 255, 255), font=font) text_width, _ = font.getsize(daily_title) canvas.text( (shop_image.width - (text_width + 20), 255), daily_title, (255, 255, 255), font=font, ) # Track grid position i = 0 for item in featured: card = self.generate_card(item) if card is not None: shop_image.paste( card, ( (20 + ((i % 3) * (card.width + 5))), (315 + ((i // 3) * (card.height + 5))), ), card, ) i += 1 # Reset grid position i = 0 for item in daily: card = self.generate_card(item) if card is not None: shop_image.paste( card, ( (990 + ((i % 3) * (card.width + 5))), (315 + ((i // 3) * (card.height + 5))), ), card, ) i += 1 try: shop_image.save("itemshop.png") log.info("Generated Item Shop image") return True except Exception as error: log.critical(f"Failed to save Item Shop image, {error}") return False
def generate_card(self, item: dict): """Return the card image for the provided Fortnite Item Shop item.""" try: name = item["items"][0]["name"] rarity = item["items"][0]["rarity"]["value"] category = item["items"][0]["type"]["value"] price = item["finalPrice"] if item["items"][0]["images"]["featured"] is not None: icon = item["items"][0]["images"]["featured"] else: icon = item["items"][0]["images"]["icon"] # Select bundle image and name if item["bundle"] is not None: name = item["bundle"]["name"] icon = item["bundle"]["image"] except Exception as error: log.error(f"Failed to parse item, {error}") return # Should be outdated if rarity == "frozen": blend_color = (148, 223, 255) elif rarity == "lava": blend_color = (234, 141, 35) elif rarity == "legendary": blend_color = (211, 120, 65) elif rarity == "dark": blend_color = (251, 34, 223) elif rarity == "starwars": blend_color = (231, 196, 19) elif rarity == "marvel": blend_color = (197, 51, 52) elif rarity == "dc": blend_color = (84, 117, 199) elif rarity == "icon": blend_color = (54, 183, 183) elif rarity == "shadow": blend_color = (113, 113, 113) elif rarity == "epic": blend_color = (177, 91, 226) elif rarity == "rare": blend_color = (73, 172, 242) elif rarity == "uncommon": blend_color = (96, 170, 58) elif rarity == "common": blend_color = (190, 190, 190) else: blend_color = (255, 255, 255) card = Image.new("RGBA", (300, 545)) try: layer = ImageUtil().open_image(self.style + f"/card_top_{rarity}.png") except FileNotFoundError: log.warning( f"Failed to open card_top_{rarity}.png, defaulted to Common") layer = ImageUtil().open_image(f"{self.style}/card_top_common.png") card.paste(layer) icon = ImageUtil().download_image(icon).convert("RGBA") if category in ["outfit", "emote"]: icon = ImageUtil().resize_ratio(icon, 285, 365) elif category == "wrap": icon = ImageUtil().resize_ratio(icon, 230, 310) else: icon = ImageUtil().resize_ratio(icon, 310, 390) if category in ["outfit", "emote"]: card.paste(icon, ImageUtil().align_center(card.width, icon.width), icon) else: card.paste(icon, ImageUtil().align_center(card.width, icon.width, 15), icon) if len(item["items"]) > 1: # Track grid position i = 0 # Start at position 1 in items array for extra in item["items"][1:]: try: extra_rarity = extra["rarity"]['value'] extra_icon = extra["images"]["smallIcon"] except Exception as error: log.error(f"Failed to parse item {name}, {error}") return try: layer = ImageUtil().open_image( self.style + f"/box_bottom_{extra_rarity}.png") except FileNotFoundError: log.warning( f"Failed to open box_bottom_{extra_rarity}.png, defaulted to Common" ) layer = ImageUtil().open_image( f"{self.style}/box_bottom_common.png") card.paste( layer, ( (card.width - (layer.width + 9)), (9 + ((i // 1) * layer.height)), ), ) extra_icon = ImageUtil().download_image(extra_icon) extra_icon = ImageUtil().resize_ratio(extra_icon, 75, 75) card.paste( extra_icon, ( (card.width - (layer.width + 9)), (9 + ((i // 1) * extra_icon.height)), ), extra_icon, ) try: layer = ImageUtil().open_image( self.style + f"/box_faceplate_{extra_rarity}.png") except FileNotFoundError: log.warning( f"Failed to open box_faceplate_{extra_rarity}.png, defaulted to Common" ) layer = ImageUtil().open_image( f"{self.style}/box_faceplate_common.png") card.paste( layer, ( (card.width - (layer.width + 9)), (9 + ((i // 1) * layer.height)), ), layer, ) i += 1 if self.style == 'old': try: layer = ImageUtil().open_image(self.style + f"/card_faceplate_{rarity}.png") except FileNotFoundError: log.warning( f"Failed to open card_faceplate_{rarity}.png, defaulted to Common" ) layer = ImageUtil().open_image( f"{self.style}/card_faceplate_common.png") card.paste(layer, layer) try: layer = ImageUtil().open_image(self.style + f"/card_bottom_{rarity}.png") except FileNotFoundError: log.warning( f"Failed to open card_bottom_{rarity}.png, defaulted to Common" ) layer = ImageUtil().open_image( f"{self.style}/card_bottom_common.png") card.paste(layer, layer) canvas = ImageDraw.Draw(card) if self.style == 'old': font = ImageUtil().get_font(30) text_width, _ = font.getsize( f"{rarity.capitalize()} {category.capitalize()}") canvas.text( ImageUtil().align_center(card.width, text_width, 385), f"{rarity.capitalize()} {category.capitalize()}", blend_color, font=font, ) vbucks = ImageUtil().open_image("vbucks.png") vbucks = ImageUtil().resize_ratio(vbucks, 25, 25) price = str(f"{price:,}") text_width, _ = font.getsize(price) canvas.text( ImageUtil().align_center(card.width, (text_width - vbucks.width), 495), price, blend_color, font=font, ) card.paste( vbucks, ImageUtil().align_center(card.width, (vbucks.width + (text_width + 5)), 495), vbucks, ) font = ImageUtil().get_font(56) text_width, _ = font.getsize(name) change = 0 if text_width >= 270: # Ensure that the item name does not overflow font, text_width, change = ImageUtil().fit_text(name, 56, 260) canvas.text( ImageUtil().align_center(card.width, text_width, (425 + (change // 2))), name, (255, 255, 255), font=font, ) elif self.style == 'new': font = ImageUtil().get_font(33) vbucks = ImageUtil().open_image("vbucks_card.png") vbucks = ImageUtil().resize_ratio(vbucks, 49, 49) price = str(f"{price:,}") text_width, _ = font.getsize(price) canvas.text( ImageUtil().align_center(card.width, ((text_width + 15) - vbucks.width), 450), price, blend_color, font=font, ) card.paste( vbucks, ImageUtil().align_center(card.width, (vbucks.width + (text_width - 290)), 436), vbucks, ) font = ImageUtil().get_font(56) text_width, _ = font.getsize(name) change = 0 if text_width >= 270: # Ensure that the item name does not overflow font, text_width, change = ImageUtil().fit_text(name, 56, 260) canvas.text( ImageUtil().align_center(card.width, text_width, (380 + (change // 2))), name, (255, 255, 255), font=font, ) return card