{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import requests\n", "import io\n", "from io import BytesIO\n", "from PIL import ImageDraw, ImageFont\n", "from PIL import Image, ImageDraw, ImageFont\n", "import matplotlib.pyplot as plt\n", "import numpy as np" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class MyDraw():\n", " def __init__(self, height, width):\n", " self.height = height\n", " self.width = width\n", " pass\n", "\n", " def save_image(self, filename):\n", " self.image.save(filename)\n", " pass\n", "\n", " def start_draw(self, background_color=None):\n", " if background_color is None:\n", " background_color = self.theme_params['background_color']\n", "\n", " self.image = Image.new(\"RGB\", (self.width, self.height), background_color)\n", " self.draw = ImageDraw.Draw(self.image)\n", " pass\n", "\n", " def add_image(self, obj, position):\n", " image_to_add = obj.get_image() # Obtiene la imagen del objeto pasado como argumento\n", " if image_to_add:\n", " self.image.paste(image_to_add, position)\n", " pass\n", "\n", " def get_draw(self):\n", " return self.draw\n", " \n", " def get_image(self):\n", " return self.image\n", "\n", " def set_params(self,params):\n", " self.prms = params\n", " pass\n", "\n", " def set_theme(self,mode='day'):\n", " if(mode=='day'):\n", " self.start_day_mode()\n", " else:\n", " self.start_night_mode()\n", " pass\n", "\n", " def start_day_mode(self):\n", " self.theme_params = {\n", " 'background_color': 'white',\n", " 'text_color': 'black',\n", " 'poster_line_color': 'black',\n", " }\n", " pass\n", "\n", " def start_night_mode(self):\n", " self.theme_params = {\n", " 'background_color': 'black',\n", " 'text_color': 'white',\n", " 'poster_line_color': 'gray',\n", " }\n", " pass\n", " \n", " def load_barlow(self, font_size=None):\n", " # Ruta a la fuente TTF personalizada\n", " font_path = \"/app/data/Barlow-Medium.ttf\"\n", " # Carga la fuente\n", " if font_size is None:\n", " self.font = ImageFont.truetype(font_path, self.prms['font_size'])\n", " else:\n", " self.font = ImageFont.truetype(font_path, font_size)\n", " pass\n", "\n", " def preview(self):\n", " plt.imshow(self.image)\n", " plt.axis('off')\n", " plt.show()\n", "\n", " def crop_image(self, top_cut, bottom_cut):\n", " width, height = self.image.size\n", " self.image = self.image.crop((0, top_cut, width, height - bottom_cut))\n", " pass\n", "\n", " def resize_image(self, target_width=None, target_height=None):\n", " \"\"\"\n", " Ajusta el tamaño de la imagen mientras mantiene las proporciones.\n", " \n", " Args:\n", " image (PIL.Image.Image): La imagen a redimensionar.\n", " target_width (int, opcional): El ancho objetivo deseado.\n", " target_height (int, opcional): La altura objetivo deseada.\n", " \n", " Returns:\n", " PIL.Image.Image: La imagen redimensionada.\n", " \"\"\"\n", " width, height = self.image.size\n", " aspect_ratio = width / height\n", " \n", " if target_width is None and target_height is None:\n", " raise ValueError(\"Debes proporcionar al menos una de las dimensiones objetivo.\")\n", " \n", " if target_width is not None and target_height is None:\n", " new_width = target_width\n", " new_height = int(target_width / aspect_ratio)\n", " elif target_width is None and target_height is not None:\n", " new_width = int(target_height * aspect_ratio)\n", " new_height = target_height\n", " else:\n", " new_width = target_width\n", " new_height = target_height\n", " \n", " self.image = self.image.resize((new_width, new_height))\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class BusPoster(MyDraw):\n", "\n", " def start_draw(self):\n", " return super().start_draw()\n", "\n", " def set_colors(self):\n", " width_border = self.prms['width_border']\n", " proportion = self.prms['proportion']\n", " fill_color_l = self.prms['letter_background_color']\n", " fill_color_n = self.prms['number_background_color']\n", "\n", "\n", " self.draw.rounded_rectangle(\n", " (0, 0, self.width-width_border, self.height-width_border),\n", " fill=fill_color_l,\n", " outline=self.theme_params['poster_line_color'],\n", " width=width_border,\n", " radius=5)\n", " \n", " self.draw.rounded_rectangle(\n", " (0, 0, proportion*self.width-width_border, self.height-width_border),\n", " fill=fill_color_n,\n", " outline=self.theme_params['poster_line_color'],\n", " width=width_border,\n", " radius=5)\n", " pass\n", "\n", " def set_bus_number(self, bus_number=\"11\"):\n", " text_color = 'black'\n", " width_border = self.prms['width_border']\n", " text_bbox = self.font.getbbox(str(bus_number))\n", " font_width, font_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]\n", " offset_width = np.round((self.prms['proportion']*self.width-width_border)/2) - np.round(font_width/2)\n", " text_position = (offset_width,0)\n", " self.draw.text(\n", " text_position,\n", " bus_number,\n", " fill=text_color,\n", " font=self.font,\n", " # align =\"center\"\n", " )\n", " pass\n", "\n", " def set_bus_letter(self, bus_letter=\"E\"):\n", " proportion = self.prms['proportion']\n", " width_border = self.prms['width_border']\n", " text_color = 'white'\n", " text_bbox = self.font.getbbox(str(bus_letter))\n", " font_width, font_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]\n", " offset_width = np.round((proportion*self.width-width_border)) + 0.75*np.round(font_width/2)\n", " text_position = (offset_width,0)\n", " self.draw.text(\n", " text_position,\n", " bus_letter,\n", " fill=text_color,\n", " font=self.font,\n", " # align =\"center\"\n", " )\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class TimeAnnouncement(MyDraw):\n", "\n", " def start_draw(self):\n", " super().start_draw()\n", " self.border = 1\n", "\n", " def set_background(self):\n", " self.draw.rounded_rectangle(\n", " (0, 0, self.width-0.5*self.border, self.height-0.5*self.border),\n", " fill=\"#dcdcdc\",\n", " outline=\"gray\",\n", " width=self.border,\n", " radius=1)\n", " pass\n", "\n", " def set_base_text(self):\n", " text = \"Tiempo aprox\"\n", " text_color = self.theme_params['text_color']\n", " self.load_barlow(font_size=11)\n", " text_bbox = self.font.getbbox(text)\n", " font_width, font_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]\n", " # print(font_width, font_height)\n", " offset_width = (np.round((self.width-self.border)) - np.round(font_width))/2\n", " text_position = (offset_width,5)\n", " # text_position = (0, 0)\n", " self.draw.text(\n", " text_position,\n", " text,\n", " fill=text_color,\n", " font=self.font,\n", " align =\"center\"\n", " )\n", " pass\n", "\n", " def set_min_max_text(self, min_time, max_time):\n", "\n", " text = \"Tiempo aprox\"\n", " text_color = self.theme_params['text_color']\n", " self.load_barlow(font_size=11)\n", " text_bbox = self.font.getbbox(text)\n", " base_font_width, base_font_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]\n", "\n", " if (int(max_time) <= 1):\n", " text = f\"< 1 min\"\n", " else:\n", " text = f'{min_time} a {max_time} min'\n", " \n", " self.load_barlow(font_size=18)\n", " text_bbox = self.font.getbbox(text)\n", " font_width, font_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]\n", " # print(font_width, font_height)\n", " offset_width = (np.round((self.width-self.border)) - np.round(font_width))/2\n", " offset_height = (np.round((self.height-self.border)) - np.round(base_font_height))/2\n", " text_position = (offset_width,5+offset_height)\n", " # text_position = (0, 0)\n", " self.draw.text(\n", " text_position,\n", " text,\n", " fill=text_color,\n", " font=self.font,\n", " align =\"center\"\n", " )" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class DistanceAnnouncement(MyDraw):\n", "\n", " def start_draw(self):\n", " super().start_draw()\n", " self.border = 1\n", "\n", " def set_background(self):\n", " self.draw.rounded_rectangle(\n", " (0, 0, self.width-0.5*self.border, self.height-0.5*self.border),\n", " fill=\"#dcdcdc\",\n", " outline=\"gray\",\n", " width=self.border,\n", " radius=1)\n", " pass\n", "\n", " def set_base_text(self):\n", " text = \"Distancia\"\n", " text_color = self.theme_params['text_color']\n", " self.load_barlow(font_size=11)\n", " text_bbox = self.font.getbbox(text)\n", " font_width, font_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]\n", " # print(font_width, font_height)\n", " offset_width = (np.round((self.width-self.border)) - np.round(font_width))/2\n", " text_position = (offset_width,5)\n", " # text_position = (0, 0)\n", " self.draw.text(\n", " text_position,\n", " text,\n", " fill=text_color,\n", " font=self.font,\n", " align =\"center\"\n", " )\n", " pass\n", "\n", " def set_distance_text(self, distance):\n", "\n", " text = \"Distancia\"\n", " text_color = self.theme_params['text_color']\n", " self.load_barlow(font_size=11)\n", " text_bbox = self.font.getbbox(text)\n", " base_font_width, base_font_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]\n", "\n", "\n", " text = f'{distance} km'\n", " self.load_barlow(font_size=18)\n", " text_bbox = self.font.getbbox(text)\n", " font_width, font_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]\n", " # print(font_width, font_height)\n", " offset_width = (np.round((self.width-self.border)) - np.round(font_width))/2\n", " offset_height = (np.round((self.height-self.border)) - np.round(base_font_height))/2\n", " text_position = (offset_width,5+offset_height)\n", " # text_position = (0, 0)\n", " self.draw.text(\n", " text_position,\n", " text,\n", " fill=text_color,\n", " font=self.font,\n", " align =\"center\"\n", " )" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\n", "class BusPlate():\n", " def __init__(self, url=None) -> None:\n", " if url is None:\n", " self.url = \"https://matriculasdelmundo.com/gRCH1.php\"\n", " pass\n", "\n", " def set_url(self, url):\n", " self.url = url\n", " pass\n", "\n", " def get_image(self):\n", " if hasattr(self, 'image'):\n", " return self.image\n", " else:\n", " print(\"Error: No se ha generado ninguna imagen aún.\")\n", " return None\n", "\n", " def request_bus_plate(self, bus_plate=None):\n", " if bus_plate is None:\n", " self.bus_plate = \"AABB11\"\n", " else:\n", " self.bus_plate = bus_plate\n", "\n", " params = {\n", " \"textRCH1\": self.bus_plate[0:2],\n", " \"textRCH1A\": self.bus_plate[2:4],\n", " \"textRCH1B\": self.bus_plate[4:],\n", " \"textRCH1C\": \"\"\n", " }\n", "\n", " self.response = requests.get(self.url, params=params)\n", " pass\n", "\n", " def save_bus_plate_image(self):\n", " if self.response.status_code == 200:\n", " filename = f\"/app/data/output/plate_{self.bus_plate}.png\" \n", " with open(filename, \"wb\") as f:\n", " f.write(self.response.content)\n", " print(f\"Imagen generada guardada como '{filename}'\")\n", " else:\n", " print(\"Error al guardar la imagen generada\")\n", " pass\n", "\n", " def generate_image(self):\n", " image_bytes = io.BytesIO(self.response.content)\n", " self.image = Image.open(image_bytes)\n", " self.image = self.image.convert(\"RGBA\") # Convertir a formato RGBA\n", " pass\n", "\n", " def resize_image(self, new_size):\n", " proportion = np.min([self.image.size[0]/new_size[0], self.image.size[1]/new_size[1]])\n", " nx, ny = int(np.round(image.size[0]/proportion)), int(np.round(image.size[1]/proportion))\n", " self.image = self.image.resize((nx, ny))\n", " pass\n", "\n", " def preview(self):\n", " plt.imshow(self.image)\n", " plt.axis('off')\n", " plt.show()\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class BusImage(MyDraw):\n", " def __init__(self):\n", " pass\n", " \n", " def load_image_from_url(self, url=None):\n", " if(url is None):\n", " # URL de la imagen en línea\n", " url = \"https://img.freepik.com/iconos-gratis/autobus_318-574563.jpg\"\n", "\n", " # Descarga la imagen desde la URL\n", " response = requests.get(url)\n", " image_data = response.content\n", " # Crea un objeto Image desde los datos descargados\n", " self.image = Image.open(BytesIO(image_data))\n", " # Crear una imagen en blanco del mismo tamaño que loaded_image con fondo blanco\n", " background = Image.new(\"RGB\", self.image.size, self.theme_params['background_color'])\n", "\n", " # Pega la loaded_image en la imagen en blanco\n", " background.paste(self.image, (0, 0), self.image)\n", " \n", " self.image = background\n", "\n", " # Calcula la posición para agregar la imagen cargada\n", " image_position = (0, 0) # Cambia esto según tu diseño\n", "\n", " # Agrega la imagen a la imagen creada\n", " self.image.paste(background, image_position)\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "theme = 'night'\n", "\n", "panel_height, panel_width = 40, 80\n", "n_panels = 3\n", "full_panel = MyDraw(n_panels*panel_height, n_panels*panel_width)\n", "full_panel.set_theme(theme)\n", "full_panel.start_draw()\n", "# full_panel.preview()\n", "\n", "bp = BusPlate()\n", "plate = \"WXYZ88\"\n", "bp.request_bus_plate(bus_plate=plate)\n", "bp.generate_image()\n", "bp.resize_image((40,80))\n", "\n", "dist_anmc = DistanceAnnouncement(50, 80)\n", "dist_anmc.set_theme(theme)\n", "dist_anmc.start_draw()\n", "# dist_anmc.set_background()\n", "dist_anmc.set_base_text()\n", "dist_anmc.set_distance_text(distance=5)\n", "\n", "time_anmc = TimeAnnouncement(50, 80)\n", "time_anmc.set_theme(theme)\n", "time_anmc.start_draw()\n", "# time_anmc.set_background()\n", "time_anmc.set_base_text()\n", "time_anmc.set_min_max_text(min_time=2, max_time=3)\n", "\n", "poster = BusPoster(30, 60)\n", "poster.set_theme(theme)\n", "poster.start_draw()\n", "\n", "poster_params = {\n", " 'proportion': 0.6,\n", " 'width_border': 1,\n", " 'font_size': 25,\n", " 'number_background_color': 'yellow',\n", " 'letter_background_color': 'green',\n", "}\n", "\n", "poster.set_params(poster_params)\n", "poster.load_barlow()\n", "poster.set_colors()\n", "poster.set_bus_number(bus_number=\"20\")\n", "poster.set_bus_letter(bus_letter=\"L\")\n", "\n", "bm = BusImage()\n", "bm.set_theme(theme)\n", "bm.load_image_from_url()\n", "bm.crop_image(top_cut=165, bottom_cut=165)\n", "bm.resize_image(target_width=80)\n", "\n", "full_panel.add_image(bp, (120, 80))\n", "full_panel.add_image(dist_anmc, (90, 10))\n", "full_panel.add_image(time_anmc, (160, 10))\n", "full_panel.add_image(poster, (40, 80))\n", "full_panel.add_image(bm, (5,20))\n", "full_panel.get_image()\n", "full_panel.save_image('/app/data/output.png')" ] } ], "metadata": { "language_info": { "name": "python" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 }