progress
This commit is contained in:
65
.opencode/skills/alpha-mask-creator/SKILL.md
Normal file
65
.opencode/skills/alpha-mask-creator/SKILL.md
Normal file
@@ -0,0 +1,65 @@
|
||||
---
|
||||
name: alpha-mask-creator
|
||||
description: Takes an image and creates a high quality alpha mask of the requested entity. Typically invoked when the user asks to "create a specific alpha mask for"
|
||||
---
|
||||
# Alpha Mask Creator
|
||||
|
||||
This skill creates alpha masks from images based on user requests. Use this when the user asks to "create a specific alpha mask for..." along with providing an image.
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Draw Polygon Outline
|
||||
Use the `polygon-drawer` skill to draw a polygon outline around the identified object. Save the output to `./tmp/` with a sensible filename:
|
||||
- Format: `<original_name>.<object>.png`
|
||||
- Example: If the user wants a door mask from `room_010.png`, name it `room_010.door.png`
|
||||
|
||||
### 2. Extract Mask
|
||||
Run the `extract_mask` script to generate the alpha mask:
|
||||
```bash
|
||||
source ./tools/venv/bin/activate
|
||||
python tools/extract_mask.py "the door with the red outline" ./tmp/<original_name>.<object>.png ./tmp/<original_name>.<object>.mask.png
|
||||
```
|
||||
|
||||
### 3. Quality Check
|
||||
Examine the generated mask using the `image-inspector` agent:
|
||||
- Check if the mask is precise and accurate
|
||||
- Verify the object is properly isolated
|
||||
- Ensure no artifacts or incorrect areas
|
||||
|
||||
**If the mask quality is poor:**
|
||||
- Retry the polygon-drawer step with different parameters or seed
|
||||
- Iterate until the mask is satisfactory
|
||||
|
||||
### 5. Report Results
|
||||
Tell the user:
|
||||
- Location of the polygon outline image: `./tmp/<original_name>.<object>.png`
|
||||
- Location of the mask: `./tmp/<original_name>.<object>.mask.png`
|
||||
- Confirm the mask quality is acceptable
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
| User Request | Original File | Output Files |
|
||||
|--------------|---------------|--------------|
|
||||
| Door | `room_010.png` | `room_010.door.png`, `room_010.door.mask.png` |
|
||||
| Tree | `forest_bg.png` | `forest_bg.tree.png`, `forest_bg.tree.mask.png` |
|
||||
| Character | `scene_005.png` | `scene_005.character.png`, `scene_005.character.mask.png` |
|
||||
|
||||
## Tools Used
|
||||
|
||||
- `image-inspector` - Analyze images to locate objects
|
||||
- `polygon-drawer` - Draw polygon outlines
|
||||
- `bash` - Run extract_mask script
|
||||
- `image-expert` - Verify mask quality
|
||||
|
||||
## Example Interaction
|
||||
|
||||
**User:** "Create a specific alpha mask for the door in this image" [attaches `cottage_exterior.png`]
|
||||
|
||||
**You:**
|
||||
1. Analyze the image to locate the door
|
||||
2. Draw polygon around the door → `./tmp/cottage_exterior.door.png`
|
||||
3. Extract mask → `./tmp/cottage_exterior.door.mask.png`
|
||||
4. Verify mask quality
|
||||
5. Report: "Mask created successfully! Files saved to:
|
||||
- Polygon outline: `./tmp/cottage_exterior.door.png`
|
||||
- Alpha mask: `./tmp/cottage_exterior.door.mask.png`"
|
||||
67
.opencode/skills/kq4-polygon-drawer/SKILL.md
Normal file
67
.opencode/skills/kq4-polygon-drawer/SKILL.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Polygon Drawer Skill
|
||||
|
||||
This skill uses @tools/draw_polygon.py to draw polygons on any image. It's useful for highlighting areas, marking objects, creating masks, or annotating images with colored shapes.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Draw a red box around an object
|
||||
```
|
||||
Draw a red box around the door in image.png
|
||||
# Executes: python tools/draw_polygon.py tmp/annotated_image.png "0.3,0.2 0.7,0.2 0.7,0.8 0.3,0.8" --color red --save tmp/annotated_image.png
|
||||
# Opens: /home/noti/dev/ai-game-2/tmp/annotated_image.png
|
||||
```
|
||||
|
||||
### Highlight an area in green
|
||||
```
|
||||
Highlight the pool in green using a polygon
|
||||
# Executes: python tools/draw_polygon.py tmp/pool_highlight.png "0.2,0.5 0.8,0.5 0.8,0.9 0.2,0.9" --color green --fill --save tmp/pool_highlight.png
|
||||
# Opens: /home/noti/dev/ai-game-2/tmp/pool_highlight.png
|
||||
```
|
||||
|
||||
### Draw a blue circle (approximated with polygon)
|
||||
```
|
||||
Draw a blue circle around the tree
|
||||
# Executes: python tools/draw_polygon.py tmp/tree_circle.png "0.4,0.3 0.6,0.3 0.6,0.5 0.4,0.5" --color blue --save tmp/tree_circle.png
|
||||
# Opens: /home/noti/dev/ai-game-2/tmp/tree_circle.png
|
||||
```
|
||||
|
||||
### Fill a region with semi-transparent overlay
|
||||
```
|
||||
Fill the sky area with semi-transparent yellow
|
||||
# Executes: python tools/draw_polygon.py tmp/sky_overlay.png "0,0 1,0 1,0.6 0,0.6" --color yellow --fill --save tmp/sky_overlay.png
|
||||
# Opens: /home/noti/dev/ai-game-2/tmp/sky_overlay.png
|
||||
```
|
||||
|
||||
### Mark a specific point with a small polygon
|
||||
```
|
||||
Mark the character location with a red polygon
|
||||
# Executes: python tools/draw_polygon.py tmp/character_mark.png "0.5,0.4 0.52,0.38 0.54,0.4 0.52,0.42" --color red --save tmp/character_mark.png
|
||||
# Opens: /home/noti/dev/ai-game-2/tmp/character_mark.png
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Analyze** the image to understand what needs highlighting
|
||||
2. **Extract** polygon coordinates based on visual patterns or user input
|
||||
3. **Execute** the draw_polygon.py script with percentage coordinates (0.0-1.0)
|
||||
4. **Save** the result to ./tmp/ and display the file path
|
||||
5. **Open** the output for you to view
|
||||
|
||||
## Coordinate System
|
||||
|
||||
Coordinates use **percentage mode** (0.0-1.0) by default:
|
||||
- `0.0,0.0` = top-left corner
|
||||
- `1.0,1.0` = bottom-right corner
|
||||
- `0.5,0.5` = center of image
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
- Highlighting objects or regions of interest
|
||||
- Creating masks for image processing
|
||||
- Marking locations for documentation
|
||||
- Annotating images with colored boundaries
|
||||
- Visualizing spatial relationships
|
||||
|
||||
## Output
|
||||
|
||||
All annotated images are saved to `./tmp/` and the full path is displayed so you can open them in your image viewer.
|
||||
79
.opencode/skills/polygon-drawer/SKILL.md
Normal file
79
.opencode/skills/polygon-drawer/SKILL.md
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
name: polygon-drawer
|
||||
description: Takes an image and adds a polygon to it at the user's request. Typically the user will ask to add a polygon around a particular object
|
||||
---
|
||||
# Polygon Drawer Skill
|
||||
|
||||
This skill uses @tools/draw_polygon.py to help you draw polygons on any image. It's useful for highlighting areas, drawing boxes, marking regions, or annotating images with colored shapes.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Draw a red box around an object
|
||||
```
|
||||
Draw a red box around the door in <image>
|
||||
```
|
||||
|
||||
### Highlight a specific area in green
|
||||
```
|
||||
Highlight the pool area in <image> with a green polygon
|
||||
```
|
||||
|
||||
### Draw a blue rectangle with percentage coordinates
|
||||
```
|
||||
Draw a blue polygon on <image> using coordinates: 0.5,0.3 0.7,0.3 0.7,0.6 0.5,0.6
|
||||
```
|
||||
|
||||
### Mark a triangular region with fill
|
||||
```
|
||||
Draw a semi-transparent yellow triangle on <image>: 0.5,0.1 0.9,0.9 0.1,0.9 --fill
|
||||
```
|
||||
|
||||
### Draw a custom-colored polygon with thick lines
|
||||
```
|
||||
Draw a cyan polygon on <image>: 0.2,0.2 0.8,0.2 0.8,0.8 0.2,0.8 --color cyan --thickness 5
|
||||
```
|
||||
|
||||
### Use pixel coordinates (absolute mode)
|
||||
```
|
||||
Draw a red box using pixel coordinates: 100,50 400,50 400,300 100,300 --absolute
|
||||
```
|
||||
|
||||
Make sure to quote the coordinates when calling the script: "100,50 400,50 400,300 100,300"
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Analyze** the image to identify what needs highlighting
|
||||
2. **Calculate** polygon coordinates (default: percentage mode 0.0-1.0)
|
||||
3. **Execute** `@tools/draw_polygon.py` with the calculated points
|
||||
4. **Output** the resulting image to `./tmp/` directory
|
||||
5. **Display** the save path so you can open it
|
||||
|
||||
## Coordinate Systems
|
||||
|
||||
- **Percentage mode** (default): 0.0-1.0 coordinates where (0,0) is top-left and (1,1) is bottom-right
|
||||
- **Absolute mode**: Use `--absolute` flag for actual pixel coordinates
|
||||
|
||||
## Output Location
|
||||
By default, the image is just shown to the user without writing anything.
|
||||
|
||||
If the user asks to save it, use the --save flag.
|
||||
|
||||
All generated images are saved to:
|
||||
```
|
||||
./tmp/
|
||||
```
|
||||
|
||||
The skill will output the full path to the saved image, e.g., `./tmp/output_1234567890.png`
|
||||
|
||||
## Color Options
|
||||
|
||||
- Named colors: red, green, blue, yellow, cyan, magenta, white, black, orange, purple, brown, pink, gray, grey, lime, navy, teal, maroon
|
||||
- Hex codes: #FF0000, #00FF00FF, etc.
|
||||
|
||||
## Flags
|
||||
|
||||
- `--fill`: Fill polygon with semi-transparent color
|
||||
- `--absolute`: Use pixel coordinates instead of percentages
|
||||
- `--color <name>`: Specify polygon color (default: red)
|
||||
- `--thickness <pixels>`: Line thickness (default: 4)
|
||||
- `--save <path>`: Override default save location (still goes to ./tmp/)
|
||||
@@ -8,6 +8,10 @@ This is a **Godot 4.6** game project (King's Quest IV remake). The project uses:
|
||||
- NavigationServer2D for pathfinding
|
||||
- Custom script builder pattern for game scripts
|
||||
|
||||
## Tools
|
||||
* Helper scripts (python) exist in ./tools
|
||||
* a virtual env exists there, too. When you need to run one, you should source ./tools/venv/bin/activate
|
||||
|
||||
|
||||
## Build & Development Commands
|
||||
|
||||
@@ -290,3 +294,4 @@ scene.start_main_script(scene.ScriptBuilder.init(scene.ScriptBuilder.walk_path(e
|
||||
func _on_exit_interacted() -> void:
|
||||
$target_scene.default_script(self)
|
||||
```
|
||||
|
||||
|
||||
BIN
asset-work/kq4_062_bedroom/caption_1_3877392774_with_box.png
LFS
Normal file
BIN
asset-work/kq4_062_bedroom/caption_1_3877392774_with_box.png
LFS
Normal file
Binary file not shown.
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://w8p5drokk4sv"
|
||||
path="res://.godot/imported/caption_1_3877392774_with_box.png-49e8cf7c8aa8b4112e682ca98e672cf8.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://asset-work/kq4_062_bedroom/caption_1_3877392774_with_box.png"
|
||||
dest_files=["res://.godot/imported/caption_1_3877392774_with_box.png-49e8cf7c8aa8b4112e682ca98e672cf8.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
Binary file not shown.
BIN
tmp/caption_1_1979017646_generated.house.mask.png
LFS
Normal file
BIN
tmp/caption_1_1979017646_generated.house.mask.png
LFS
Normal file
Binary file not shown.
40
tmp/caption_1_1979017646_generated.house.mask.png.import
Normal file
40
tmp/caption_1_1979017646_generated.house.mask.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cqq5llgcudt15"
|
||||
path="res://.godot/imported/caption_1_1979017646_generated.house.mask.png-405a06af69107de7cf56d2c8cd43a11a.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tmp/caption_1_1979017646_generated.house.mask.png"
|
||||
dest_files=["res://.godot/imported/caption_1_1979017646_generated.house.mask.png-405a06af69107de7cf56d2c8cd43a11a.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
tmp/caption_1_1979017646_generated.house.png
LFS
Normal file
BIN
tmp/caption_1_1979017646_generated.house.png
LFS
Normal file
Binary file not shown.
40
tmp/caption_1_1979017646_generated.house.png.import
Normal file
40
tmp/caption_1_1979017646_generated.house.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bjjc74ww1ynu2"
|
||||
path="res://.godot/imported/caption_1_1979017646_generated.house.png-ee332416aaeec8be4ca29e4afab44dae.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tmp/caption_1_1979017646_generated.house.png"
|
||||
dest_files=["res://.godot/imported/caption_1_1979017646_generated.house.png-ee332416aaeec8be4ca29e4afab44dae.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
tmp/caption_1_454377357_generated.door.mask.png
LFS
Normal file
BIN
tmp/caption_1_454377357_generated.door.mask.png
LFS
Normal file
Binary file not shown.
40
tmp/caption_1_454377357_generated.door.mask.png.import
Normal file
40
tmp/caption_1_454377357_generated.door.mask.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bh0a1gngcfhlt"
|
||||
path="res://.godot/imported/caption_1_454377357_generated.door.mask.png-aec0dc7b07204882097c739e77da3d17.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tmp/caption_1_454377357_generated.door.mask.png"
|
||||
dest_files=["res://.godot/imported/caption_1_454377357_generated.door.mask.png-aec0dc7b07204882097c739e77da3d17.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
tmp/caption_1_454377357_generated.door.png
LFS
Normal file
BIN
tmp/caption_1_454377357_generated.door.png
LFS
Normal file
Binary file not shown.
40
tmp/caption_1_454377357_generated.door.png.import
Normal file
40
tmp/caption_1_454377357_generated.door.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cgx781rh8x160"
|
||||
path="res://.godot/imported/caption_1_454377357_generated.door.png-bc97e1ab49f2ba5ecd73d4604203e223.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tmp/caption_1_454377357_generated.door.png"
|
||||
dest_files=["res://.godot/imported/caption_1_454377357_generated.door.png-bc97e1ab49f2ba5ecd73d4604203e223.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
tmp/caption_1_454377357_generated.door.v2.mask.png
LFS
Normal file
BIN
tmp/caption_1_454377357_generated.door.v2.mask.png
LFS
Normal file
Binary file not shown.
40
tmp/caption_1_454377357_generated.door.v2.mask.png.import
Normal file
40
tmp/caption_1_454377357_generated.door.v2.mask.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b82q5606fv2ta"
|
||||
path="res://.godot/imported/caption_1_454377357_generated.door.v2.mask.png-3db723937261dbc5639bd97d6f39ddb8.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tmp/caption_1_454377357_generated.door.v2.mask.png"
|
||||
dest_files=["res://.godot/imported/caption_1_454377357_generated.door.v2.mask.png-3db723937261dbc5639bd97d6f39ddb8.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
tmp/caption_1_454377357_generated.door.v2.png
LFS
Normal file
BIN
tmp/caption_1_454377357_generated.door.v2.png
LFS
Normal file
Binary file not shown.
40
tmp/caption_1_454377357_generated.door.v2.png.import
Normal file
40
tmp/caption_1_454377357_generated.door.v2.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://doo1jdbpi336u"
|
||||
path="res://.godot/imported/caption_1_454377357_generated.door.v2.png-2fdfe23cc08c0c7abc1681d07849c9fc.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tmp/caption_1_454377357_generated.door.v2.png"
|
||||
dest_files=["res://.godot/imported/caption_1_454377357_generated.door.v2.png-2fdfe23cc08c0c7abc1681d07849c9fc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
tmp/stone_box.png
LFS
Normal file
BIN
tmp/stone_box.png
LFS
Normal file
Binary file not shown.
40
tmp/stone_box.png.import
Normal file
40
tmp/stone_box.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://tc8kp6g4d6v3"
|
||||
path="res://.godot/imported/stone_box.png-23873de51cd956b464cbf5c40e614970.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tmp/stone_box.png"
|
||||
dest_files=["res://.godot/imported/stone_box.png-23873de51cd956b464cbf5c40e614970.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
tmp/stone_mask.png
LFS
Normal file
BIN
tmp/stone_mask.png
LFS
Normal file
Binary file not shown.
40
tmp/stone_mask.png.import
Normal file
40
tmp/stone_mask.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d1h745821w85b"
|
||||
path="res://.godot/imported/stone_mask.png-2f0bfa74533a45031cbfc6d9f501b5d2.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tmp/stone_mask.png"
|
||||
dest_files=["res://.godot/imported/stone_mask.png-2f0bfa74533a45031cbfc6d9f501b5d2.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
tmp/stone_outline.png
LFS
Normal file
BIN
tmp/stone_outline.png
LFS
Normal file
Binary file not shown.
40
tmp/stone_outline.png.import
Normal file
40
tmp/stone_outline.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cdo0hg0am6cus"
|
||||
path="res://.godot/imported/stone_outline.png-33fd18a74617b08b2436a4a9d98a33b6.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tmp/stone_outline.png"
|
||||
dest_files=["res://.godot/imported/stone_outline.png-33fd18a74617b08b2436a4a9d98a33b6.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
212
tools/draw_polygon.py
Normal file
212
tools/draw_polygon.py
Normal file
@@ -0,0 +1,212 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Draw a polygon on an image using Pillow."""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from PIL import Image, ImageDraw, ImageFilter
|
||||
except ImportError:
|
||||
print("Error: Pillow is required. Install with: pip install Pillow")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
COLOR_PALETTE = {
|
||||
"red": "#FF0000",
|
||||
"green": "#00FF00",
|
||||
"blue": "#0000FF",
|
||||
"yellow": "#FFFF00",
|
||||
"cyan": "#00FFFF",
|
||||
"magenta": "#FF00FF",
|
||||
"white": "#FFFFFF",
|
||||
"black": "#000000",
|
||||
"orange": "#FFA500",
|
||||
"purple": "#800080",
|
||||
"brown": "#A52A2A",
|
||||
"pink": "#FFC0CB",
|
||||
"gray": "#808080",
|
||||
"grey": "#808080",
|
||||
"lime": "#00FF00",
|
||||
"navy": "#000080",
|
||||
"teal": "#008080",
|
||||
"maroon": "#800000",
|
||||
}
|
||||
|
||||
|
||||
def parse_color(color_str: str) -> str:
|
||||
"""Parse color string to hex."""
|
||||
color_lower = color_str.lower()
|
||||
if color_lower in COLOR_PALETTE:
|
||||
return COLOR_PALETTE[color_lower]
|
||||
if color_str.startswith("#") and len(color_str) in [7, 9]:
|
||||
return color_str
|
||||
raise ValueError(
|
||||
f"Invalid color '{color_str}'. Use a name (e.g., red, blue) or hex code (e.g., #FF0000)."
|
||||
)
|
||||
|
||||
|
||||
def parse_points(points_str: str) -> list[tuple[float, float]]:
|
||||
"""Parse points string into list of (x, y) tuples."""
|
||||
points = []
|
||||
for point in points_str.split():
|
||||
if "," not in point:
|
||||
raise ValueError(f"Invalid point format: '{point}'. Use 'x,y' format.")
|
||||
try:
|
||||
x, y = point.split(",")
|
||||
points.append((float(x.strip()), float(y.strip())))
|
||||
except ValueError:
|
||||
raise ValueError(f"Invalid point format: '{point}'. Use 'x,y' format.")
|
||||
return points
|
||||
|
||||
|
||||
def convert_to_pixels(
|
||||
points: list[tuple[float, float]], width: int, height: int, absolute: bool
|
||||
) -> list[tuple[int, int]]:
|
||||
"""Convert points to pixel coordinates."""
|
||||
if absolute:
|
||||
return [(int(x), int(y)) for x, y in points]
|
||||
else:
|
||||
return [
|
||||
(int(x * width), int(y * height))
|
||||
for x, y in points
|
||||
if 0 <= x <= 1 and 0 <= y <= 1
|
||||
]
|
||||
|
||||
|
||||
def draw_polygon_on_image(
|
||||
image_path: Path,
|
||||
points: list[tuple[float, float]],
|
||||
color: str,
|
||||
thickness: int,
|
||||
fill: bool,
|
||||
absolute: bool,
|
||||
save_path: Path | None,
|
||||
) -> None:
|
||||
"""Draw polygon on image."""
|
||||
image = Image.open(image_path).convert("RGBA")
|
||||
width, height = image.size
|
||||
|
||||
pixel_points = convert_to_pixels(points, width, height, absolute)
|
||||
|
||||
if len(pixel_points) < 3:
|
||||
raise ValueError("Need at least 3 points to draw a polygon.")
|
||||
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
hex_color = parse_color(color)
|
||||
draw.polygon(pixel_points, fill=hex_color if fill else None, outline=hex_color, width=thickness)
|
||||
|
||||
if fill:
|
||||
alpha = 128
|
||||
fill_color = hex_color[:7] + f"{alpha:02X}"
|
||||
draw.polygon(pixel_points, fill=fill_color, outline=hex_color, width=thickness)
|
||||
|
||||
if save_path:
|
||||
image.save(save_path)
|
||||
print(f"Saved: {save_path}")
|
||||
else:
|
||||
image.show()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Draw a polygon on an image using Pillow.",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Draw a triangle using percentages (default)
|
||||
python draw_polygon.py scene.png "0.5,0.1 0.9,0.9 0.1,0.9"
|
||||
|
||||
# Draw with pixel coordinates (absolute mode)
|
||||
python draw_polygon.py scene.png "100,50 400,50 250,300" --absolute
|
||||
|
||||
# Save to file with blue color
|
||||
python draw_polygon.py scene.png "0.2,0.2 0.8,0.2 0.8,0.8 0.2,0.8" --color blue --save output.png
|
||||
|
||||
# Fill polygon semi-transparently
|
||||
python draw_polygon.py scene.png "0.3,0.3 0.7,0.3 0.7,0.7 0.3,0.7" --fill --color red
|
||||
|
||||
# Draw with custom hex color and thickness
|
||||
python draw_polygon.py scene.png "0.1,0.1 0.9,0.1 0.9,0.9 0.1,0.9" --color #00FF00 --thickness 5
|
||||
|
||||
Coordinate Formats:
|
||||
Percentage (default): 0.0 to 1.0, where 0.0,0.0 is top-left and 1.0,1.0 is bottom-right
|
||||
Absolute (with --absolute): Actual pixel coordinates from 0 to image width/height
|
||||
|
||||
Color Formats:
|
||||
Named colors: red, green, blue, yellow, cyan, magenta, white, black, orange, purple, brown, pink, gray, grey, lime, navy, teal, maroon
|
||||
Hex codes: #FF0000, #00FF00FF, etc.
|
||||
""",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"image",
|
||||
type=Path,
|
||||
help="Path to input image file",
|
||||
)
|
||||
parser.add_argument(
|
||||
"points",
|
||||
type=str,
|
||||
help="Space-separated list of x,y coordinates (e.g., '0.1,0.1 0.9,0.1 0.5,0.9'). Use percentages (0.0-1.0) by default, or --absolute for pixel coordinates.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--color",
|
||||
type=str,
|
||||
default="red",
|
||||
help="Polygon color (default: red). Use named colors (red, blue, green, etc.) or hex codes (#FF0000).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--absolute",
|
||||
action="store_true",
|
||||
help="Use pixel coordinates instead of percentages",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--save",
|
||||
type=Path,
|
||||
default=None,
|
||||
help="Save output to file path instead of displaying",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--thickness",
|
||||
type=int,
|
||||
default=2,
|
||||
help="Line thickness in pixels (default: 2)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--fill",
|
||||
action="store_true",
|
||||
help="Fill polygon with semi-transparent color",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.image.exists():
|
||||
print(f"Error: Image not found: {args.image}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
points = parse_points(args.points)
|
||||
if len(points) < 3:
|
||||
print(f"Error: Need at least 3 points. Got {len(points)}.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
draw_polygon_on_image(
|
||||
image_path=args.image,
|
||||
points=points,
|
||||
color=args.color,
|
||||
thickness=args.thickness,
|
||||
fill=args.fill,
|
||||
absolute=args.absolute,
|
||||
save_path=args.save,
|
||||
)
|
||||
except ValueError as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -125,7 +125,7 @@ def extract_mask(
|
||||
with open(workflow_path, "r") as f:
|
||||
workflow = json.load(f)
|
||||
|
||||
prompt_text = f"Create a black and white alpha mask of {subject}"
|
||||
prompt_text = f"Create a black and white alpha mask of {subject}, leaving everything else black"
|
||||
|
||||
print(f"Encoding input image...")
|
||||
base64_image = encode_image_base64(input_image)
|
||||
|
||||
154
tools/make_ora.py
Normal file
154
tools/make_ora.py
Normal file
@@ -0,0 +1,154 @@
|
||||
import argparse
|
||||
import os
|
||||
import zipfile
|
||||
import tempfile
|
||||
import shutil
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
NUM_LAYERS = 4
|
||||
MASKS_PER_LAYER = 3
|
||||
NOISE_SCALES = [4, 16, 64]
|
||||
|
||||
|
||||
def noise_mask(w, h, scale):
|
||||
sw = max(1, w // scale)
|
||||
sh = max(1, h // scale)
|
||||
|
||||
noise = np.random.rand(sh, sw)
|
||||
img = Image.fromarray((noise * 255).astype(np.uint8), "L")
|
||||
img = img.resize((w, h), Image.BILINEAR)
|
||||
|
||||
return img
|
||||
|
||||
|
||||
def build_stack_xml(w, h, groups):
|
||||
image = ET.Element(
|
||||
"image",
|
||||
{"version": "0.0.3", "w": str(w), "h": str(h)}
|
||||
)
|
||||
|
||||
root_stack = ET.SubElement(image, "stack")
|
||||
|
||||
for group in groups:
|
||||
|
||||
group_el = ET.SubElement(
|
||||
root_stack,
|
||||
"stack",
|
||||
{"name": group["name"]}
|
||||
)
|
||||
|
||||
for layer in group["layers"]:
|
||||
ET.SubElement(
|
||||
group_el,
|
||||
"layer",
|
||||
{
|
||||
"name": layer["name"],
|
||||
"src": layer["src"],
|
||||
"opacity": "1.0"
|
||||
}
|
||||
)
|
||||
|
||||
return ET.tostring(image, encoding="utf-8", xml_declaration=True)
|
||||
|
||||
|
||||
def build_ora(input_png, output_ora):
|
||||
|
||||
base = Image.open(input_png).convert("RGBA")
|
||||
w, h = base.size
|
||||
|
||||
tmp = tempfile.mkdtemp()
|
||||
|
||||
try:
|
||||
|
||||
data_dir = os.path.join(tmp, "data")
|
||||
thumb_dir = os.path.join(tmp, "Thumbnails")
|
||||
|
||||
os.makedirs(data_dir)
|
||||
os.makedirs(thumb_dir)
|
||||
|
||||
groups = []
|
||||
|
||||
for i in range(NUM_LAYERS):
|
||||
|
||||
group_layers = []
|
||||
|
||||
base_path = f"data/layer_{i}.png"
|
||||
base.save(os.path.join(tmp, base_path))
|
||||
|
||||
group_layers.append({
|
||||
"name": "base",
|
||||
"src": base_path
|
||||
})
|
||||
|
||||
for j in range(MASKS_PER_LAYER):
|
||||
|
||||
mask = noise_mask(w, h, NOISE_SCALES[j])
|
||||
|
||||
mask_path = f"data/layer_{i}_mask_{j}.png"
|
||||
mask.save(os.path.join(tmp, mask_path))
|
||||
|
||||
group_layers.append({
|
||||
"name": f"mask {j}",
|
||||
"src": mask_path
|
||||
})
|
||||
|
||||
groups.append({
|
||||
"name": f"group {i}",
|
||||
"layers": group_layers
|
||||
})
|
||||
|
||||
stack_xml = build_stack_xml(w, h, groups)
|
||||
|
||||
with open(os.path.join(tmp, "stack.xml"), "wb") as f:
|
||||
f.write(stack_xml)
|
||||
|
||||
with open(os.path.join(tmp, "mimetype"), "w") as f:
|
||||
f.write("image/openraster")
|
||||
|
||||
base.save(os.path.join(tmp, "mergedimage.png"))
|
||||
|
||||
thumb = base.copy()
|
||||
thumb.thumbnail((256, 256))
|
||||
thumb.save(os.path.join(thumb_dir, "thumbnail.png"))
|
||||
|
||||
with zipfile.ZipFile(output_ora, "w") as z:
|
||||
|
||||
z.write(
|
||||
os.path.join(tmp, "mimetype"),
|
||||
"mimetype",
|
||||
compress_type=zipfile.ZIP_STORED
|
||||
)
|
||||
|
||||
for root, _, files in os.walk(tmp):
|
||||
for file in files:
|
||||
|
||||
if file == "mimetype":
|
||||
continue
|
||||
|
||||
full = os.path.join(root, file)
|
||||
rel = os.path.relpath(full, tmp)
|
||||
|
||||
z.write(full, rel)
|
||||
|
||||
finally:
|
||||
shutil.rmtree(tmp)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate ORA with grouped layers and mask layers"
|
||||
)
|
||||
|
||||
parser.add_argument("input_png")
|
||||
parser.add_argument("output_ora")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
build_ora(args.input_png, args.output_ora)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,3 +1,4 @@
|
||||
opencv-python
|
||||
numpy
|
||||
shapely
|
||||
Pillow>=10.0.0
|
||||
|
||||
Reference in New Issue
Block a user