diff --git a/Stable_Diffusion.ipynb b/Stable_Diffusion.ipynb
new file mode 100644
index 00000000..ca086bd1
--- /dev/null
+++ b/Stable_Diffusion.ipynb
@@ -0,0 +1,343 @@
+{
+ "nbformat": 4,
+ "nbformat_minor": 0,
+ "metadata": {
+ "colab": {
+ "provenance": [],
+ "gpuType": "T4",
+ "private_outputs": true,
+ "authorship_tag": "ABX9TyOiUWRZBuOHzRrD41UDa1vL",
+ "include_colab_link": true
+ },
+ "kernelspec": {
+ "name": "python3",
+ "display_name": "Python 3"
+ },
+ "language_info": {
+ "name": "python"
+ },
+ "accelerator": "GPU"
+ },
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "view-in-github",
+ "colab_type": "text"
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [],
+ "metadata": {
+ "id": "3he-zWjkTirt"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "aSGhPjR3Er-N"
+ },
+ "outputs": [],
+ "source": [
+ "#@title 👇 Installing dependencies { display-mode: \"form\" }\n",
+ "#@markdown ---\n",
+ "#@markdown Make sure to select **GPU** as the runtime type:
\n",
+ "#@markdown *Runtime->Change Runtime Type->Under Hardware accelerator, select GPU*\n",
+ "#@markdown\n",
+ "#@markdown ---\n",
+ "\n",
+ "!pip -q install torch torchvision tor diffusers transformers accelerate scipy safetensors xformers mediapy ipywidgets==7.7.1\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "#@title 👇 Selecting Model { form-width: \"20%\", display-mode: \"form\" }\n",
+ "#@markdown ---\n",
+ "#@markdown - **Select Model** - A list of Stable Diffusion models to choose from.\n",
+ "#@markdown - **Select Sampler** - A list of schedulers to choose from. Default is EulerAncestralScheduler.\n",
+ "#@markdown - **Safety Checker** - Enable/Disable uncensored content\n",
+ "#@markdown\n",
+ "#@markdown ---\n",
+ "\n",
+ "from diffusers import StableDiffusionPipeline, EulerAncestralDiscreteScheduler\n",
+ "from diffusers.models import AutoencoderKL\n",
+ "import torch\n",
+ "import ipywidgets as widgets\n",
+ "import importlib\n",
+ "from PIL import Image\n",
+ "\n",
+ "#Enable third party widget support\n",
+ "from google.colab import output\n",
+ "output.enable_custom_widget_manager()\n",
+ "\n",
+ "#Pipe\n",
+ "pipe = None\n",
+ "\n",
+ "#Models\n",
+ "select_model = widgets.Dropdown(\n",
+ " options=[\n",
+ " (\"Dreamlike Photoreal 2.0\" , \"stabilityai/stable-diffusion-xl-base-1.0\")\n",
+ " ],\n",
+ " description=\"Select Model:\"\n",
+ ")\n",
+ "\n",
+ "#Schedulers\n",
+ "select_sampler = widgets.Dropdown(\n",
+ " options=[\n",
+ " \"EulerAncestralDiscreteScheduler\",\n",
+ " \"EulerDiscreteScheduler\",\n",
+ " \"UniPCMultistepScheduler\",\n",
+ " \"DDIMScheduler\"\n",
+ " ],\n",
+ " description=\"Select Schedular:\"\n",
+ ")\n",
+ "select_sampler.style.description_width = \"auto\"\n",
+ "\n",
+ "#Safety Checker\n",
+ "safety_check = widgets.Checkbox(\n",
+ " value=False,\n",
+ " description=\"Enable Safety Check\",\n",
+ " layout=widgets.Layout(margin=\"0px 0px 0px -85px\")\n",
+ ")\n",
+ "\n",
+ "#Output\n",
+ "out = widgets.Output()\n",
+ "\n",
+ "#Apply Settings\n",
+ "apply_btn = widgets.Button(\n",
+ " description=\"Apply\",\n",
+ " button_style=\"info\"\n",
+ ")\n",
+ "\n",
+ "\n",
+ "#Get scheduler\n",
+ "def get_scheduler(name):\n",
+ "\n",
+ " match name:\n",
+ "\n",
+ " case \"EulerAncestralDiscreteScheduler\":\n",
+ " return EulerAncestralDiscreteScheduler.from_pretrained(select_model.value, subfolder=\"scheduler\")\n",
+ "\n",
+ "#Run pipeline\n",
+ "def pipeline(p):\n",
+ "\n",
+ " global pipe\n",
+ "\n",
+ " out.clear_output()\n",
+ " apply_btn.disabled = True\n",
+ "\n",
+ " with out:\n",
+ "\n",
+ " print(\"Running, please wait...\")\n",
+ "\n",
+ " pipe = StableDiffusionPipeline.from_pretrained(\n",
+ " select_model.value,\n",
+ " scheduler=get_scheduler(select_sampler.value),\n",
+ " torch_dtype=torch.float16,\n",
+ " vae=AutoencoderKL.from_pretrained(\"stabilityai/sd-vae-ft-mse\", torch_dtype=torch.float16).to(\"cuda\")\n",
+ " ).to(\"cuda\")\n",
+ "\n",
+ " pipe.safety_checker = None\n",
+ "\n",
+ " pipe.enable_xformers_memory_efficient_attention()\n",
+ "\n",
+ " print(\"Finished!\")\n",
+ "\n",
+ " apply_btn.disabled = False\n",
+ "\n",
+ "\n",
+ "#Display\n",
+ "apply_btn.on_click(pipeline)\n",
+ "\n",
+ "widgets.VBox(\n",
+ " [\n",
+ " widgets.HTML(value=\"
Configure Pipeline
\"),\n",
+ " select_model, select_sampler, safety_check, apply_btn, out\n",
+ " ]\n",
+ ")\n",
+ "\n",
+ "#call pipeline method from here\n",
+ "pipeline(None)"
+ ],
+ "metadata": {
+ "id": "b_0UKcp3E9no"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "%env PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True"
+ ],
+ "metadata": {
+ "id": "ef0CwmfkmKOg"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "#@title 👇 Generating Images { form-width: \"20%\", display-mode: \"form\" }\n",
+ "#@markdown ---\n",
+ "#@markdown - **Prompt** - Description of the image\n",
+ "#@markdown - **Negative Prompt** - Things you don't want to see or ignore in the image\n",
+ "#@markdown - **Steps** - Number of denoising steps. Higher steps may lead to better results but takes longer time to generate the image. Default is `30`.\n",
+ "#@markdown - **CFG** - Guidance scale ranging from `0` to `20`. Lower values allow the AI to be more creative and less strict at following the prompt. Default is `7.5`.\n",
+ "#@markdown - **Seed** - A random value that controls image generation. The same seed and prompt produce the same images. Set `-1` for using random seed values.\n",
+ "#@markdown ---\n",
+ "import ipywidgets as widgets, mediapy, random\n",
+ "import IPython.display\n",
+ "\n",
+ "\n",
+ "#PARAMETER WIDGETS\n",
+ "width = \"300px\"\n",
+ "\n",
+ "prompt = widgets.Textarea(\n",
+ " value=\"(photorealistic (HDR):2), (gorgeous:1.1), (only one:1.4) \",\n",
+ " placeholder=\"Enter prompt\",\n",
+ " #description=\"Prompt:\",\n",
+ " rows=5,\n",
+ " layout=widgets.Layout(width=\"600px\")\n",
+ ")\n",
+ "\n",
+ "camera = widgets.Dropdown(\n",
+ " options=[('leica summicron 35mm f2.0', ' leica summicron 35mm f2.0'), ('sony a7ii', ' sony a7ii'), ('kodak potra', ' kodak potra')],\n",
+ " value=' leica summicron 35mm f2.0',\n",
+ " description=\"Camera:\",\n",
+ " layout=widgets.Layout(width=width)\n",
+ ")\n",
+ "\n",
+ "lighting = widgets.Dropdown(\n",
+ " options=[('romantic lighting', ' romantic lighting,'), ('dramatic lighting', ' dramatic lighting,')],\n",
+ " value=' dramatic lighting,',\n",
+ " description=\"Lighting:\",\n",
+ " layout=widgets.Layout(width=width)\n",
+ ")\n",
+ "\n",
+ "neg_prompt = widgets.Textarea(\n",
+ " value=\"disfigured, bad lips, ugly lips, bad nose, ugly nose, bad face, ugly face, bad mouth, ugly mouth, beard, bad teeth, ugly teeth, ((grayscale)), cross-eyed, uneven eyes, bad ears, ugly ears, deformed, malformed limbs, bad anatomy, bad hands, three hands, three legs, bad arms, missing legs, missing arms, poorly drawn face, bad face, fused face, cloned face, worst face, three crus, extra crus, fused crus, worst feet, three feet, fused feet, fused thigh, three thigh, fused thigh, extra thigh, worst thigh, missing fingers, extra fingers, ugly fingers, long fingers, horn, extra eyes, huge eyes, 2girl, amputation, disconnected limbs, cartoon, cg, 3d, anime, unreal, animate, masculine, fat, chubby, (tamil:1.7), (red dot:2), (red mark:1.9), (pottu:1.8), (bindi:1.6), paintings, sketches, lowres, ((monochrome)), heavy make-up, (worst quality:1.6), (low quality:1.5), body hair, jewellary, topless, multiple people, blurry, duplicate bodies\",\n",
+ " placeholder=\"Enter negative prompt\",\n",
+ " #description=\"Negative Prompt:\",\n",
+ " rows=5,\n",
+ " layout=widgets.Layout(width=\"600px\")\n",
+ ")\n",
+ "\n",
+ "num_images = widgets.IntText(\n",
+ " value=4,\n",
+ " description=\"Images:\",\n",
+ " layout=widgets.Layout(width=width),\n",
+ ")\n",
+ "\n",
+ "steps = widgets.IntText(\n",
+ " value=35,\n",
+ " description=\"Steps:\",\n",
+ " layout=widgets.Layout(width=width)\n",
+ ")\n",
+ "\n",
+ "CFG = widgets.FloatText(\n",
+ " value=7,\n",
+ " description=\"CFG:\",\n",
+ " layout=widgets.Layout(width=width)\n",
+ ")\n",
+ "\n",
+ "img_height = widgets.Dropdown(\n",
+ " options=[('512px', 512), ('768px', 768), ('720px', 720)],\n",
+ " value=720,\n",
+ " description=\"Height:\",\n",
+ " layout=widgets.Layout(width=width)\n",
+ ")\n",
+ "\n",
+ "img_width = widgets.Dropdown(\n",
+ " options=[('512px', 512), ('768px', 768), ('1280px', 1280)],\n",
+ " value=1280,\n",
+ " description=\"Width:\",\n",
+ " layout=widgets.Layout(width=width)\n",
+ ")\n",
+ "\n",
+ "random_seed = widgets.IntText(\n",
+ " value=-1,\n",
+ " description=\"Seed:\",\n",
+ " layout=widgets.Layout(width=width),\n",
+ " disabled=False\n",
+ ")\n",
+ "\n",
+ "generate = widgets.Button(\n",
+ " description=\"Generate\",\n",
+ " disabled=False,\n",
+ " button_style=\"primary\"\n",
+ ")\n",
+ "\n",
+ "display_imgs = widgets.Output()\n",
+ "\n",
+ "\n",
+ "#RUN\n",
+ "def generate_img(i):\n",
+ "\n",
+ " #Clear output\n",
+ " display_imgs.clear_output()\n",
+ " generate.disabled = True\n",
+ "\n",
+ " #Calculate seed\n",
+ " seed = random.randint(0, 2147483647) if random_seed.value == -1 else random_seed.value\n",
+ "\n",
+ " with display_imgs:\n",
+ "\n",
+ " print(f\"Prompt:\\n{prompt.value + lighting.value + camera.value}\")\n",
+ "\n",
+ " images = pipe(\n",
+ " prompt.value + lighting.value + camera.value,\n",
+ " height = img_height.value,\n",
+ " width = img_width.value,\n",
+ " num_inference_steps = steps.value,\n",
+ " guidance_scale = CFG.value,\n",
+ " num_images_per_prompt = num_images.value,\n",
+ " negative_prompt = neg_prompt.value,\n",
+ " generator = torch.Generator(\"cuda\").manual_seed(seed),\n",
+ " ).images\n",
+ "\n",
+ " mediapy.show_images(images)\n",
+ "\n",
+ " print(f\"Seed:\\n{seed}\")\n",
+ "\n",
+ " generate.disabled = False\n",
+ "\n",
+ "#Display\n",
+ "generate.on_click(generate_img)\n",
+ "\n",
+ "widgets.VBox(\n",
+ " [\n",
+ " widgets.AppLayout(\n",
+ " header=widgets.VBox(\n",
+ " [camera, lighting]\n",
+ " ),\n",
+ " left_sidebar=widgets.VBox(\n",
+ " [num_images, steps, CFG, img_height, img_width, random_seed]\n",
+ " ),\n",
+ " center=widgets.VBox(\n",
+ " [prompt, neg_prompt, generate]\n",
+ " ),\n",
+ " right_sidebar=None,\n",
+ " footer=None\n",
+ " ),\n",
+ " display_imgs\n",
+ " ]\n",
+ ")"
+ ],
+ "metadata": {
+ "id": "-g-Q5UGcGATS"
+ },
+ "execution_count": null,
+ "outputs": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/runtime/getUrl.js b/src/runtime/getUrl.js
index 6c70a337..a7c0bdc8 100644
--- a/src/runtime/getUrl.js
+++ b/src/runtime/getUrl.js
@@ -21,7 +21,7 @@ module.exports = (url, options) => {
// Should url be wrapped?
// See https://drafts.csswg.org/css-values-3/#urls
if (/["'() \t\n]|(%20)/.test(url) || options.needQuotes) {
- return `"${url.replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
+ return `"${url.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
}
return url;
diff --git a/stable_diffusion.ipynb b/stable_diffusion.ipynb
new file mode 100644
index 00000000..da09c917
--- /dev/null
+++ b/stable_diffusion.ipynb
@@ -0,0 +1,96 @@
+{
+ "nbformat": 4,
+ "nbformat_minor": 0,
+ "metadata": {
+ "colab": {
+ "name": "stable-diffusion.ipynb",
+ "private_outputs": true,
+ "provenance": []
+ },
+ "kernelspec": {
+ "name": "python3",
+ "display_name": "Python 3"
+ },
+ "language_info": {
+ "name": "python"
+ },
+ "accelerator": "GPU",
+ "gpuClass": "standard"
+ },
+ "cells": [
+ {
+ "cell_type": "code",
+ "source": [
+ "%pip install --quiet --upgrade diffusers transformers accelerate mediapy peft"
+ ],
+ "metadata": {
+ "id": "ufD_d64nr08H"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "import mediapy as media\n",
+ "import random\n",
+ "import sys\n",
+ "import torch\n",
+ "\n",
+ "from diffusers import DiffusionPipeline, TCDScheduler\n",
+ "from huggingface_hub import hf_hub_download\n",
+ "\n",
+ "# Choose either 8 or 12 steps:\n",
+ "num_inference_steps = 12\n",
+ "\n",
+ "base_model_id = \"stabilityai/stable-diffusion-xl-base-1.0\"\n",
+ "repo_name = \"ByteDance/Hyper-SD\"\n",
+ "plural = \"s\" if num_inference_steps > 1 else \"\"\n",
+ "ckpt_name = f\"Hyper-SDXL-{num_inference_steps}step{plural}-CFG-lora.safetensors\"\n",
+ "device = \"cuda\"\n",
+ "\n",
+ "pipe = DiffusionPipeline.from_pretrained(base_model_id, torch_dtype=torch.float16, variant=\"fp16\").to(device)\n",
+ "pipe.load_lora_weights(hf_hub_download(repo_name, ckpt_name))\n",
+ "pipe.fuse_lora()\n",
+ "pipe.scheduler = TCDScheduler.from_config(pipe.scheduler.config)"
+ ],
+ "metadata": {
+ "id": "bG2hkmSEvByV"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "prompt = \"Romance Horror book cover in a dark, noir style, featuring a haunted mansion with eerie, glowing windows in a dark forest. three leopards, bat on sky\"\n",
+ "negative_prompt = \"text\"\n",
+ "seed = random.randint(0, 2147483647)\n",
+ "\n",
+ "# Pick a value between 5.0 and 8.0:\n",
+ "guidance_scale = 7\n",
+ "\n",
+ "# Decrease eta (min: 0, max: 1.0) to get more details with multi-step inference:\n",
+ "eta = 0.5\n",
+ "\n",
+ "images = pipe(\n",
+ " prompt = prompt,\n",
+ " negative_prompt = negative_prompt,\n",
+ " num_inference_steps = num_inference_steps,\n",
+ " guidance_scale = guidance_scale,\n",
+ " eta = eta,\n",
+ " generator = torch.Generator(device).manual_seed(seed),\n",
+ " ).images\n",
+ "\n",
+ "print(f\"Prompt:\\t{prompt}\\nSeed:\\t{seed}\")\n",
+ "media.show_images(images)\n",
+ "images[0].save(\"output.jpg\")"
+ ],
+ "metadata": {
+ "id": "AUc4QJfE-uR9"
+ },
+ "execution_count": null,
+ "outputs": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/runtime/__snapshots__/getUrl.test.js.snap b/test/runtime/__snapshots__/getUrl.test.js.snap
index a4eda700..f25225d7 100644
--- a/test/runtime/__snapshots__/getUrl.test.js.snap
+++ b/test/runtime/__snapshots__/getUrl.test.js.snap
@@ -85,3 +85,5 @@ exports[`escape should escape url 41`] = `""image other.png#hash""`;
exports[`escape should escape url 42`] = `""image other.png#hash""`;
exports[`escape should escape url 43`] = `""image other.png#hash""`;
+
+exports[`escape should escape url 44`] = `"https://www.example.com?path=path\\to\\resource"`;
diff --git a/test/runtime/getUrl.test.js b/test/runtime/getUrl.test.js
index 9dd2ae2b..9d68e475 100644
--- a/test/runtime/getUrl.test.js
+++ b/test/runtime/getUrl.test.js
@@ -127,5 +127,8 @@ describe("escape", () => {
{ hash: "#hash", needQuotes: true },
),
).toMatchSnapshot();
+ expect(
+ getUrl("https://www.example.com?path=path\\to\\resource"),
+ ).toMatchSnapshot();
});
});