{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 포즈 + 캐릭터 → 웹툰풍 그림 (무료 Colab, 폰용)\n",
    "**순서**: 런타임 → 런타임 유형 변경 → **T4 GPU** 선택 → 아래 셀을 위에서부터 ▶ 한 번씩 실행\n",
    "1. 설치 (2~3분) → 2. 모델 불러오기 (처음 한 번, 5분 안팎) → 3. 그림 만들기 (사진 2장 올리고 생성)\n",
    "\n",
    "- 포즈 그림: 마네킹/스케치 그림, 또는 그 포즈를 취한 사진/3D 마네킹 캡처\n",
    "- 캐릭터 그림: 설정화에서 **정면 한 장만 잘라서** 올리는 게 가장 잘 나와요\n"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "#@title 1. 설치\n",
    "!pip -q install -U diffusers transformers accelerate controlnet_aux\n",
    "print(\"설치 완료\")"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "#@title 2. 모델 불러오기 (처음 한 번)\n",
    "import torch\n",
    "from diffusers import StableDiffusionControlNetPipeline, ControlNetModel, UniPCMultistepScheduler\n",
    "\n",
    "BASE = \"Linaqruf/anything-v3.0\" #@param {type:\"string\"}\n",
    "MODE = \"스케치(선화)\" #@param [\"스케치(선화)\",\"포즈추출(openpose)\"]\n",
    "\n",
    "CN = {\"스케치(선화)\":\"lllyasviel/control_v11p_sd15_lineart\",\"포즈추출(openpose)\":\"lllyasviel/control_v11p_sd15_openpose\"}[MODE]\n",
    "cn = ControlNetModel.from_pretrained(CN, torch_dtype=torch.float16)\n",
    "try:\n",
    "    pipe = StableDiffusionControlNetPipeline.from_pretrained(BASE, controlnet=cn, torch_dtype=torch.float16, safety_checker=None)\n",
    "except Exception as e:\n",
    "    print(\"기본 모델을 못 불러와 SD1.5로 대체해요:\", str(e)[:120])\n",
    "    pipe = StableDiffusionControlNetPipeline.from_pretrained(\"stable-diffusion-v1-5/stable-diffusion-v1-5\", controlnet=cn, torch_dtype=torch.float16, safety_checker=None)\n",
    "pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)\n",
    "pipe.load_ip_adapter(\"h94/IP-Adapter\", subfolder=\"models\", weight_name=\"ip-adapter-plus_sd15.bin\")\n",
    "pipe.to(\"cuda\")\n",
    "print(\"준비 완료 (모드:\", MODE, \")\")"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "#@title 3. 그림 만들기\n",
    "PROMPT = \"1girl, masterpiece, best quality, korean webtoon style, manhwa, soft cel shading, colored shadows, detailed eyes, sharp lineart\" #@param {type:\"string\"}\n",
    "NEGATIVE = \"lowres, bad anatomy, bad hands, extra fingers, missing fingers, worst quality, blurry, text, watermark\" #@param {type:\"string\"}\n",
    "CHARACTER_STRENGTH = 0.7 #@param {type:\"slider\", min:0.2, max:1.0, step:0.05}\n",
    "POSE_STRENGTH = 1.0 #@param {type:\"slider\", min:0.3, max:1.5, step:0.05}\n",
    "NUM_IMAGES = 4 #@param {type:\"slider\", min:1, max:8, step:1}\n",
    "STEPS = 28 #@param {type:\"slider\", min:15, max:40, step:1}\n",
    "SEED = -1 #@param {type:\"integer\"}\n",
    "\n",
    "import io, zipfile, numpy as np\n",
    "from PIL import Image, ImageOps\n",
    "from google.colab import files\n",
    "from IPython.display import display\n",
    "\n",
    "def pick(msg):\n",
    "    print(msg); up = files.upload()\n",
    "    return Image.open(io.BytesIO(list(up.values())[0])).convert(\"RGB\")\n",
    "\n",
    "pose = pick(\"1) 포즈 그림을 선택하세요\")\n",
    "char = pick(\"2) 캐릭터 그림(정면 1장)을 선택하세요\")\n",
    "\n",
    "W, H = 512, 768\n",
    "pose = ImageOps.pad(pose, (W, H), color=(255,255,255))\n",
    "if \"openpose\" in CN:\n",
    "    from controlnet_aux import OpenposeDetector\n",
    "    pose = OpenposeDetector.from_pretrained(\"lllyasviel/Annotators\")(pose).resize((W, H))\n",
    "elif np.asarray(pose.convert(\"L\")).mean() > 127:\n",
    "    pose = ImageOps.invert(pose)          # 라인아트 컨트롤은 검은 배경+흰 선\n",
    "s = max(char.size)\n",
    "char = ImageOps.pad(char, (s, s), color=(255,255,255))   # 옆이 잘리지 않게 정사각형으로\n",
    "\n",
    "pipe.set_ip_adapter_scale(CHARACTER_STRENGTH)\n",
    "base = SEED if SEED >= 0 else int(torch.randint(0, 10**6, (1,)))\n",
    "outs = []\n",
    "for i in range(NUM_IMAGES):\n",
    "    g = torch.Generator(\"cuda\").manual_seed(base + i)\n",
    "    im = pipe(prompt=PROMPT, negative_prompt=NEGATIVE, image=pose, ip_adapter_image=char,\n",
    "              num_inference_steps=STEPS, guidance_scale=7, controlnet_conditioning_scale=POSE_STRENGTH,\n",
    "              height=H, width=W, generator=g).images[0]\n",
    "    outs.append(im); display(im)\n",
    "\n",
    "with zipfile.ZipFile(\"result.zip\", \"w\") as z:\n",
    "    for i, im in enumerate(outs):\n",
    "        im.save(f\"draw_{i+1}.png\"); z.write(f\"draw_{i+1}.png\")\n",
    "files.download(\"result.zip\")"
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "provenance": [],
   "gpuType": "T4"
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}