diff --git a/deno.json b/deno.json index f16c9aa..2a9d4f8 100644 --- a/deno.json +++ b/deno.json @@ -2,8 +2,8 @@ "lock": false, "tasks": { "check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx", - "start": "deno run -A --watch=static/,routes/ dev.ts", - "build": "deno run -A dev.ts build", + "start": "deno run -A --unstable --watch=static/,routes/ dev.ts ", + "build": "deno run -A --unstable dev.ts build", "preview": "deno run -A main.ts", "update": "deno run -A -r https://fresh.deno.dev/update ." }, diff --git a/fresh.gen.ts b/fresh.gen.ts index 3d51ba6..491380d 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -5,9 +5,8 @@ import * as $0 from "./routes/_404.tsx"; import * as $1 from "./routes/_app.tsx"; import * as $2 from "./routes/api/image.ts"; -import * as $3 from "./routes/api/joke.ts"; -import * as $4 from "./routes/greet/[name].tsx"; -import * as $5 from "./routes/index.tsx"; +import * as $3 from "./routes/greet/[name].tsx"; +import * as $4 from "./routes/index.tsx"; import * as $$0 from "./islands/ImageForm.tsx"; import * as $$1 from "./islands/Top.tsx"; import * as $$2 from "./islands/subTitle.tsx"; @@ -17,9 +16,8 @@ const manifest = { "./routes/_404.tsx": $0, "./routes/_app.tsx": $1, "./routes/api/image.ts": $2, - "./routes/api/joke.ts": $3, - "./routes/greet/[name].tsx": $4, - "./routes/index.tsx": $5, + "./routes/greet/[name].tsx": $3, + "./routes/index.tsx": $4, }, islands: { "./islands/ImageForm.tsx": $$0, diff --git a/islands/ImageForm.tsx b/islands/ImageForm.tsx index 9e429f1..2fce365 100644 --- a/islands/ImageForm.tsx +++ b/islands/ImageForm.tsx @@ -1,6 +1,4 @@ import { useSignal } from "https://esm.sh/*@preact/signals@1.2.1"; -import { encode } from "https://deno.land/std@0.193.0/encoding/hex.ts"; -import { decode } from "$std/encoding/base64.ts"; export default function ImageForm() { const isActiveFileUpLoderDisable = useSignal(false); diff --git a/python/__pycache__/extract_png.cpython-311.pyc b/python/__pycache__/extract_png.cpython-311.pyc deleted file mode 100644 index c5b8a6e..0000000 Binary files a/python/__pycache__/extract_png.cpython-311.pyc and /dev/null differ diff --git a/python/extract_png.py b/python/extract_png.py deleted file mode 100644 index c6d025c..0000000 --- a/python/extract_png.py +++ /dev/null @@ -1,45 +0,0 @@ -from pathlib import Path -from PIL import Image, ImageSequence -import os - - -# 現在の状況を標準出力に表示するかどうか -DEBUG_MODE = True - - -def extract_png( - image_path: str, destination: str = os.getcwd() + "/python" + "/splitted" -): - frames = get_frames(image_path) - write_frames(frames, image_path, destination) - - -def get_frames(path): - """パスで指定されたファイルのフレーム一覧を取得する""" - im = Image.open(path) - return (frame.copy() for frame in ImageSequence.Iterator(im)) - - -def write_frames(frames, name_original, destination): - """フレームを別個の画像ファイルとして保存する""" - path = Path(name_original) - - stem = path.stem - extension = ".png" - - # 出力先のディレクトリが存在しなければ作成しておく - dir_dest = Path(destination) - if not dir_dest.is_dir(): - dir_dest.mkdir(0o700) - if DEBUG_MODE: - print('Destionation directory is created: "{}".'.format(destination)) - - for i, f in enumerate(frames): - name = "{}/{}-{:02}{}".format(destination, stem, i + 1, extension) - f.save(name) - if DEBUG_MODE: - print('A frame is saved as "{}".'.format(name)) - - -if __name__ == "__main__": - main("ずんだもん.GIF") diff --git a/python/gif_ascii_arter.py b/python/gif_ascii_arter.py deleted file mode 100644 index 502ce3c..0000000 --- a/python/gif_ascii_arter.py +++ /dev/null @@ -1,149 +0,0 @@ -from PIL import Image, ImageDraw, ImageFont -import numpy as np -from extract_png import extract_png -import os.path -import os -import glob -import pathlib -import pprint -import shutil -import sys -import base64 -import cv2 - -FONT_SIZE = 10 -GRID_SIZE = (1, 1) -FONT_COLOR_SET = ("#ffffff", "#000000") -FONT_PATH = "./font/UbuntuMono-B.ttf" - -FONT_COLOR, FONT_BACKGROUND_COLOR = FONT_COLOR_SET -COLUMNS, ROWS = GRID_SIZE - -ABSOLUTE_PATH = os.getcwd() + "/python" - - -def convert_to_png(gif_name: str): - extract_png(gif_name) - IMAGE_NAMES = glob.glob(ABSOLUTE_PATH + "/splitted/*.png") - - return sorted(IMAGE_NAMES) - - -def image2ascii(input_image): - original_width = int(input_image.size[0]) - original_height = int(input_image.size[1]) - - width = original_width * COLUMNS - height = original_height * ROWS - - character, line = "", [] - font = ImageFont.truetype(FONT_PATH, FONT_SIZE, encoding="utf-8") - input_pix = input_image.load() - output_image = Image.new("RGBA", (width, height), FONT_BACKGROUND_COLOR) - draw = ImageDraw.Draw(output_image) - - font_width = int(font.getlength("#")) - font_height = int(FONT_SIZE) - - margin_width = width % font_width - margin_height = height % font_height - - offset_x = int(round(margin_width / 2)) - offset_y = int(round(margin_height / 2)) - - for row in range(ROWS): - for y in range(offset_y, int(original_height) - offset_y, font_height): - line = [] - for column in range(COLUMNS): - for x in range(offset_x, int(original_width) - offset_x, font_width): - pixel = input_pix[x - offset_x, y - offset_y] - # ピクセルが整数(つまり、グレースケール)の場合 - if isinstance(pixel, int): - gray = pixel - # ピクセルがタプル(つまり、カラー)の場合 - else: - r, g, b, _ = pixel - gray = r * 0.2126 + g * 0.7152 + b * 0.0722 - "polikeiji" - if gray > 130: - character = " " - elif gray > 100: - character = "i" - elif gray > 90: - character = "l" - elif gray > 80: - character = "j" - elif gray > 60: - character = "o" - elif gray > 50: - character = "e" - elif gray > 40: - character = "p" - elif gray > 30: - character = "k" - else: - character = "#" - line.append(character) - draw.text( - (offset_x, y + row * original_height), - "".join(line), - font=font, - fill=FONT_COLOR, - ) - return output_image - - -def make_ascii_gif(): - files = sorted(glob.glob(ABSOLUTE_PATH + "/ascii_arts/*.png")) - - images = list(map(lambda file: Image.open(file), files)) - images[0].save( - ABSOLUTE_PATH + "/anime.gif", - save_all=True, - append_images=images[1:], - optimize=False, - duration=100, - loop=0, - ) - - -def delete_folder(): - shutil.rmtree(ABSOLUTE_PATH + "/splitted") - shutil.rmtree(ABSOLUTE_PATH + "/ascii_arts") - - -def announce(): - return sys.stdout.write("converting gif is successed!!") - - -if __name__ == "__main__": - image_names = convert_to_png(ABSOLUTE_PATH + "/posted-image.gif") - directory_name = os.path.dirname(image_names[0]) - sys.stdout.write(directory_name) - if directory_name != "": - directory_name = directory_name + "/" - ascii_image_directory = ABSOLUTE_PATH + "/ascii_arts" - - for image_name in image_names: - print("Input image: {0}".format(image_name)) - - with Image.open(image_name) as input_image: - output_image = image2ascii(input_image) - - file_name, extension = os.path.splitext(os.path.basename(image_name)) - - ascii_image_name = "{0}/ascii_{1}_{2}x{3}_{4}{5}".format( - ascii_image_directory, FONT_SIZE, ROWS, COLUMNS, file_name, extension - ) - - ascii_image_directory = os.path.dirname(ascii_image_name) - if not os.path.exists(ascii_image_directory): - os.makedirs(ascii_image_directory) - - output_image.save(ascii_image_name) - - print("Output image: {0}".format(ascii_image_name)) - - make_ascii_gif() - delete_folder() - announce() diff --git a/routes/api/image.ts b/routes/api/image.ts index 3dcc048..af6a69d 100644 --- a/routes/api/image.ts +++ b/routes/api/image.ts @@ -1,18 +1,209 @@ import { HandlerContext } from "$fresh/server.ts"; import { decode, encode } from "$std/encoding/base64.ts"; +import { python } from "https://deno.land/x/python/mod.ts"; +const createAsciiArtFile = ` +from PIL import Image, ImageDraw, ImageFont +import os.path +import os +import glob +import pprint +import shutil +import sys + +from pathlib import Path +from PIL import Image, ImageSequence + + +class ExtractPng: + __DEBUG_MODE = True + + def extract_png( + self, image_path: str, destination: str = os.getcwd() + "/splitted" + ): + frames = self.__get_frames(image_path) + self.__write_frames(frames, image_path, destination) + + def __get_frames(self, path): + """パスで指定されたファイルのフレーム一覧を取得する""" + im = Image.open(path) + return (frame.copy() for frame in ImageSequence.Iterator(im)) + + def __write_frames(self, frames, name_original, destination): + """フレームを別個の画像ファイルとして保存する""" + path = Path(name_original) + + stem = path.stem + extension = ".png" + + # 出力先のディレクトリが存在しなければ作成しておく + dir_dest = Path(destination) + if not dir_dest.is_dir(): + dir_dest.mkdir(0o700) + if self.__DEBUG_MODE: + print('Destionation directory is created: "{}".'.format(destination)) + + for i, f in enumerate(frames): + name = "{}/{}-{:02}{}".format(destination, stem, i + 1, extension) + f.save(name) + if self.__DEBUG_MODE: + print('A frame is saved as "{}".'.format(name)) + + +class CreateAsciiArt: + def __init__( + self, + FONT_SIZE=10, + GRID_SIZE=(1, 1), + FONT_COLOR_SET=("#ffffff", "#000000"), + ABSOLUTE_PATH=os.getcwd(), + ) -> None: + self.__FONT_SIZE = FONT_SIZE + self.__FONT_PATH = "./font/UbuntuMono-B.ttf" + + self.__FONT_COLOR, self.__FONT_BACKGROUND_COLOR = FONT_COLOR_SET + self.__COLUMNS, self.__ROWS = GRID_SIZE + + self.__ABSOLUTE_PATH = ABSOLUTE_PATH + + def __convert_to_png(self, gif_name: str): + ExtractPng().extract_png(gif_name) + image_names = glob.glob(self.__ABSOLUTE_PATH + "/splitted/*.png") + + return sorted(image_names) + + def __image2ascii(self, input_image): + original_width = int(input_image.size[0]) + original_height = int(input_image.size[1]) + + width = original_width * self.__COLUMNS + height = original_height * self.__ROWS + + character, line = "", [] + font = ImageFont.truetype(self.__FONT_PATH, self.__FONT_SIZE, encoding="utf-8") + input_pix = input_image.load() + output_image = Image.new("RGBA", (width, height), self.__FONT_BACKGROUND_COLOR) + draw = ImageDraw.Draw(output_image) + + font_width = int(font.getlength("#")) + font_height = int(self.__FONT_SIZE) + + margin_width = width % font_width + margin_height = height % font_height + + offset_x = int(round(margin_width / 2)) + offset_y = int(round(margin_height / 2)) + + for row in range(self.__ROWS): + for y in range(offset_y, int(original_height) - offset_y, font_height): + line = [] + for column in range(self.__COLUMNS): + for x in range( + offset_x, int(original_width) - offset_x, font_width + ): + pixel = input_pix[x - offset_x, y - offset_y] + # ピクセルが整数(つまり、グレースケール)の場合 + if isinstance(pixel, int): + gray = pixel + # ピクセルがタプル(つまり、カラー)の場合 + else: + r, g, b, _ = pixel + gray = r * 0.2126 + g * 0.7152 + b * 0.0722 + "polikeiji" + if gray > 130: + character = " " + elif gray > 100: + character = "i" + elif gray > 90: + character = "l" + elif gray > 80: + character = "j" + elif gray > 60: + character = "o" + elif gray > 50: + character = "e" + elif gray > 40: + character = "p" + elif gray > 30: + character = "k" + else: + character = "#" + line.append(character) + draw.text( + (offset_x, y + row * original_height), + "".join(line), + font=font, + fill=self.__FONT_COLOR, + ) + return output_image + + def __make_ascii_gif(self): + files = sorted(glob.glob(self.__ABSOLUTE_PATH + "/ascii_arts/*.png")) + + images = list(map(lambda file: Image.open(file), files)) + images[0].save( + self.__ABSOLUTE_PATH + "/anime.gif", + save_all=True, + append_images=images[1:], + optimize=False, + duration=100, + loop=0, + ) + + def __delete_folder(self): + shutil.rmtree(self.__ABSOLUTE_PATH + "/splitted") + shutil.rmtree(self.__ABSOLUTE_PATH + "/ascii_arts") + + def __announce(self): + return sys.stdout.write("converting gif is successed!!") + + def create_ascii_art(self): + image_names = self.__convert_to_png(self.__ABSOLUTE_PATH + "/posted-image.gif") + directory_name = os.path.dirname(image_names[0]) + sys.stdout.write(directory_name) + if directory_name != "": + directory_name = directory_name + "/" + ascii_image_directory = self.__ABSOLUTE_PATH + "/ascii_arts" + + for image_name in image_names: + print("Input image: {0}".format(image_name)) + + with Image.open(image_name) as input_image: + output_image = self.__image2ascii(input_image) + + file_name, extension = os.path.splitext(os.path.basename(image_name)) + + ascii_image_name = "{0}/ascii_{1}_{2}x{3}_{4}{5}".format( + ascii_image_directory, + self.__FONT_SIZE, + self.__ROWS, + self.__COLUMNS, + file_name, + extension, + ) + + ascii_image_directory = os.path.dirname(ascii_image_name) + if not os.path.exists(ascii_image_directory): + os.makedirs(ascii_image_directory) + + output_image.save(ascii_image_name) + + print("Output image: {0}".format(ascii_image_name)) + + self.__make_ascii_gif() + self.__delete_folder() + self.__announce() + + +`; async function runPython(): Promise { - const command = await new Deno.Command("python3", { - args: ["python/gif_ascii_arter.py"], - }); - const { code, stdout, stderr } = await command.output(); - if (code === 0) { - console.info(new TextDecoder().decode(stdout)); - } else { - return new TextDecoder().decode(stderr); - } + const createAsciiArt = await python.runModule( + createAsciiArtFile, + "CreateAsciiArt", + ); + createAsciiArt.CreateAsciiArt().create_ascii_art(); - const getAsciiArt = await Deno.readFile("./python/anime.gif"); + const getAsciiArt = await Deno.readFile("./anime.gif"); return encode(getAsciiArt); } export const handler = async ( @@ -27,11 +218,11 @@ export const handler = async ( ); const decodeArrayBuffer: Uint8Array = decode(imagestring); - await Deno.writeFile("./python/posted-image.gif", decodeArrayBuffer); + await Deno.writeFile("./posted-image.gif", decodeArrayBuffer); const output = await runPython(); try { - await Deno.remove("./python/posted-image.gif"); - await Deno.remove("./python/anime.gif"); + await Deno.remove("./posted-image.gif"); + await Deno.remove("./anime.gif"); } catch (e) { return new Response("There are any error", e); }