-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
334 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
Welcome to submit a Pull Request to include new scripts into the Draw Things app! | ||
|
||
To submit the Pull Request, please make sure to include the following items in the description: | ||
|
||
1. License for the new script; | ||
|
||
2. Recommended settings / parameters to use with the script; | ||
|
||
3. Self-reported age rating, following this: <https://developer.apple.com/help/app-store-connect/reference/age-ratings/>. | ||
|
||
Thanks for your contribution! | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
name: Auto Assign | ||
on: | ||
issues: | ||
types: [opened] | ||
pull_request: | ||
types: [opened] | ||
jobs: | ||
run: | ||
runs-on: ubuntu-latest | ||
permissions: | ||
issues: write | ||
pull-requests: write | ||
steps: | ||
- name: 'Auto-assign issue' | ||
uses: pozil/auto-assign-issue@v1 | ||
with: | ||
repo-token: ${{ secrets.GITHUB_TOKEN }} | ||
assignees: liuliu | ||
numOfAssignee: 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
name: json | ||
on: | ||
push: | ||
branches: | ||
- main | ||
|
||
permissions: | ||
contents: write | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
with: | ||
fetch-depth: 0 | ||
|
||
- name: Run JSON generation. | ||
run: | | ||
python3 scripts/script_json.py | ||
git config --global user.email "[email protected]" | ||
git config --global user.name "docbot" | ||
git commit -a -m "Update metadata with new script." || true | ||
git pull --rebase && git push || true | ||
- name: Clean up json branch | ||
run: | | ||
git branch -D json || true | ||
git checkout -b json | ||
mkdir -p docs | ||
mkdir -p docs/scripts | ||
mv *.json ./docs | ||
find scripts -type f -name "*.js" -exec cp {} ./docs/scripts \; | ||
echo "scripts.drawthings.ai" > ./docs/CNAME | ||
- name: Add and commit documentation | ||
run: | | ||
git add "docs/*" && git commit -m "Update docs." | ||
- name: Push the new branch | ||
run: | | ||
git push --force origin json:json | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"display_name": "SD Ultimate Upscale", | ||
"author": "Draw Things", | ||
"file": "sd-ultimate-upscale.js", | ||
"description": "Generate high-quality, high-resolution images from low-resolution inputs, while preserving fine details and textures." | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
sd-ultimate-upscale |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"display_name": "SD Ultimate Upscale", | ||
"author": "Draw Things", | ||
"file": "sd-ultimate-upscale.js", | ||
"description": "Generate high-quality, high-resolution images from low-resolution inputs, while preserving fine details and textures." | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
//@api-1.0 | ||
// | ||
// "Scripts" is a way to enhance Draw Things experience with custom JavaScript snippets. Over time, | ||
// we will enhance Scripts with more APIs and do neccessary modifications on the said APIs, but we | ||
// will also keep the API stable. In particular, you can mark your script with a particular API | ||
// version and it will make sure we use the proper API toolchain to execute the script even in | ||
// the future some of the APIs changed. | ||
// | ||
// Existing APIs: | ||
// | ||
// We currently provides three global objects for you to interact with: `canvas`, `pipeline` and `filesystem`. | ||
// | ||
// `canvas` is the object to manipulate the existing infinite canvas on Draw Things interface. It | ||
// supports the following methods: | ||
// | ||
// `canvas.boundingBox`: return the bounding box of existing images collectively on the canvas. | ||
// `canvas.clear()`: remove all images on the canvas, same effect as you tap on the "New Canvas" button. | ||
// `canvas.createMask(width, height, value)`: create a mask with a given size and fill-in value. | ||
// `canvas.currentMask`: return the current mask on the view, you can further manipulate the mask through `Mask` APIs. | ||
// `canvas.loadImage(file)`: load image from a file (a path). | ||
// `canvas.moveCanvas(left, top)`: move the canvas to a particular coordinate, these coordinates work as if the zoom scale is 1x. | ||
// `canvas.saveImage(file, visibleRegionOnly)`: save the image currently on canvas to a file. | ||
// `canvas.canvasZoom`: Set / get the zoom scale of the canvas. | ||
// `canvas.topLeftCorner`: return the coordinate the canvas currently at, these coordinates work as if the zoom scale is 1x. | ||
// `canvas.updateCanvasSize(configuration)`: extract the size from configuration object and then sync the canvas with that size. | ||
// | ||
// `pipeline` is the object to run the image generation pipeline. It supports the following methods: | ||
// | ||
// `pipeline.configuration`: extract the current configuration, whether it is on screen or after pipeline.run. | ||
// `pipeline.findControlByName(name)`: using the display name to find a particular control. | ||
// `pipeline.findLoRAByName(name)`: using the display name to find a particular LoRA. | ||
// `pipeline.prompts`: return prompt and negativePrompt currently on the screen. | ||
// `pipeline.run({prompt: null, negativePrompt: null, configuration: configuration, mask: null})`: run image generation through | ||
// this API. You can optionally provides prompt / negativePrompt, or it can take prompt from the screen. mask can be provided | ||
// optionally too and sometimes it can be helpful. | ||
// | ||
// `filesystem` provides a simple access to the underlying file system. Note that Draw Things is | ||
// sandboxed so we can only have access to particular directories within the user file system. | ||
// | ||
// `filesystem.pictures.path`: get the path of the pictures folder. It is the system Pictures folder on macOS, and a Pictures | ||
// folder under Draw Things within Files app for iOS / iPadOS. | ||
// `filesystem.pictures.readEntries`: enumerate all the files under the pictures folder. | ||
// | ||
// This script upscales the original image to 512x512 tiles and fixes the seams. | ||
// This script assumes the original image is at 1x zoom level. | ||
// The configuration parameters in this script may need tuning to improve final image. | ||
// Author: @Gooster | ||
|
||
const USE_UPSCALER = "Real-ESRGAN X2+"; | ||
const UPSCALE_FACTOR = 2; | ||
|
||
const TILE_STRENGTH = 0.7; | ||
const INPAINTING_STRENGTH = 0.7; | ||
const MASK_BLUR = 6; | ||
const MIN_OVERLAP = 48; | ||
const TILE_SIZE = 512; | ||
|
||
const configuration = pipeline.configuration; | ||
configuration.maskBlur = MASK_BLUR; | ||
|
||
const imageRect = canvas.boundingBox; | ||
|
||
// initial upscale | ||
canvas.canvasZoom = 1; | ||
if (USE_UPSCALER) { | ||
pipeline.downloadBuiltin(USE_UPSCALER); | ||
configuration.strength = 0; | ||
configuration.upscaler = USE_UPSCALER; | ||
canvas.moveCanvas(imageRect.x, imageRect.y); | ||
// generate the upscaled image | ||
pipeline.run({ configuration: configuration }); | ||
} | ||
pipeline.downloadBuiltin("controlnet_tile_1.x_v1.1_f16.ckpt"); | ||
pipeline.downloadBuiltin("controlnet_inpaint_1.x_v1.1_f16.ckpt"); | ||
// tiling over the image to upscale | ||
configuration.strength = TILE_STRENGTH; | ||
tiledUpscale(configuration, imageRect, UPSCALE_FACTOR); | ||
canvas.moveCanvas(imageRect.x, imageRect.y); | ||
canvas.canvasZoom = 1; | ||
|
||
// tiled upscale support integer zoom levels | ||
function tiledUpscale(configuration, imageRect, zoom) { | ||
const minOverlap = MIN_OVERLAP; | ||
// set zoom level and reconfigure the canvas dimensions to tile size | ||
configuration.upscaler = null; | ||
configuration.width = configuration.height = TILE_SIZE; | ||
canvas.updateCanvasSize(configuration); | ||
canvas.canvasZoom = zoom; | ||
canvas.moveCanvas(imageRect.x, imageRect.y); | ||
const numTiles = { | ||
x: imageRect.width * zoom / configuration.width, | ||
y: imageRect.height * zoom / configuration.height | ||
} | ||
const tileSize = Math.floor(imageRect.width / numTiles.x) | ||
numTiles.x = Math.ceil(numTiles.x + (minOverlap * (numTiles.x - 1)) / configuration.width) | ||
numTiles.y = Math.ceil(numTiles.y + (minOverlap * (numTiles.y - 1)) / configuration.height) | ||
console.log(`Number of tiles: ${numTiles.x * numTiles.y}`); | ||
canvas.moveCanvas(imageRect.x, imageRect.y); | ||
const generatedRows = []; | ||
let previousCoordinates = { x: imageRect.x, y: imageRect.y }; | ||
let currentCoordinates = previousCoordinates; | ||
|
||
// tiling over the image | ||
for (let y = 0; y < numTiles.y; y++) { | ||
let currentRow = new Rectangle(0, 0, 0, 0) | ||
for (let x = 0; x < numTiles.x; x++) { | ||
// restore tile control and strength for each tile | ||
const tileControl = pipeline.findControlByName("Tile (SD v1.x, ControlNet 1.1)"); | ||
configuration.controls = [tileControl]; | ||
configuration.strength = TILE_STRENGTH; | ||
currentCoordinates = { | ||
x: imageRect.x + Math.floor(x * (tileSize - minOverlap)), | ||
y: imageRect.y + Math.floor(y * (tileSize - minOverlap)) | ||
}; | ||
if (x == 0) { | ||
currentRow.x = currentCoordinates.x; | ||
currentRow.y = currentCoordinates.y; | ||
} | ||
// the last tile should match the x, y edge of the image | ||
if (x == numTiles.x - 1) currentCoordinates.x = imageRect.x + imageRect.width - tileSize; | ||
if (y == numTiles.y - 1) currentCoordinates.y = imageRect.y + imageRect.height - tileSize; | ||
console.log(`Upscaling tile ${x + y * numTiles.x} of ${numTiles.x * numTiles.y}`); | ||
// add mask for tiles other than the first | ||
let mask = new Rectangle(currentCoordinates.x, currentCoordinates.y, tileSize, tileSize); | ||
if (x != 0) { | ||
// exclude the current row | ||
mask = mask.exclude(currentRow); | ||
} | ||
if (y != 0) { | ||
// exclude the row above | ||
mask = mask.exclude(generatedRows[y - 1]); | ||
} | ||
// convert mask coordinate to tile local coordinates | ||
mask.x -= currentCoordinates.x; | ||
mask.y -= currentCoordinates.y; | ||
mask.scale(configuration.width / tileSize); | ||
// ensure mask is the same size as the configuration | ||
const generateRegion = canvas.createMask(configuration.width, configuration.height, 0); | ||
generateRegion.fillRectangle( | ||
mask.x, | ||
mask.y, | ||
mask.width, | ||
mask.height, 2); | ||
canvas.moveCanvas(currentCoordinates.x, currentCoordinates.y) | ||
pipeline.run({ configuration: configuration, mask: generateRegion }); | ||
// seams are created at the right and bottom edge of each previous tile we can fix the seam at the next tile generation | ||
configuration.strength = INPAINTING_STRENGTH; | ||
const inpaintControl = pipeline.findControlByName("Inpainting (SD v1.x, ControlNet 1.1)"); | ||
configuration.controls = [inpaintControl]; | ||
const inpaintRegion = canvas.createMask(configuration.width, configuration.height, 0); | ||
let edgePolisher = 5; | ||
if (y > 0) { | ||
// after the first row always fill the previous row's bottom edge | ||
edgePolisher = x === 0 ? 0 : 5; | ||
const inpaintRect = new Rectangle( | ||
edgePolisher, | ||
mask.y - Math.floor(minOverlap / 2), | ||
configuration.width - edgePolisher, | ||
minOverlap); | ||
inpaintRegion.fillRectangle( | ||
inpaintRect.x, | ||
inpaintRect.y, | ||
inpaintRect.width, | ||
inpaintRect.height, | ||
2 | ||
) | ||
} | ||
if (x > 0) { | ||
edgePolisher = y === 0 ? 0 : 5; | ||
const inpaintRect = new Rectangle( | ||
mask.x - Math.floor(minOverlap / 2), | ||
edgePolisher, | ||
minOverlap, | ||
configuration.height - edgePolisher); | ||
inpaintRegion.fillRectangle( | ||
inpaintRect.x, | ||
inpaintRect.y, | ||
inpaintRect.width, | ||
inpaintRect.height, | ||
2 | ||
) | ||
} | ||
// skip over the first tile that doesn't have seams yet | ||
canvas.moveCanvas(currentCoordinates.x, currentCoordinates.y) | ||
if (x != 0 || y != 0) { | ||
console.log(`Inpainting seams on tile ${x + y * numTiles.x} of ${numTiles.x * numTiles.y}`); | ||
pipeline.run({ configuration: configuration, mask: inpaintRegion }); | ||
} | ||
previousCoordinates = currentCoordinates; | ||
currentRow = Rectangle.union(currentRow, new Rectangle(currentCoordinates.x, currentCoordinates.y, tileSize, tileSize)); | ||
} | ||
generatedRows.push(currentRow); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import copy | ||
import subprocess | ||
import os | ||
import json | ||
|
||
def sha256sum(file): | ||
result = subprocess.run(['sha256sum', file], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) | ||
if result.returncode == 0: | ||
return result.stdout.strip().split()[0] | ||
|
||
def collect_metadata_from_list(file_path): | ||
metadata_array = [] | ||
sha256_dict = {} | ||
with open(file_path, 'r') as file: | ||
# Read each line in the file | ||
for line in file: | ||
directory_path = line.strip() # Remove newline and whitespace | ||
print(directory_path) | ||
metadata_path = os.path.join('scripts', directory_path, 'metadata.json') | ||
# process the folders with metadata | ||
if os.path.exists(metadata_path): | ||
# Open and load the JSON content | ||
with open(metadata_path, 'r') as json_file: | ||
metadata = json.load(json_file) | ||
js_file_path = os.path.join('scripts', directory_path, metadata['file']) | ||
if os.path.exists(js_file_path): | ||
sha = sha256sum(js_file_path) | ||
metadata_array.append(copy.deepcopy(metadata)) | ||
sha256_dict[directory_path] = sha | ||
|
||
|
||
return metadata_array, sha256_dict | ||
|
||
# Replace 'scripts.txt' with the path to your actual file if it's located elsewhere | ||
scripts_txt_path = 'scripts.txt' | ||
metadata_array, sha256_dict = collect_metadata_from_list(scripts_txt_path) | ||
with open('scripts_sha256.json', 'w') as file: | ||
sha256_string = json.dumps(sha256_dict, indent=2) | ||
file.write(sha256_string) | ||
|
||
# Convert the array to a JSON string for printing or further processing | ||
with open('scripts.json', 'w') as file: | ||
metadata_json_string = json.dumps(metadata_array, indent=2) | ||
file.write(metadata_json_string) |