فهرست منبع

Merge branch 'master' into release

Krzysztof Krysiński 1 ماه پیش
والد
کامیت
5fefed6569
100فایلهای تغییر یافته به همراه4983 افزوده شده و 573 حذف شده
  1. 39 0
      .env-dev/run-build-bin.nix
  2. 35 0
      .env-dev/run-rider.nix
  3. 34 0
      .env-dev/shell.nix
  4. 0 17
      .github/ISSUE_TEMPLATE/feature_request.md
  5. 33 0
      .github/ISSUE_TEMPLATE/feature_request.yml
  6. 0 35
      .github/ISSUE_TEMPLATE/report_a_bug.md
  7. 104 0
      .github/ISSUE_TEMPLATE/report_a_bug.yml
  8. 89 0
      .github/workflows/localization.yml
  9. 179 0
      .github/workflows/localization/download-languages.py
  10. 96 0
      .github/workflows/localization/reference-comparison.py
  11. 50 0
      .github/workflows/localization/upload-reference.py
  12. 66 0
      .github/workflows/tests-windows.yml
  13. 8 2
      .gitignore
  14. 12 0
      .gitmodules
  15. 128 0
      CODE_OF_CONDUCT.md
  16. 19 12
      CONTRIBUTING.md
  17. 10 6
      PULL_REQUEST_TEMPLATE.md
  18. 61 23
      README.md
  19. 1214 0
      Third Party Licenses/ThirdPartyLicenses.txt
  20. 165 0
      Third Party Licenses/ffmpeg-linux.txt
  21. 168 0
      Third Party Licenses/ffmpeg-macos.txt
  22. 227 0
      Third Party Licenses/ffmpeg-windows.txt
  23. 0 46
      azure-pipelines.yml
  24. 2 0
      gen_third_party_licenses.sh
  25. 1 1
      incompatible.json
  26. 97 0
      samples/Custom.ruleset
  27. 103 0
      samples/Directory.Build.props
  28. 93 0
      samples/PixiEditorExtensionSamples.sln
  29. 8 0
      samples/Sample10_VisualTree/Program.cs
  30. 45 0
      samples/Sample10_VisualTree/Sample10_VisualTree.csproj
  31. 44 0
      samples/Sample10_VisualTree/VisualTreeSampleExtension.cs
  32. 39 0
      samples/Sample10_VisualTree/extension.json
  33. 22 0
      samples/Sample1_HelloWorld/HelloWorldExtension.cs
  34. 14 0
      samples/Sample1_HelloWorld/Program.cs
  35. 30 0
      samples/Sample1_HelloWorld/Sample1_HelloWorld.csproj
  36. 27 0
      samples/Sample1_HelloWorld/extension.json
  37. 4 0
      samples/Sample2_LocalizationSample/Localization/en.json
  38. 29 0
      samples/Sample2_LocalizationSample/LocalizationSampleExtension.cs
  39. 14 0
      samples/Sample2_LocalizationSample/Program.cs
  40. 13 0
      samples/Sample2_LocalizationSample/README.md
  41. 37 0
      samples/Sample2_LocalizationSample/Sample2_LocalizationSample.csproj
  42. 36 0
      samples/Sample2_LocalizationSample/extension.json
  43. 45 0
      samples/Sample3_Preferences/PreferencesSampleExtension.cs
  44. 14 0
      samples/Sample3_Preferences/Program.cs
  45. 0 0
      samples/Sample3_Preferences/README.md
  46. 37 0
      samples/Sample3_Preferences/Sample3_Preferences.csproj
  47. 30 0
      samples/Sample3_Preferences/extension.json
  48. 30 0
      samples/Sample4_CreatePopup/CreatePopupSampleExtension.cs
  49. 14 0
      samples/Sample4_CreatePopup/Program.cs
  50. 0 0
      samples/Sample4_CreatePopup/README.md
  51. 37 0
      samples/Sample4_CreatePopup/Sample4_CreatePopup.csproj
  52. 27 0
      samples/Sample4_CreatePopup/extension.json
  53. 16 0
      samples/Sample5_Resources/Program.cs
  54. 1 0
      samples/Sample5_Resources/Resources/ExampleFile.txt
  55. 37 0
      samples/Sample5_Resources/ResourcesSampleExtension.cs
  56. 44 0
      samples/Sample5_Resources/Sample5_Resources.csproj
  57. 27 0
      samples/Sample5_Resources/extension.json
  58. 25 0
      samples/Sample6_Palettes/ExamplePaletteDataSource.cs
  59. 27 0
      samples/Sample6_Palettes/PalettesSampleExtension.cs
  60. 14 0
      samples/Sample6_Palettes/Program.cs
  61. 43 0
      samples/Sample6_Palettes/Sample6_Palettes.csproj
  62. 27 0
      samples/Sample6_Palettes/extension.json
  63. 18 0
      samples/Sample7_FlyUI/FlyUISampleExtension.cs
  64. 9 0
      samples/Sample7_FlyUI/Program.cs
  65. BIN
      samples/Sample7_FlyUI/Resources/Pizza.png
  66. 43 0
      samples/Sample7_FlyUI/Sample7_FlyUI.csproj
  67. 69 0
      samples/Sample7_FlyUI/WindowContentElement.cs
  68. 27 0
      samples/Sample7_FlyUI/extension.json
  69. 55 0
      samples/Sample8_CommandLibrary/CommandLibraryExtension.cs
  70. 9 0
      samples/Sample8_CommandLibrary/Program.cs
  71. 39 0
      samples/Sample8_CommandLibrary/Sample8_CommandLibrary.csproj
  72. 41 0
      samples/Sample8_CommandLibrary/extension.json
  73. 75 0
      samples/Sample8_Commands/CommandsSampleExtension.cs
  74. 3 0
      samples/Sample8_Commands/Localization/en.json
  75. 3 0
      samples/Sample8_Commands/Localization/pl.json
  76. 9 0
      samples/Sample8_Commands/Program.cs
  77. 39 0
      samples/Sample8_Commands/Sample8_Commands.csproj
  78. 41 0
      samples/Sample8_Commands/extension.json
  79. 24 0
      samples/Sample9_Document/CommandsSampleExtension.cs
  80. 8 0
      samples/Sample9_Document/Program.cs
  81. BIN
      samples/Sample9_Document/Resources/cs.png
  82. 45 0
      samples/Sample9_Document/Sample9_Document.csproj
  83. 44 0
      samples/Sample9_Document/extension.json
  84. 7 0
      samples/global.json
  85. 20 0
      samples/stylecop.json
  86. 13 0
      src/.config/dotnet-tools.json
  87. 47 18
      src/ChunkyImageLib/Chunk.cs
  88. 19 14
      src/ChunkyImageLib/ChunkPool.cs
  89. 286 77
      src/ChunkyImageLib/ChunkyImage.cs
  90. 10 9
      src/ChunkyImageLib/ChunkyImageEx.cs
  91. 6 46
      src/ChunkyImageLib/ChunkyImageLib.csproj
  92. 12 1
      src/ChunkyImageLib/ColorEx.cs
  93. 5 4
      src/ChunkyImageLib/CommittedChunkStorage.cs
  94. 2 1
      src/ChunkyImageLib/DataHolders/AffectedArea.cs
  95. 20 5
      src/ChunkyImageLib/DataHolders/ColorBounds.cs
  96. 0 225
      src/ChunkyImageLib/DataHolders/ShapeCorners.cs
  97. 18 11
      src/ChunkyImageLib/DataHolders/ShapeData.cs
  98. 12 7
      src/ChunkyImageLib/IReadOnlyChunkyImage.cs
  99. 4 3
      src/ChunkyImageLib/Operations/ApplyMaskOperation.cs
  100. 11 10
      src/ChunkyImageLib/Operations/BresenhamLineHelper.cs

+ 39 - 0
.env-dev/run-build-bin.nix

@@ -0,0 +1,39 @@
+{ pkgs ? import <nixpkgs> {} }:
+
+
+(pkgs.buildFHSEnv {
+  name = "pixieditor-env";
+  targetPkgs = pkgs: (with pkgs; [
+    dotnet-sdk
+    avalonia
+    fontconfig
+    alsa-lib
+    glew
+    udev
+    gnumake 
+    vulkan-headers
+    vulkan-loader
+    vulkan-validation-layers
+    vulkan-tools
+    vulkan-tools-lunarg
+    powershell
+  ]) ++ (with pkgs.xorg; [
+   libX11
+    libICE
+    libSM
+    libXi
+    libXcursor
+    libXext
+    libXrandr  ]);
+
+  multiPkgs = pkgs: (with pkgs; [
+   udev
+   alsa-lib
+  ]);
+
+  runScript = "nohup ./PixiEditor &";
+}).env
+
+
+
+

+ 35 - 0
.env-dev/run-rider.nix

@@ -0,0 +1,35 @@
+{ pkgs ? import <nixpkgs> {} }:
+
+
+(pkgs.buildFHSEnv {
+  name = "rider-env";
+  targetPkgs = pkgs: (with pkgs; [
+    dotnet-sdk
+    avalonia
+    fontconfig
+    alsa-lib
+    glew
+    udev
+    gnumake 
+    vulkan-headers
+    vulkan-loader
+    vulkan-validation-layers
+    vulkan-tools
+    vulkan-tools-lunarg
+    powershell
+  ]) ++ (with pkgs.xorg; [
+   libX11
+    libICE
+    libSM
+    libXi
+    libXcursor
+    libXext
+    libXrandr  ]);
+
+  multiPkgs = pkgs: (with pkgs; [
+   udev
+   alsa-lib
+  ]);
+
+  runScript = "nohup rider &";
+}).env

+ 34 - 0
.env-dev/shell.nix

@@ -0,0 +1,34 @@
+{ pkgs ? import <nixpkgs> { } }:
+
+with pkgs;
+let
+
+dotnet = dotnet-sdk; 
+
+in mkShell {
+  name = "avalonia-env";
+  packages = (with pkgs; [
+    dotnet
+    avalonia
+    fontconfig
+    alsa-lib
+    glew
+    udev
+    gnumake 
+    vulkan-headers
+    vulkan-loader
+    vulkan-validation-layers
+    vulkan-tools
+    vulkan-tools-lunarg
+    powershell
+  ]) ++ (with pkgs.xorg; [
+   libX11
+    libICE
+    libSM
+    libXi
+    libXcursor
+    libXext
+    libXrandr  ]);
+
+    DOTNET_ROOT = "${dotnet}";
+}

+ 0 - 17
.github/ISSUE_TEMPLATE/feature_request.md

@@ -1,17 +0,0 @@
----
-name: Feature request
-about: Suggest an idea for this project
-
----
-
-**Is your feature request related to a problem? Please describe.**
-A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
-
-**Describe the solution you'd like**
-A clear and concise description of what you want to happen.
-
-**Describe alternatives you've considered**
-A clear and concise description of any alternative solutions or features you've considered.
-
-**Additional context**
-Add any other context or screenshots about the feature request here.

+ 33 - 0
.github/ISSUE_TEMPLATE/feature_request.yml

@@ -0,0 +1,33 @@
+name: Feature request
+description: Suggest an improvement or a feature
+title: "[FEATURE]: "
+labels: ["enhancement"]
+body:
+- type: textarea
+  id: featuredesc
+  attributes:
+    label: Feature Description
+    description: A general description of the feature
+  validations:
+    required: true
+- type: textarea
+  id: featuresteps
+  attributes:
+    label: Steps how to use this feature
+    description: A step by step list how to utilize this feature. How exactly do you imagine users use it and what result they can expect?
+    value: |
+      1.
+      2.
+      3.
+      ...
+    render: bash
+  validations:
+    required: false
+- type: checkboxes
+  id: terms
+  attributes:
+    label: Code of Conduct
+    description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/PixiEditor/PixiEditor/blob/master/CODE_OF_CONDUCT.md). 
+    options:
+      - label: I agree to follow this project's Code of Conduct
+        required: true

+ 0 - 35
.github/ISSUE_TEMPLATE/report_a_bug.md

@@ -1,35 +0,0 @@
----
-name: Report a bug
-about: Report a bug, not working feature or anything related
----
-
-**Describe as detailed as possible when it happens**
-
-A clear and concise description of what the problem is. Ex. Holding CTRL+Z and P at the same time, causes program to crash  
-
-**Add reproduction steps**
-
-If you are able to, include steps to reproduce bug
-
-Example:
-1. Create new file with size 64x64
-2. Draw line anywhere
-3. Center content
-4. PixiEditor crashes
-
-**Expected behaviour**
-
-What should happen?
-
-**Include related files,**
-
-If bug makes PixiEditor crash, include crash report. If it is possible, include screenshots and videos. 
-
-**System information**
-
-Windows version: 11/10/8/7
-PixiEditor version: (for example: 0.1.8.0)
-
-**Additional context**
-
-Add any other context here.

+ 104 - 0
.github/ISSUE_TEMPLATE/report_a_bug.yml

@@ -0,0 +1,104 @@
+---
+name: Bug Report
+description: Report a bug
+title: "[BUG]: "
+labels:
+  - bug
+body:
+  - type: markdown
+    attributes:
+      value: >
+        Thanks for taking the time to fill out this bug! Please remember to
+        incude all relevant files such as crash reports, logs and screenshots if
+        available.
+  - type: textarea
+    id: bugdesc
+    attributes:
+      label: Bug Description
+      description: A general description of the bug
+    validations:
+      required: true
+  - type: textarea
+    id: repro
+    attributes:
+      label: Steps how to trigger the issue
+      description: Walk us through how we can reproduce this bug. If you don't know
+        the exact steps, write down when it happened or any info that could help
+        us identify the issue.
+      value: |
+        1.
+        2.
+        3.
+        ...
+      render: bash
+    validations:
+      required: true
+  - type: dropdown
+    id: download
+    attributes:
+      label: How did you download the software?
+      options:
+        - PixiEditor Website
+        - Microsoft Store
+        - Steam
+        - GitHub
+        - Linux Package Manager
+        - App Store
+        - Built from source
+    validations:
+      required: true
+  - type: dropdown
+    id: version
+    attributes:
+      label: Version Channel
+      description: What channel of PixiEditor are you running?
+      options:
+        - Stable
+        - Development
+      default: 0
+    validations:
+      required: true
+  - type: input
+    id: versionNum
+    attributes:
+      label: Version Number
+      description: What version of PixiEditor are you running?
+      placeholder: 2.0.0.64
+    validations:
+      required: true
+  - type: dropdown
+    id: os
+    attributes:
+      label: Operating System
+      description: On what operating system did you encounter the bug?
+      options:
+        - Windows
+        - MacOS
+        - Linux
+      default: 0
+    validations:
+      required: true
+  - type: input
+    id: osVersion
+    attributes:
+      label: OS Version
+      description: Version of above operating system (ex. Windows 11, macOS Sonoma 14.7.3, Arch Linux). Please specify Linux Desktop Environment if applicable.
+      placeholder: "Windows 11"
+    validations:
+      required: true
+  - type: textarea
+    id: additional
+    attributes:
+      label: Additional Context
+      description: Additional context that could help us fix the issue.
+    validations:
+      required: false
+  - type: checkboxes
+    id: terms
+    attributes:
+      label: Code of Conduct
+      description: By submitting this issue, you agree to follow our [Code of
+        Conduct](https://github.com/PixiEditor/PixiEditor/blob/master/CODE_OF_CONDUCT.md).
+      options:
+        - label: I agree to follow this project's Code of Conduct
+          required: true

+ 89 - 0
.github/workflows/localization.yml

@@ -0,0 +1,89 @@
+name: Language Sync
+
+on:
+  workflow_dispatch:
+    inputs:
+      mode:
+        description: "Mode"
+        required: true
+        default: "Full Sync"
+        type: choice
+        options:
+        - Full Sync
+        - Download only
+        - Upload only
+
+permissions:
+  contents: write
+  pull-requests: write
+
+jobs:
+  compare-upload:
+    name: "Compare local English"
+    runs-on: ubuntu-latest
+    if: ${{ inputs.mode != 'Download only' }}
+    steps:
+      - name: Checkout
+        uses: actions/[email protected]
+      - name: Compare local reference
+        id: upload-comparison
+        run: python .github/workflows/localization/reference-comparison.py
+        env:
+          POEDITOR_API_KEY: ${{ secrets.POEDITORKEY }}
+          POEDITOR_PROJECT_ID: ${{ secrets.POEDITORPROJECT }}
+    outputs:
+      HAS_CHANGES: ${{ steps.upload-comparison.outputs.HAS_CHANGES }}
+
+  upload:
+    name: "Upload English changes"
+    runs-on: ubuntu-latest
+    needs: compare-upload
+    environment: poeditor
+    if: ${{ needs.compare-upload.outputs.HAS_CHANGES == 'true' }}
+    steps:
+      - name: Checkout
+        uses: actions/[email protected]
+      - name: Upload Changes
+        run: python .github/workflows/localization/upload-reference.py
+        env:
+          POEDITOR_API_KEY: ${{ secrets.POEDITORKEY }}
+          POEDITOR_PROJECT_ID: ${{ secrets.POEDITORPROJECT }}
+
+  download:
+    name: "Download changes"
+    runs-on: ubuntu-latest
+    if: ${{ inputs.mode != 'Upload only' }}
+    steps:
+      - name: Checkout
+        uses: actions/[email protected]
+      - name: Download languages
+        id: download-languages
+        run: python .github/workflows/localization/download-languages.py
+        env:
+          POEDITOR_API_KEY: ${{ secrets.POEDITORKEY }}
+          POEDITOR_PROJECT_ID: ${{ secrets.POEDITORPROJECT }}
+      - name: Create branch
+        if: ${{ steps.download-languages.outputs.HAS_CHANGES == 'true' }}
+        run: git checkout -B "language-update-${{ github.run_number }}-${{ github.run_attempt }}"
+      - name: Add changes and commit
+        if: ${{ steps.download-languages.outputs.HAS_CHANGES == 'true' }}
+        run: |
+          git config --global user.name "github-actions[bot]"
+          git config --global user.email "github-actions[bot]@users.noreply.github.com"
+          git add -A
+          git commit -m "Update language files"
+      - name: Push changes
+        if: ${{ steps.download-languages.outputs.HAS_CHANGES == 'true' }}
+        run: |
+          git push --set-upstream origin $(git rev-parse --abbrev-ref HEAD)
+      - name: Create Pull Request
+        if: ${{ steps.download-languages.outputs.HAS_CHANGES == 'true' }}
+        run: |
+          gh pr create --title "Language Update ${{ github.run_number }}" \
+                --body "This PR was created automatically.
+
+                https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs/${{ github.job }}" \
+                --base master \
+                --head $(git rev-parse --abbrev-ref HEAD)
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

+ 179 - 0
.github/workflows/localization/download-languages.py

@@ -0,0 +1,179 @@
+import json
+import os
+import requests
+from collections import OrderedDict
+from datetime import datetime
+
+API_KEY = os.getenv("POEDITOR_API_KEY")
+PROJECT_ID = os.getenv("POEDITOR_PROJECT_ID")
+GITHUB_OUTPUT = os.getenv('GITHUB_OUTPUT')
+
+# POEditor API URLs
+EXPORT_API_URL = "https://api.poeditor.com/v2/projects/export"
+LANGUAGES_LIST_URL = "https://api.poeditor.com/v2/languages/list"
+
+# Paths
+LOCALIZATION_DATA_PATH = "src/PixiEditor/Data/Localization/LocalizationData.json"
+LOCALES_DIR = "src/PixiEditor/Data/Localization/Languages/"
+
+def load_ordered_json(file_path):
+    """Load JSON preserving order (as an OrderedDict) and handle UTF-8 BOM."""
+    try:
+        with open(file_path, "r", encoding="utf-8-sig") as f:
+            return json.load(f, object_pairs_hook=OrderedDict)
+    except FileNotFoundError:
+        print(f"::error::File not found: {file_path}")
+        return OrderedDict()
+    except json.JSONDecodeError as e:
+        print(f"::error::Failed to parse JSON in {file_path}: {e}")
+        return OrderedDict()
+
+def write_ordered_json(file_path, data):
+    """Write an OrderedDict to a JSON file in UTF-8 (without BOM)."""
+    with open(file_path, "w", encoding="utf-8") as f:
+        json.dump(data, f, indent=2, ensure_ascii=False)
+
+def fetch_poeditor_language_json(language_code):
+    """
+    Fetch the latest key-value JSON for the given language from POEditor.
+    Returns a dictionary if successful, otherwise None.
+    """
+    if not API_KEY or not PROJECT_ID:
+        print("::error::Missing API_KEY or PROJECT_ID in environment variables.")
+        return None
+
+    response = requests.post(EXPORT_API_URL, data={
+        "api_token": API_KEY,
+        "id": PROJECT_ID,
+        "type": "key_value_json",
+        "language": language_code,
+        "filters": "translated"
+    })
+    if response.status_code == 200:
+        data = response.json()
+        if "result" in data and "url" in data["result"]:
+            download_url = data["result"]["url"]
+            remote_response = requests.get(download_url)
+            if remote_response.status_code == 200:
+                return remote_response.json()
+    print(f"::error::Failed to fetch POEditor data for language '{language_code}'")
+    return None
+
+def update_locale_file(language):
+    """
+    For a given language (dict from LocalizationData.json), update its locale file:
+      - Only keep keys that exist in the POEditor (remote) file.
+      - For keys present both locally and remotely, update with the remote value (preserving the original local order).
+      - Append new keys from POEditor at the bottom.
+    """
+    # Use "remote-code" if available, otherwise default to "code"
+    lang_code = language.get("remoteCode", language["code"])
+    if language["code"].lower() == "en":
+        return False # Skip English (do not update)
+
+    file_name = language["localeFileName"]
+    file_path = os.path.join(LOCALES_DIR, file_name)
+    local_data = load_ordered_json(file_path)
+    remote_data = fetch_poeditor_language_json(lang_code)
+    if remote_data is None:
+        print(f"::error::Skipping update for {language['name']} ({lang_code}) due to fetch error.")
+        return False
+
+    # Build new ordered data:
+    # 1. Start with keys from local file that exist in remote.
+    updated_data = OrderedDict()
+    for key in local_data:
+        if key in remote_data:
+            updated_data[key] = remote_data[key]
+    # 2. Append keys from remote that are missing locally.
+    for key in remote_data:
+        if key not in updated_data:
+            updated_data[key] = remote_data[key]
+
+    # Write file if changes exist (or if file was missing)
+    if updated_data != local_data:
+        write_ordered_json(file_path, updated_data)
+        print(f"✅ Updated locale file for {language['name']} ({language['code']}).")
+        return True
+    else:
+        print(f"✅ No changes for {language['name']} ({language['code']}).")
+        return False
+
+def fetch_languages_list():
+    """
+    Fetch the languages list from POEditor and return a mapping of language codes to
+    updated dates (formatted as "YYYY-MM-DD hh:MM:ss").
+    """
+    if not API_KEY or not PROJECT_ID:
+        print("::error::Missing API_KEY or PROJECT_ID in environment variables.")
+        return {}
+
+    response = requests.post(LANGUAGES_LIST_URL, data={
+        "api_token": API_KEY,
+        "id": PROJECT_ID
+    })
+    languages_updates = {}
+    if response.status_code == 200:
+        data = response.json()
+        if "result" in data and "languages" in data["result"]:
+            for lang in data["result"]["languages"]:
+                code = lang.get("code")
+                updated_iso = lang.get("updated")
+                if code and updated_iso:
+                    try:
+                        # Parse ISO8601 format (example: "2015-05-04T14:21:41+0000")
+                        dt = datetime.strptime(updated_iso, "%Y-%m-%dT%H:%M:%S%z")
+                        formatted = dt.strftime("%Y-%m-%d %H:%M:%S")
+                        languages_updates[code.lower()] = formatted
+                    except Exception as e:
+                        print(f"::error::Failed to parse date for language '{code}': {e}")
+    else:
+        print("::error::Failed to fetch languages list from POEditor.")
+    return languages_updates
+
+def update_localization_data(languages_updates):
+    """
+    Update the lastUpdated field for each language (except English) in LocalizationData.json.
+    """
+    localization_data = load_ordered_json(LOCALIZATION_DATA_PATH)
+    if "Languages" not in localization_data:
+        print("::error::'Languages' key not found in LocalizationData.json")
+        return
+
+    for language in localization_data["Languages"]:
+        code = language.get("code", "").lower()
+        if code == "en":
+            continue  # Do not update English
+        if code in languages_updates:
+            language["lastUpdated"] = languages_updates[code]
+    write_ordered_json(LOCALIZATION_DATA_PATH, localization_data)
+    print("✅ Updated LocalizationData.json with new lastUpdated values.")
+
+def main():
+    # Fetch updated dates for languages from POEditor
+    languages_updates = fetch_languages_list()
+
+    # Load LocalizationData.json and update each language file (except English)
+    localization_data = load_ordered_json(LOCALIZATION_DATA_PATH)
+    if "Languages" not in localization_data:
+        print("::error::'Languages' key not found in LocalizationData.json")
+        exit(1)
+        return
+    
+    has_changes = False
+
+    for language in localization_data["Languages"]:
+        if language.get("code", "").lower() == "en":
+            continue
+        if update_locale_file(language):
+            has_changes = True
+
+    with open(GITHUB_OUTPUT, "a") as f:
+        f.write(f"HAS_CHANGES={str(has_changes).lower()}")
+
+    # Update lastUpdated field in LocalizationData.json
+    update_localization_data(languages_updates)
+    print("🎉 All language updates complete.")
+
+if __name__ == "__main__":
+    main()

+ 96 - 0
.github/workflows/localization/reference-comparison.py

@@ -0,0 +1,96 @@
+import json
+import os
+import requests
+
+API_KEY = os.getenv("POEDITOR_API_KEY")
+PROJECT_ID = os.getenv("POEDITOR_PROJECT_ID")
+GITHUB_OUTPUT = os.getenv('GITHUB_OUTPUT')
+
+API_URL = "https://api.poeditor.com/v2/projects/export"
+LANGUAGE = "en"
+LOCAL_FILE_PATH = "src/PixiEditor/Data/Localization/Languages/en.json"
+
+def fetch_poeditor_json():
+    """Fetches the latest en.json from POEditor API (remote data)"""
+    if not API_KEY or not PROJECT_ID:
+        print("::error::Missing API_KEY or PROJECT_ID in environment variables.")
+        return None
+
+    response = requests.post(API_URL, data={
+        "api_token": API_KEY,
+        "id": PROJECT_ID,
+        "type": "key_value_json",
+        "language": LANGUAGE
+    })
+
+    if response.status_code == 200:
+        data = response.json()
+        if "result" in data and "url" in data["result"]:
+            download_url = data["result"]["url"]
+            latest_response = requests.get(download_url)
+            return latest_response.json() if latest_response.status_code == 200 else None
+    return None
+
+def load_local_json():
+    """Loads the local en.json file (authoritative source)"""
+    try:
+        with open(LOCAL_FILE_PATH, "r", encoding="utf-8") as file:
+            return json.load(file)
+    except FileNotFoundError:
+        print("::error::Local en.json file not found!")
+        return {}
+
+def compare_json(local_data, remote_data):
+    """Compares the local and remote JSON data, detecting added, removed, and modified keys"""
+    modifications = []
+    additions = []
+    deletions = []
+    
+    # Check for modified keys (key exists in both, but value changed)
+    for key, local_value in local_data.items():
+        remote_value = remote_data.get(key)
+        if remote_value is not None and local_value != remote_value:
+            modifications.append(f"🔄 {key}: '{remote_value}' → '{local_value}'")
+
+    # Check for added keys (exist in local but missing in POEditor)
+    for key in local_data.keys() - remote_data.keys():
+        additions.append(f"➕ {key}: '{local_data[key]}'")
+
+    # Check for removed keys (exist in POEditor but missing locally)
+    for key in remote_data.keys() - local_data.keys():
+        deletions.append(f"❌ {key}: '{remote_data[key]}'")
+
+    return modifications, additions, deletions
+
+def print_group(title, items):
+    """Prints grouped items using GitHub Actions logging format"""
+    if items:
+        print(f"::group::{len(items)} {title}")
+        for item in items:
+            print(item)
+        print("::endgroup::")
+
+def main():
+    remote_json = fetch_poeditor_json()
+    if remote_json is None:
+        print("::error::Failed to fetch POEditor en.json")
+        exit(1)
+        return
+    
+    local_json = load_local_json()
+    
+    modifications, additions, deletions = compare_json(local_json, remote_json)
+    has_changes = (modifications or additions or deletions)
+
+    with open(GITHUB_OUTPUT, "a") as f:
+        if not has_changes:
+            f.write(f"HAS_CHANGES=false")
+            print("✅ No changes detected. Local and remote are in sync.")
+        else:
+            f.write(f"HAS_CHANGES=true")
+            print_group("Key(s) Modified", modifications)
+            print_group("Key(s) to be Added", additions)
+            print_group("Key(s) to be Removed", deletions)
+
+if __name__ == "__main__":
+    main()

+ 50 - 0
.github/workflows/localization/upload-reference.py

@@ -0,0 +1,50 @@
+import os
+import requests
+
+# Configuration
+API_KEY = os.getenv("POEDITOR_API_KEY")
+PROJECT_ID = os.getenv("POEDITOR_PROJECT_ID")
+UPLOAD_API_URL = "https://api.poeditor.com/v2/projects/upload"
+LOCAL_FILE_PATH = "src/PixiEditor/Data/Localization/Languages/en.json"
+
+def upload_en_json():
+    if not API_KEY or not PROJECT_ID:
+        print("::error::Missing POEDITOR_API_KEY or POEDITOR_PROJECT_ID environment variables.")
+        exit(1)
+        return
+
+    try:
+        with open(LOCAL_FILE_PATH, "rb") as file:
+            files = {
+                "file": ("en.json", file, "application/json")
+            }
+            data = {
+                "api_token": API_KEY,
+                "id": PROJECT_ID,
+                "updating": "terms_translations",  # Updates both terms and translations.
+                "language": "en",                  # Specify language as English.
+                "overwrite": 1,                    # Overwrite existing terms/translations.
+                "sync_terms": 1,                   # Sync terms: delete terms not in the uploaded file.
+                "fuzzy_trigger": 1                 # Mark translations in other languages as fuzzy.
+            }
+            response = requests.post(UPLOAD_API_URL, data=data, files=files)
+    except FileNotFoundError:
+        print(f"::error::Local file not found: {LOCAL_FILE_PATH}")
+        return
+
+    if response.status_code == 200:
+        result = response.json()
+        if result.get("response", {}).get("status") == "success":
+            print("✅ Upload succeeded:")
+            print(result)
+        else:
+            print("::error::Upload failed:")
+            print(result)
+            exit(1)
+    else:
+        print("::error::HTTP Error:", response.status_code)
+        print(response.text)
+        exit(1)
+
+if __name__ == "__main__":
+    upload_en_json()

+ 66 - 0
.github/workflows/tests-windows.yml

@@ -0,0 +1,66 @@
+name: Tests Windows
+
+on:
+  push:
+    branches: [ "master" ]
+  pull_request:
+    branches: [ "master" ]
+
+env:
+  wasiVer: 'wasi-sdk-25.0-x86_64-windows'
+  wasiUrl: 'https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-windows.tar.gz'
+
+jobs:
+
+  build:
+
+    strategy:
+      matrix:
+        configuration: [Release]
+
+    runs-on: windows-latest  # For a list of available runner types, refer to
+                             # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
+
+    steps:
+    - name: Checkout
+      uses: actions/checkout@v4
+      with:
+        fetch-depth: 0
+        submodules: 'recursive'
+
+    # Install the .NET Core workload
+    - name: Install .NET Core
+      uses: actions/setup-dotnet@v4
+      with:
+        dotnet-version: 8.0.x
+
+    - name: Install wasi-experimental workload
+      working-directory: tests
+      run: dotnet workload install wasi-experimental
+
+    - name: Download WASI SDK
+      run: |
+        Invoke-WebRequest -Uri "${{ env.wasiUrl }}" -OutFile "${{ env.wasiVer }}.tar.gz"
+
+    - name: Unpack WASI SDK
+      run: |
+              tar -xzf ${{ env.wasiVer }}.tar.gz
+              echo "Contents of directory after extraction:"
+              dir "${{ env.wasiVer }}"
+      shell: pwsh
+
+    - name: Set Environment Path for WASI SDK
+      run: |
+              $env:WASI_SDK_PATH = "${{ github.workspace }}\${{ env.wasiVer }}"
+              echo "WASI_SDK_PATH=$env:WASI_SDK_PATH" >> $env:GITHUB_ENV
+      shell: pwsh
+
+    - name: Verify Environment Path
+      run: |
+              Write-Host "Environment path set to: $env:WASI_SDK_PATH"
+      shell: pwsh
+
+    # Execute all unit tests in the solution
+    - name: Execute unit tests
+      working-directory: tests
+      run: dotnet test

+ 8 - 2
.gitignore

@@ -85,6 +85,8 @@ StyleCopReport.xml
 *.svclog
 *.scc
 
+.packages
+
 # Chutzpah Test files
 _Chutzpah*
 
@@ -329,9 +331,13 @@ ASALocalRun/
 # MFractors (Xamarin productivity tool) working folder 
 .mfractor/
 
-PixiEditor/Properties/
-
 .vscode/
 
 Builds/
 Installer/Assets
+
+GitIgnore
+
+Cache/
+.DS_Store
+nohup.out

+ 12 - 0
.gitmodules

@@ -0,0 +1,12 @@
+[submodule "src/PixiDocks"]
+	path = src/PixiDocks
+	url = https://github.com/PixiEditor/PixiDocks.git
+[submodule "src/PixiParser"]
+	path = src/PixiParser
+	url = https://github.com/PixiEditor/PixiParser.git
+[submodule "src/Drawie"]
+	path = src/Drawie
+	url = https://github.com/PixiEditor/Drawie.git
+[submodule "src/ColorPicker"]
+	path = src/ColorPicker
+	url = https://github.com/PixiEditor/ColorPicker.git

+ 128 - 0
CODE_OF_CONDUCT.md

@@ -0,0 +1,128 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+  and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+  overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+  advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+  address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+  professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
[email protected].
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior,  harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.

+ 19 - 12
CONTRIBUTING.md

@@ -1,21 +1,28 @@
 # Contributing
 
-Hey! Thanks for being interested in the project! It means a lot. But, before contributing please read this guide :) 
+Hey! Thanks for being interested in contributing to the PixiEditor! Please read below guide before continuing.
 
-When contributing to this repository, please first discuss the change you wish to make via issue,
-email, or any other method with the owners of this repository before making a change. 
+## Overview
 
-## Issues
+PixiEditor consists of a few sub-projects:
 
-If you want to report a bug, follow the steps below, if you want to request a feature, check [this](https://github.com/flabbet/PixiEditor/blob/master/.github/ISSUE_TEMPLATE/feature_request.md)
+- [ColorPicker](https://github.com/PixiEditor/ColorPicker) - UI component for picking colors and gradients.
+- [Drawie](https://github.com/PixiEditor/Drawie) - Core rendering library
+- [PixiDocks](https://github.com/PixiEditor/PixiDocks) - Docking and layout manager for Avalonia
+- [PixiParser](https://github.com/PixiEditor/PixiParser) - Parser for .pixi files
 
-* First of all, check if the issue is on the [list](https://github.com/flabbet/PixiEditor/issues) and/or [board](https://github.com/flabbet/PixiEditor/projects), if yes, upvote it.
+Before making any changes to files that are part of above, remember that these are standalone, PixiEditor independed libraries and shouldn't contain code strictly related to PixiEditor codebase.
 
-* If not, report an issue [here](https://github.com/flabbet/PixiEditor/issues) while following these guidelines:
- 1. Keep the title short and straightforward.
- 2. Describe the issue as detailed as possible
- 3. Include screenshots if you can.
+## Where to start?
 
- ## Pull Requests
+PixiEditor codebase is big, it might be a little intimidating to start. Make sure to start with little tasks first and get slowly familiar with it. 
+The very first place to look at is [Issues](https://github.com/PixiEditor/PixiEditor/issues), find issues with `good first issue` labels. Each issue should contain a description or comments with tips on what files to look at.
 
- Before submitting a pull request, read [this](https://github.com/flabbet/PixiEditor/blob/master/PULL_REQUEST_TEMPLATE.md)
+And if you're stuck, don't hesitate to join our [Discord](https://discord.gg/qSRMYmq). We are more than happy to guide you and help.
+
+It is also a good practice to comment on the issue that you want to work on it, we'll assign the issue to you. Generally, you should take the issues with `up-for-grabs` label, it basically means that core development team doesn't plan to work on them anytime soon. If the issue is assigned
+to someone, ask in the comments if the person is actively working on it, and if not you might be assigned.
+
+## **Do not make undiscussed features!**
+
+It is very important to discuss any new features with PixiEditor's development team. This is just to ensure that your work and our time won't be wasted, it is perfectly sufficient to create an issue on GitHub or even ask on [Discord](https://discord.gg/qSRMYmq) or [Forum](https://forum.pixieditor.net) with brief description of what you want to do.

+ 10 - 6
PULL_REQUEST_TEMPLATE.md

@@ -1,8 +1,12 @@
- ## Pull Requests
+ ## Clearly describe changes, as detailed as possible
 
- Pull request rules:
+_Include the link to the related issues if relevant_ 
 
- 1. Clearly describe changes, as detailed as possible
- 2. If possible, show examples of usage
- 3. Ensure any install or build dependencies are removed before the end of the layer when doing a build.
- 4. Make sure all tests are passing.
+ ## If possible, show examples of usage
+
+_a video, screenshots, text description_
+
+## SELECT BELOW TO CONTINUE
+- [ ] I wrote tests for my changes (if possible)
+- [ ] I've included XML docs inside the code in relevant places
+- [ ] I agree to [PixiEditor's Code of Conduct](https://github.com/PixiEditor/PixiEditor/blob/master/CODE_OF_CONDUCT.md)

+ 61 - 23
README.md

@@ -1,39 +1,58 @@
-<img src="https://user-images.githubusercontent.com/25402427/102633463-c1d3bb80-4150-11eb-8262-535e568fa781.png" width="700">
+<img src="https://github.com/user-attachments/assets/bd08c8bd-f610-449d-b1e2-6a990e562518">
 
----
 
-**PixiEditor** is a Pixel art editing software. Create beautiful sprites for your games, animations (coming soon!), and edit images. All packed in eye-friendly dark theme.
+**PixiEditor** is a universal 2D editor that aims to provide you with tools and features for all your 2D needs. Create beautiful sprites for your games, animations, edit images, create logos. All packed in an eye-friendly dark theme.     
+
 
-[![Build Status](https://img.shields.io/azure-devops/build/flabbet/PixiEditor/6/master)](https://dev.azure.com/flabbet/PixiEditor/_build?definitionId=6) 
-[![CodeFactor](https://www.codefactor.io/repository/github/pixieditor/pixieditor/badge)](https://www.codefactor.io/repository/github/pixieditor/pixieditor)
 [![Release](https://img.shields.io/github/v/release/flabbet/PixiEditor)](https://github.com/flabbet/PixiEditor/releases) 
 [![Downloads](https://img.shields.io/github/downloads/PixiEditor/PixiEditor/total)](https://github.com/flabbet/PixiEditor/releases)
 [![Discord Server](https://badgen.net/badge/discord/join%20chat/7289DA?icon=discord)](https://discord.gg/qSRMYmq)
 [![Subreddit subscribers](https://img.shields.io/reddit/subreddit-subscribers/PixiEditor?label=%20r%2FPixiEditor&logoColor=%23e3002d)](https://reddit.com/r/PixiEditor)
-[![contributions](https://img.shields.io/badge/contributions-open-brightgreen)](https://github.com/flabbet/PixiEditor/pulls)
+[![Forum](https://img.shields.io/badge/PixiEditor-Forum-red?link=https%3A%2F%2Fforum.pixieditor.net%2F)](https://forum.pixieditor.net/)
 
-### Check out our website [pixieditor.net](https://pixieditor.net)
+### Check out our website [pixieditor.net](https://pixieditor.net) and [PixiEditor Forum](https://forum.pixieditor.net/)
 
 ## About PixiEditor
 
-Want to create beautiful pixel art for your games? PixiEditor can help you! Our goal is to create a fully open-source, fast, and feature-rich pixel art creator. 
+PixiEditor aims to be all-in-one solution for 2D image editing, we want to achieve this by building a solid foundation with built-in tools for editing raster and vector graphics, procedural artworks, animations and more. To fully customize PixiEditor for all of your 2D needs, we built advanced Node Graph rendering, that allows for creating basically anything. From tiled texturing workspace, procedural animations that wouldn't be possible to make by hand, to even rendering 3D shapes.
+
+The project started as a pixel-art editor, but quickly evolved into something much more complex. Version 1.0 was downloaded over 100 000 times on all platforms and received 93% positive rating on Steam.
 
 ### Familiar interface
 
 Have you ever used Photoshop or Gimp? Reinventing the wheel is unnecessary, we wanted users to get familiar with the tool quickly and with ease. 
 
-![](https://user-images.githubusercontent.com/45312141/235351211-e00bcaea-9c63-4ecd-a2ee-e4fb2b2c9651.png)
+![](https://opencollective-production.s3.us-west-1.amazonaws.com/account-long-description/d2e269a7-8ded-4e0a-a723-c014730dba1c/PixiEditor_6OoxS5PGVD.png)
+
+### Toolsets for any scenario
+
+PixiEditor 2.0 comes by default with multiple toolsets: 
+- Pixel art - it contains tool suited for pixel-perfect scenarios
+- Painting - Basic painting tools, soft brushes, anti aliased shapes
+- Vector - Shapes and paths for creating vectors
+
+All toolsets can be used on one canvas, mix vector with raster. Export to png, jpg, svg, gif, mp4 and more!
+
+![](https://github.com/user-attachments/assets/605c901a-24aa-4c91-9ef9-0fa44878b614)
+
+### Animations
+
+Version 2.0 comes with Timeline and animation capabilities. You can create frame by frame animations or use nodes to animate your custom shaders.
+Key frame animations with vectors are planned.
 
-### Fast
+![PixiEditor_YdWFRnYxfb](https://github.com/user-attachments/assets/8fba0c6c-35c8-4ccb-9d69-d6beaff5d97f)
 
-PixiEditor is fast, drawing feels smooth on any canvas size, we've developed original chunk-based system and adaptive rendering to minimize pixel processing time.
+### Nodes
 
-### Active development
+Node render system is what powers such extensive capabilities. All layers, effects, layer structure are nodes or a result of node connections. PixiEditor exposes node graph for every document, so you are free to customize your image however you want and create procedural art/animations!
 
-PixiEditor started in 2018 and it's been actively developed since. We continuously improve code quality to ensure the best experience and performance.
+Here are some examples of what you can do with custom nodes https://pixieditor.net/blog/2024/08/16/devlog7#madeinpixieditor20
 
+## Installation - PixiEditor 2.0
 
-## Installation
+Currently version 2.0 is in open beta, follow this guide to install it https://pixieditor.net/docs/open-beta
+
+## Installation PixiEditor 1.0 - Pixel Art Editor
 
 <a href='//www.microsoft.com/store/apps/9NDDRHS8PBRN?cid=storebadge&ocid=badge'><img src='https://developer.microsoft.com/store/badges/images/English_get-it-from-MS.png' alt='Microsoft Store badge' width="184"/></a>
 
@@ -50,7 +69,6 @@ Follow these instructions to get PixiEditor working on your machine.
 3. Launch it
 4. Follow the steps in the installer to finish the installation
 
-
 ## Featured content
 
 ### PixiEditor 1.0 Trailer
@@ -70,31 +88,51 @@ Check out some pixel arts made with PixiEditor [here](https://github.com/PixiEdi
 
 Struggling with something? You can find support in a few places:
 
-* Check out [documentation](https://github.com/flabbet/PixiEditor/wiki)
+* Check out [documentation](https://pixieditor.net/docs)
 
 * Ask on [Discord](https://discord.gg/qSRMYmq)
+* Check out [Forum](https://forum.pixieditor.net)
 * Open new [Issue](https://github.com/flabbet/PixiEditor/issues)
-* Check out the [FAQ](https://github.com/PixiEditor/PixiEditor/wiki/FAQ). 
-
+* [Get help](https://pixieditor.net/help)
 
 
 ## Building from source
 
 ### Software Requirements
 
-* .NET 7
+* .NET 8 SDK
+* [wasi-sdk](https://github.com/WebAssembly/wasi-sdk) - PixiEditor uses WASI modules for extensions
 
 ### Instructions
 
-1. Clone Repository
+1. Clone Repository with nested submodules
+
+`git clone --recurse-submodules -j8 https://github.com/PixiEditor/PixiEditor.git`
+
+or if cloned already, init submodules with
+
+```
+cd PixiEditor
+```
+```
+git submodule update --init --recursive
+```
+
+2. Download [Wasi-sdk](https://github.com/WebAssembly/wasi-sdk/releases) release for your system
+3. Extract downloaded sdk 
+4. Set `WASI_SDK_PATH` enviroment variable to extracted directory
+5. Run 
+```
+dotnet workload install wasi-experimental
+```
 
-2. Open PixiEditor/src/PixiEditor/PixiEditor.sln in Visual Studio
+7. Open PixiEditor/src/PixiEditor.sln in Visual Studio or other IDE of your choice
 
-3. Build solution
+8. Build solution and run PixiEditor.Desktop project
 
 ## Contributing 
 
-Please read [CONTRIBUTING.md](https://github.com/flabbet/PixiEditor/blob/master/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
+Start with [Contributing Guide](https://github.com/PixiEditor/PixiEditor/blob/master/CONTRIBUTING.md)
 
 ## License
 

+ 1214 - 0
Third Party Licenses/ThirdPartyLicenses.txt

@@ -0,0 +1,1214 @@
+License notice for Avalonia.Desktop (v11.3.0)
+------------------------------------
+
+https://github.com/AvaloniaUI/Avalonia/ at d6edb46ce04f983892a61d3abf906014d3f5ec8d
+
+https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link
+
+Copyright 2013-2025 © The AvaloniaUI Project
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Diagnostics (v11.3.0)
+------------------------------------
+
+https://github.com/AvaloniaUI/Avalonia/ at d6edb46ce04f983892a61d3abf906014d3f5ec8d
+
+https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link
+
+Copyright 2013-2025 © The AvaloniaUI Project
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Microsoft.CodeAnalysis.CSharp (v4.11.0)
+------------------------------------
+
+https://github.com/dotnet/roslyn at 5e3a11e2e7f952da93f9d35bd63a2fa181c0608b
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for AsyncImageLoader.Avalonia (v3.3.0)
+------------------------------------
+
+https://github.com/AvaloniaUtils/AsyncImageLoader.Avalonia.git at 00edb42cdbf008f87f42c49d6f57034171bff02f
+
+https://github.com/AvaloniaUtils/AsyncImageLoader.Avalonia
+
+License available at https://github.com/AvaloniaUtils/AsyncImageLoader.Avalonia/blob/master/LICENSE
+
+
+License notice for Avalonia (v11.3.0)
+------------------------------------
+
+https://github.com/AvaloniaUI/Avalonia/ at d6edb46ce04f983892a61d3abf906014d3f5ec8d
+
+https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link
+
+Copyright 2013-2025 © The AvaloniaUI Project
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Angle.Windows.Natives (v2.1.22045.20230930)
+------------------------------------
+
+https://github.com/AvaloniaUI/angle/ at 26406952a84f41c4be1e762884cda61fd2e022a9
+
+https://avaloniaui.net/
+
+Copyright 2013-2023 © The AvaloniaUI Project
+
+Available at https://aka.ms/deprecateLicenseUrl
+
+LICENSE
+
+
+License notice for Avalonia.BuildServices (v0.0.31)
+------------------------------------
+
+https://avaloniaui.net/
+
+Copyright 2023-2024 © The AvaloniaUI Project
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Controls.ColorPicker (v11.3.0)
+------------------------------------
+
+https://github.com/AvaloniaUI/Avalonia/ at d6edb46ce04f983892a61d3abf906014d3f5ec8d
+
+https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link
+
+Copyright 2013-2025 © The AvaloniaUI Project
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.FreeDesktop (v11.3.0)
+------------------------------------
+
+https://github.com/AvaloniaUI/Avalonia/ at d6edb46ce04f983892a61d3abf906014d3f5ec8d
+
+https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link
+
+Copyright 2013-2025 © The AvaloniaUI Project
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Headless (v11.3.0)
+------------------------------------
+
+https://github.com/AvaloniaUI/Avalonia/ at d6edb46ce04f983892a61d3abf906014d3f5ec8d
+
+https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link
+
+Copyright 2013-2025 © The AvaloniaUI Project
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Labs.Lottie (v11.2.0)
+------------------------------------
+
+https://github.com/AvaloniaUI/Avalonia.Labs/ at 440ea074e34ee536691f9c9219afcac1835e750b
+
+https://avaloniaui.net/
+
+Copyright 2013-2024 © The AvaloniaUI Project
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Native (v11.3.0)
+------------------------------------
+
+https://github.com/AvaloniaUI/Avalonia/ at d6edb46ce04f983892a61d3abf906014d3f5ec8d
+
+https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link
+
+Copyright 2013-2025 © The AvaloniaUI Project
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Remote.Protocol (v11.3.0)
+------------------------------------
+
+https://github.com/AvaloniaUI/Avalonia/ at d6edb46ce04f983892a61d3abf906014d3f5ec8d
+
+https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link
+
+Copyright 2013-2025 © The AvaloniaUI Project
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Skia (v11.3.0)
+------------------------------------
+
+https://github.com/AvaloniaUI/Avalonia/ at d6edb46ce04f983892a61d3abf906014d3f5ec8d
+
+https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link
+
+Copyright 2013-2025 © The AvaloniaUI Project
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Svg.Skia (v11.2.0.2)
+------------------------------------
+
+https://github.com/wieslawsoltes/Svg.Skia at e958b025fb2a856b8b1d17f772329d014b92858a
+
+Copyright © Wiesław Šoltés 2024
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Themes.Fluent (v11.3.0)
+------------------------------------
+
+https://github.com/AvaloniaUI/Avalonia/ at d6edb46ce04f983892a61d3abf906014d3f5ec8d
+
+https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link
+
+Copyright 2013-2025 © The AvaloniaUI Project
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Themes.Simple (v11.3.0)
+------------------------------------
+
+https://github.com/AvaloniaUI/Avalonia/ at d6edb46ce04f983892a61d3abf906014d3f5ec8d
+
+https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link
+
+Copyright 2013-2025 © The AvaloniaUI Project
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Win32 (v11.3.0)
+------------------------------------
+
+https://github.com/AvaloniaUI/Avalonia/ at d6edb46ce04f983892a61d3abf906014d3f5ec8d
+
+https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link
+
+Copyright 2013-2025 © The AvaloniaUI Project
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.X11 (v11.3.0)
+------------------------------------
+
+https://github.com/AvaloniaUI/Avalonia/ at d6edb46ce04f983892a61d3abf906014d3f5ec8d
+
+https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link
+
+Copyright 2013-2025 © The AvaloniaUI Project
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Xaml.Behaviors (v11.2.0.14)
+------------------------------------
+
+https://github.com/wieslawsoltes/Xaml.Behaviors at 0c303a6909ff6b9c8e95b1ad134108892682bb47
+
+Copyright © Wiesław Šoltés 2025
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Xaml.Interactions (v11.2.0.14)
+------------------------------------
+
+https://github.com/wieslawsoltes/Xaml.Behaviors at 0c303a6909ff6b9c8e95b1ad134108892682bb47
+
+Copyright © Wiesław Šoltés 2025
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Xaml.Interactions.Custom (v11.2.0.14)
+------------------------------------
+
+https://github.com/wieslawsoltes/Xaml.Behaviors at 0c303a6909ff6b9c8e95b1ad134108892682bb47
+
+Copyright © Wiesław Šoltés 2025
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Xaml.Interactions.DragAndDrop (v11.2.0.14)
+------------------------------------
+
+https://github.com/wieslawsoltes/Xaml.Behaviors at 0c303a6909ff6b9c8e95b1ad134108892682bb47
+
+Copyright © Wiesław Šoltés 2025
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Xaml.Interactions.Draggable (v11.2.0.14)
+------------------------------------
+
+https://github.com/wieslawsoltes/Xaml.Behaviors at 0c303a6909ff6b9c8e95b1ad134108892682bb47
+
+Copyright © Wiesław Šoltés 2025
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Xaml.Interactions.Events (v11.2.0.14)
+------------------------------------
+
+https://github.com/wieslawsoltes/Xaml.Behaviors at 0c303a6909ff6b9c8e95b1ad134108892682bb47
+
+Copyright © Wiesław Šoltés 2025
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Xaml.Interactions.Responsive (v11.2.0.14)
+------------------------------------
+
+https://github.com/wieslawsoltes/Xaml.Behaviors at 0c303a6909ff6b9c8e95b1ad134108892682bb47
+
+Copyright © Wiesław Šoltés 2025
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Avalonia.Xaml.Interactivity (v11.2.0.14)
+------------------------------------
+
+https://github.com/wieslawsoltes/Xaml.Behaviors at 0c303a6909ff6b9c8e95b1ad134108892682bb47
+
+Copyright © Wiesław Šoltés 2025
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for BmpSharp (v0.2.0)
+------------------------------------
+
+https://github.com/dsoronda/bmp-sharp
+
+Copyright (c) Dražen Šoronda
+
+Available at https://aka.ms/deprecateLicenseUrl
+
+LICENSE.txt
+
+
+License notice for ByteSize (v2.1.2)
+------------------------------------
+
+https://github.com/omar/ByteSize at 28f1bd5aa265c4a436e4091cf85fc7ad76bd5083
+
+Copyright © Omar Khudeira 2013-2022
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for CLSEncoderDecoder (v1.0.0)
+------------------------------------
+
+https://github.com/Equbuxu/CLSEncoderDecoder
+
+Available at https://aka.ms/deprecateLicenseUrl
+
+LICENSE
+
+
+License notice for CommunityToolkit.HighPerformance (v8.3.2)
+------------------------------------
+
+https://github.com/CommunityToolkit/dotnet at 5320d4f621e145c60ef4180ea66fe57f12f0f58a
+
+(c) .NET Foundation and Contributors. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for CommunityToolkit.Mvvm (v8.3.2)
+------------------------------------
+
+https://github.com/CommunityToolkit/dotnet at 5320d4f621e145c60ef4180ea66fe57f12f0f58a
+
+(c) .NET Foundation and Contributors. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for DiscordRichPresence (v1.2.1.24)
+------------------------------------
+
+https://github.com/Lachee/discord-rpc-csharp
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for ExCSS (v4.2.3)
+------------------------------------
+
+https://github.com/TylerBrinks/ExCSS
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for FFMpegCore (v5.1.0)
+------------------------------------
+
+https://github.com/rosenbjerg/FFMpegCore
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Hardware.Info (v101.0.0)
+------------------------------------
+
+https://github.com/Jinjinov/Hardware.Info at cb4a549bb47870a90c8d3d6c92b6f80d91992764
+
+Copyright (c) Jinjinov 2022
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for HarfBuzzSharp (v7.3.0.3)
+------------------------------------
+
+https://github.com/mono/SkiaSharp at 9699cdf046a989423a5e5eca68e7fd813486d81c
+
+https://go.microsoft.com/fwlink/?linkid=868515
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for HarfBuzzSharp.NativeAssets.Linux (v7.3.0.3)
+------------------------------------
+
+https://github.com/mono/SkiaSharp at 9699cdf046a989423a5e5eca68e7fd813486d81c
+
+https://go.microsoft.com/fwlink/?linkid=868515
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for HarfBuzzSharp.NativeAssets.macOS (v7.3.0.3)
+------------------------------------
+
+https://github.com/mono/SkiaSharp at 9699cdf046a989423a5e5eca68e7fd813486d81c
+
+https://go.microsoft.com/fwlink/?linkid=868515
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for HarfBuzzSharp.NativeAssets.WebAssembly (v7.3.0.3)
+------------------------------------
+
+https://github.com/mono/SkiaSharp at 9699cdf046a989423a5e5eca68e7fd813486d81c
+
+https://go.microsoft.com/fwlink/?linkid=868515
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for HarfBuzzSharp.NativeAssets.Win32 (v7.3.0.3)
+------------------------------------
+
+https://github.com/mono/SkiaSharp at 9699cdf046a989423a5e5eca68e7fd813486d81c
+
+https://go.microsoft.com/fwlink/?linkid=868515
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Instances (v3.0.0)
+------------------------------------
+
+https://github.com/rosenbjerg/Instances
+
+Rosenbjerg Softworks
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for MessagePack (v2.5.192)
+------------------------------------
+
+https://github.com/MessagePack-CSharp/MessagePack-CSharp at d3d435b96cf09b9b0afc313bdea4257de6481c9f
+
+https://github.com/neuecc/MessagePack-CSharp
+
+Copyright © Yoshifumi Kawai and contributors. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for MessagePack.Annotations (v2.5.192)
+------------------------------------
+
+https://github.com/MessagePack-CSharp/MessagePack-CSharp at d3d435b96cf09b9b0afc313bdea4257de6481c9f
+
+https://github.com/neuecc/MessagePack-CSharp
+
+Copyright © Yoshifumi Kawai and contributors. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for MicroCom.Runtime (v0.11.0)
+------------------------------------
+
+https://github.com/kekekeks/MicroCom
+
+Copyright 2021 © Nikita Tsukanov
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Microsoft.CodeAnalysis.Common (v4.11.0)
+------------------------------------
+
+https://github.com/dotnet/roslyn at 5e3a11e2e7f952da93f9d35bd63a2fa181c0608b
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Microsoft.CSharp (v4.7.0)
+------------------------------------
+
+https://github.com/dotnet/corefx
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Microsoft.DotNet.PlatformAbstractions (v3.1.6)
+------------------------------------
+
+git://github.com/dotnet/core-setup at 3acd9b0cd16596bad450c82be08780875a73c05c
+
+https://dot.net/
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Available at https://aka.ms/deprecateLicenseUrl
+
+LICENSE.TXT
+
+
+License notice for Microsoft.Extensions.DependencyInjection (v9.0.0)
+------------------------------------
+
+https://github.com/dotnet/runtime at 9d5a6a9aa463d6d10b0b0ba6d5982cc82f363dc3
+
+https://dot.net/
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Microsoft.Extensions.DependencyInjection.Abstractions (v9.0.0)
+------------------------------------
+
+https://github.com/dotnet/runtime at 9d5a6a9aa463d6d10b0b0ba6d5982cc82f363dc3
+
+https://dot.net/
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Microsoft.Extensions.DependencyModel (v8.0.0)
+------------------------------------
+
+https://github.com/dotnet/runtime at 5535e31a712343a63f5d7d796cd874e563e5ac14
+
+https://dot.net/
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Microsoft.NET.StringTools (v17.6.3)
+------------------------------------
+
+https://github.com/dotnet/msbuild at 07e2947214f1a9f28a3517762c939d5bebb5f525
+
+http://go.microsoft.com/fwlink/?LinkId=624683
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Microsoft.NETCore.Platforms (v5.0.0)
+------------------------------------
+
+git://github.com/dotnet/runtime at cf258a14b70ad9069470a108f13765e0e5988f51
+
+https://github.com/dotnet/runtime
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Microsoft.Win32.Registry (v5.0.0)
+------------------------------------
+
+git://github.com/dotnet/runtime at cf258a14b70ad9069470a108f13765e0e5988f51
+
+https://github.com/dotnet/runtime
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Newtonsoft.Json (v13.0.3)
+------------------------------------
+
+https://github.com/JamesNK/Newtonsoft.Json at 0a2e291c0d9c0c7675d445703e51750363a549ef
+
+https://www.newtonsoft.com/json
+
+Copyright © James Newton-King 2008
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for OneOf (v3.0.271)
+------------------------------------
+
+https://github.com/mcintyre321/OneOf.git at 6e02dbe75d0f20f198c640d4c04190a85c5ac9e6
+
+https://github.com/mcintyre321/OneOf/
+
+Harry McIntyre
+
+License available at https://github.com/mcintyre321/OneOf/blob/master/licence.md
+
+
+License notice for protobuf-net (v3.2.52)
+------------------------------------
+
+https://github.com/protobuf-net/protobuf-net at f4db4afce39c3da09aac2c539df53452319c5983
+
+See https://github.com/protobuf-net/protobuf-net
+
+Licensed under Apache-2.0
+
+Available at https://licenses.nuget.org/Apache-2.0
+
+
+License notice for protobuf-net.Core (v3.2.52)
+------------------------------------
+
+https://github.com/protobuf-net/protobuf-net at f4db4afce39c3da09aac2c539df53452319c5983
+
+See https://github.com/protobuf-net/protobuf-net
+
+Licensed under Apache-2.0
+
+Available at https://licenses.nuget.org/Apache-2.0
+
+
+License notice for Qoi.NetStandard (v1.0.0)
+------------------------------------
+
+https://github.com/RGgt/Qoi.NetStandard
+
+RGgt. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for ShimSkiaSharp (v2.0.0.4)
+------------------------------------
+
+https://github.com/wieslawsoltes/Svg.Skia at e958b025fb2a856b8b1d17f772329d014b92858a
+
+Copyright © Wiesław Šoltés 2024
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Silk.NET.Core (v2.22.0)
+------------------------------------
+
+https://github.com/dotnet/Silk.NET.git at f9535d2900ac226ee6c52cf96a7110fc1115730c
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Silk.NET.Maths (v2.22.0)
+------------------------------------
+
+https://github.com/dotnet/Silk.NET.git at f9535d2900ac226ee6c52cf96a7110fc1115730c
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Silk.NET.OpenGL (v2.22.0)
+------------------------------------
+
+https://github.com/dotnet/Silk.NET.git at f9535d2900ac226ee6c52cf96a7110fc1115730c
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Silk.NET.Vulkan (v2.22.0)
+------------------------------------
+
+https://github.com/dotnet/Silk.NET.git at f9535d2900ac226ee6c52cf96a7110fc1115730c
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Silk.NET.Vulkan.Extensions.EXT (v2.22.0)
+------------------------------------
+
+https://github.com/dotnet/Silk.NET.git at f9535d2900ac226ee6c52cf96a7110fc1115730c
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Silk.NET.Vulkan.Extensions.KHR (v2.22.0)
+------------------------------------
+
+https://github.com/dotnet/Silk.NET.git at f9535d2900ac226ee6c52cf96a7110fc1115730c
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for SkiaSharp (v3.119.0)
+------------------------------------
+
+https://go.microsoft.com/fwlink/?linkid=868515 at 64f24b1cddb68d30ec0ac6b661964178fc21d5ec
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for SkiaSharp.HarfBuzz (v2.88.8)
+------------------------------------
+
+https://github.com/mono/SkiaSharp at 7af1d0840a381c0ce7ef2877454a88dbb2949686
+
+https://go.microsoft.com/fwlink/?linkid=868515
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+License available at https://go.microsoft.com/fwlink/?linkid=868514
+
+
+License notice for SkiaSharp.NativeAssets.Linux (v2.88.9)
+------------------------------------
+
+https://github.com/mono/SkiaSharp at 9699cdf046a989423a5e5eca68e7fd813486d81c
+
+https://go.microsoft.com/fwlink/?linkid=868515
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for SkiaSharp.NativeAssets.macOS (v3.119.0)
+------------------------------------
+
+https://go.microsoft.com/fwlink/?linkid=868515 at 64f24b1cddb68d30ec0ac6b661964178fc21d5ec
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for SkiaSharp.NativeAssets.WebAssembly (v3.119.0)
+------------------------------------
+
+https://go.microsoft.com/fwlink/?linkid=868515 at 64f24b1cddb68d30ec0ac6b661964178fc21d5ec
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for SkiaSharp.NativeAssets.Win32 (v3.119.0)
+------------------------------------
+
+https://go.microsoft.com/fwlink/?linkid=868515 at 64f24b1cddb68d30ec0ac6b661964178fc21d5ec
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for SkiaSharp.Skottie (v2.88.8)
+------------------------------------
+
+https://github.com/mono/SkiaSharp at 7af1d0840a381c0ce7ef2877454a88dbb2949686
+
+https://go.microsoft.com/fwlink/?linkid=868515
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+License available at https://go.microsoft.com/fwlink/?linkid=868514
+
+
+License notice for Svg.Custom (v2.0.0.4)
+------------------------------------
+
+https://github.com/wieslawsoltes/Svg.Skia at e958b025fb2a856b8b1d17f772329d014b92858a
+
+Copyright © Wiesław Šoltés 2024
+
+Licensed under MS-PL
+
+Available at https://licenses.nuget.org/MS-PL
+
+
+License notice for Svg.Model (v2.0.0.4)
+------------------------------------
+
+https://github.com/wieslawsoltes/Svg.Skia at e958b025fb2a856b8b1d17f772329d014b92858a
+
+Copyright © Wiesław Šoltés 2024
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Svg.Skia (v2.0.0.4)
+------------------------------------
+
+https://github.com/wieslawsoltes/Svg.Skia at e958b025fb2a856b8b1d17f772329d014b92858a
+
+Copyright © Wiesław Šoltés 2024
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for System.Buffers (v4.6.0)
+------------------------------------
+
+https://github.com/dotnet/maintenance-packages at d0c2a5a83211e271826172a6b0510c25a52dbd53
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for System.CodeDom (v8.0.0)
+------------------------------------
+
+https://github.com/dotnet/runtime at 5535e31a712343a63f5d7d796cd874e563e5ac14
+
+https://dot.net/
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for System.Collections.Immutable (v8.0.0)
+------------------------------------
+
+https://github.com/dotnet/runtime at 5535e31a712343a63f5d7d796cd874e563e5ac14
+
+https://dot.net/
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for System.Configuration.ConfigurationManager (v8.0.0)
+------------------------------------
+
+https://github.com/dotnet/runtime at 5535e31a712343a63f5d7d796cd874e563e5ac14
+
+https://dot.net/
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for System.Diagnostics.EventLog (v8.0.0)
+------------------------------------
+
+https://github.com/dotnet/runtime at 5535e31a712343a63f5d7d796cd874e563e5ac14
+
+https://dot.net/
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for System.Diagnostics.PerformanceCounter (v8.0.0)
+------------------------------------
+
+https://github.com/dotnet/runtime at 5535e31a712343a63f5d7d796cd874e563e5ac14
+
+https://dot.net/
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for System.IO.Pipelines (v8.0.0)
+------------------------------------
+
+https://github.com/dotnet/runtime at 5535e31a712343a63f5d7d796cd874e563e5ac14
+
+https://dot.net/
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for System.Management (v8.0.0)
+------------------------------------
+
+https://github.com/dotnet/runtime at 5535e31a712343a63f5d7d796cd874e563e5ac14
+
+https://dot.net/
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for System.Memory (v4.5.5)
+------------------------------------
+
+https://dot.net/
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+License available at https://github.com/dotnet/corefx/blob/master/LICENSE.TXT
+
+
+License notice for System.Numerics.Vectors (v4.5.0)
+------------------------------------
+
+https://dot.net/
+
+Copyright © Microsoft Corporation.  All rights reserved.
+
+License available at https://github.com/dotnet/corefx/blob/master/LICENSE.TXT
+
+
+License notice for System.Reactive (v6.0.1)
+------------------------------------
+
+https://github.com/dotnet/reactive at 1cfc6465d1c9c6144d5b4e6420240f2767c8f85c
+
+Copyright (c) .NET Foundation and Contributors.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for System.Reflection.Metadata (v8.0.0)
+------------------------------------
+
+https://github.com/dotnet/runtime at 5535e31a712343a63f5d7d796cd874e563e5ac14
+
+https://dot.net/
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for System.Runtime.CompilerServices.Unsafe (v6.0.0)
+------------------------------------
+
+https://github.com/dotnet/runtime at 4822e3c3aa77eb82b2fb33c9321f923cf11ddde6
+
+https://dot.net/
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for System.Security.AccessControl (v5.0.0)
+------------------------------------
+
+git://github.com/dotnet/runtime at cf258a14b70ad9069470a108f13765e0e5988f51
+
+https://github.com/dotnet/runtime
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for System.Security.Cryptography.ProtectedData (v8.0.0)
+------------------------------------
+
+https://github.com/dotnet/runtime at 5535e31a712343a63f5d7d796cd874e563e5ac14
+
+https://dot.net/
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for System.Security.Principal.Windows (v5.0.0)
+------------------------------------
+
+git://github.com/dotnet/runtime at cf258a14b70ad9069470a108f13765e0e5988f51
+
+https://github.com/dotnet/runtime
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for System.Text.Encodings.Web (v8.0.0)
+------------------------------------
+
+https://github.com/dotnet/runtime at 5535e31a712343a63f5d7d796cd874e563e5ac14
+
+https://dot.net/
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for System.Text.Json (v8.0.0)
+------------------------------------
+
+https://github.com/dotnet/runtime at 5535e31a712343a63f5d7d796cd874e563e5ac14
+
+https://dot.net/
+
+Copyright © Microsoft Corporation. All rights reserved.
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Tmds.DBus.Protocol (v0.21.2)
+------------------------------------
+
+https://github.com/tmds/Tmds.DBus.git at f0b2c29f57bdc7efaeccaa61ca9f13e9a0eebff7
+
+Tom Deseyn
+
+Licensed under MIT
+
+Available at https://licenses.nuget.org/MIT
+
+
+License notice for Wasmtime (v22.0.0)
+------------------------------------
+
+https://github.com/bytecodealliance/wasmtime-dotnet at 9a64e14aeb28991eb61f186fea9a745c0fd7f6a4
+
+Licensed under Apache-2.0 WITH LLVM-exception
+
+Available at https://licenses.nuget.org/Apache-2.0%20WITH%20LLVM-exception
+
+

+ 165 - 0
Third Party Licenses/ffmpeg-linux.txt

@@ -0,0 +1,165 @@
+GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.

+ 168 - 0
Third Party Licenses/ffmpeg-macos.txt

@@ -0,0 +1,168 @@
+FFmpeg compiled for MacOs with configuration
+./configure --disable-ffplay                   
+
+GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.

+ 227 - 0
Third Party Licenses/ffmpeg-windows.txt

@@ -0,0 +1,227 @@
+FFmpeg
+
+lgpl-v3 version compiled for Windows using https://github.com/m-ab-s/media-autobuild_suite
+using configuration:
+arch=3
+license2=4
+standalone=1
+vpx2=2
+aom=1
+rav1e=1
+dav1d=1
+libavif=1
+jpegxl=2
+x2643=1
+x2652=1
+other265=1
+svthevc=1
+xvc=2
+vvc=1
+uvg266=2
+vvenc=2
+vvdec=2
+svtav1=2
+svtvp9=2
+flac=2
+fdkaac=1
+faac=2
+exhale=2
+mediainfo=1
+soxB=2
+ffmpegB2=1
+ffmpegPath=https://git.ffmpeg.org/ffmpeg.git
+ffmpegUpdate=2
+ffmpegChoice=2
+mp4box=2
+rtmpdump=2
+mplayer2=2
+mpv=2
+vlc=2
+bmx=2
+curl=1
+ffmbc=2
+cyanrip2=2
+ripgrep=2
+jq=2
+jo=2
+dssim=2
+avs2=2
+dovitool=2
+hdr10plustool=2
+CC=2
+cores=12
+deleteSource=1
+strip=1
+pack=2
+logging=1
+updateSuite=1
+timeStamp=2
+ccache=2
+noMintty=2
+
+---- LICENSE
+  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.

+ 0 - 46
azure-pipelines.yml

@@ -1,46 +0,0 @@
-trigger:
-- development
-- master
-
-pool:
-  vmImage: 'windows-latest'
-
-variables:
-  solution: '**/*.sln'
-  buildPlatform: 'Any CPU'
-  buildConfiguration: 'Release'
-
-steps:
-- task: NuGetToolInstaller@1
-
-- task: NuGetCommand@2
-  inputs:
-    restoreSolution: '$(solution)'
-
-- task: DotNetCoreCLI@2
-  displayName: Build
-  inputs:
-    command: 'build'
-    projects: '**/*.csproj'
-    arguments: '--configuration Release'
-
-- task: DotNetCoreCLI@2
-  displayName: Tests
-  inputs:
-    command: test
-    projects: '**/*Tests/*.csproj'
-    arguments: '--configuration $(buildConfiguration)'
-
-#- task: PowerShell@2
-#  inputs:
-#    targetType: 'inline'
-#    script: '& "$env:userprofile\.nuget\packages\opencover\4.7.1221\tools\OpenCover.Console.exe" -register -target:"$env:programfiles/dotnet/dotnet.exe" -targetargs:test -filter:"+[*]*" -output:".\PixiEditor_coverage.xml" -oldstyle'
-#    workingDirectory: 'PixiEditorTests\'
-#  displayName: Collect code coverage
-
-  # Disiabled, because there is a problem with .NET 6 and OpenCover.Console.exe
-#- task: CmdLine@2
-#  continueOnError: true
-#  inputs:
-#    script: codecov -f .\PixiEditorTests\PixiEditor_coverage.xml -t $(CODECOV_TOKEN)
-#  displayName: Upload to Codecov.io

+ 2 - 0
gen_third_party_licenses.sh

@@ -0,0 +1,2 @@
+dotnet tool install --global ThirdLicense --version 1.3.1
+thirdlicense --project ./src/PixiEditor.Desktop/PixiEditor.Desktop.csproj --output "./Third Party Licenses/ThirdPartyLicenses.txt"

+ 1 - 1
incompatible.json

@@ -1 +1 @@
-["0.1.3.0", "0.1.3.1", "0.1.3.2", "0.1.3.3", "0.1.3.4", "0.1.3.5", "0.1.3.6", "0.1.4.0", "0.1.5.0", "0.1.6.0", "0.1.7.0", "0.1.8.0", "0.1.9.0", "0.1.9.1", "0.1.9.2", "1.0.0.0", "1.0.0.1", "1.0.1.0"]
+["0.1.3.0", "0.1.3.1", "0.1.3.2", "0.1.3.3", "0.1.3.4", "0.1.3.5", "0.1.3.6", "0.1.4.0", "0.1.5.0", "0.1.6.0", "0.1.7.0", "0.1.8.0", "0.1.9.0", "0.1.9.1", "0.1.9.2", "1.0.0.0", "1.0.0.1", "1.0.1.0", "1.1.0.0", "1.1.3.0", "1.2.0.0", "1.2.1.0", "1.2.1.3", "1.2.1.4", "1.2.2.0", "1.2.2.1", "1.2.3.0", "1.2.3.1", "1.2.3.2", "1.2.4.0", "1.2.4.1", "1.2.5.0"]

+ 97 - 0
samples/Custom.ruleset

@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RuleSet Name="Name" Description="Description" ToolsVersion="17.0">
+  <Rules AnalyzerId="Microsoft.CodeAnalysis.CSharp" RuleNamespace="Microsoft.CodeAnalysis.CSharp">
+    <Rule Id="AD0001" Action="None" />
+  </Rules>
+  <Rules AnalyzerId="Microsoft.CodeAnalysis.CSharp.Features" RuleNamespace="Microsoft.CodeAnalysis.CSharp.Features">
+    <Rule Id="IDE0090" Action="None" />
+  </Rules>
+  <Rules AnalyzerId="Microsoft.CodeAnalysis.NetAnalyzers" RuleNamespace="Microsoft.CodeAnalysis.NetAnalyzers">
+    <Rule Id="CA1416" Action="None" />
+  </Rules>
+  <Rules AnalyzerId="Microsoft.NetCore.Analyzers" RuleNamespace="Microsoft.NetCore.Analyzers">
+    <Rule Id="CA1303" Action="None" />
+    <Rule Id="CA1416" Action="None" />
+  </Rules>
+  <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
+    <Rule Id="SA0001" Action="None" />
+    <Rule Id="SA1000" Action="None" />
+    <Rule Id="SA1005" Action="None" />
+    <Rule Id="SA1008" Action="None" />
+    <Rule Id="SA1009" Action="None" />
+    <Rule Id="SA1011" Action="None" />
+    <Rule Id="SA1023" Action="None" />
+    <Rule Id="SA1028" Action="None" />
+    <Rule Id="SA1101" Action="None" />
+    <Rule Id="SA1110" Action="None" />
+    <Rule Id="SA1111" Action="None" />
+    <Rule Id="SA1112" Action="None" />
+    <Rule Id="SA1117" Action="None" />
+    <Rule Id="SA1119" Action="None" />
+    <Rule Id="SA1121" Action="None" />
+    <Rule Id="SA1122" Action="None" />
+    <Rule Id="SA1124" Action="None" />
+    <Rule Id="SA1127" Action="None" />
+    <Rule Id="SA1128" Action="None" />
+    <Rule Id="SA1129" Action="None" />
+    <Rule Id="SA1130" Action="None" />
+    <Rule Id="SA1132" Action="None" />
+    <Rule Id="SA1135" Action="None" />
+    <Rule Id="SA1136" Action="None" />
+    <Rule Id="SA1139" Action="None" />
+    <Rule Id="SA1200" Action="None" />
+    <Rule Id="SA1201" Action="None" />
+    <Rule Id="SA1202" Action="None" />
+    <Rule Id="SA1204" Action="None" />
+    <Rule Id="SA1207" Action="None" />
+    <Rule Id="SA1208" Action="None" />
+    <Rule Id="SA1209" Action="None" />
+    <Rule Id="SA1210" Action="None" />
+    <Rule Id="SA1211" Action="None" />
+    <Rule Id="SA1214" Action="None" />
+    <Rule Id="SA1216" Action="None" />
+    <Rule Id="SA1217" Action="None" />
+    <Rule Id="SA1303" Action="None" />
+    <Rule Id="SA1304" Action="None" />
+    <Rule Id="SA1307" Action="None" />
+    <Rule Id="SA1309" Action="None" />
+    <Rule Id="SA1310" Action="None" />
+    <Rule Id="SA1311" Action="None" />
+    <Rule Id="SA1313" Action="None" />
+    <Rule Id="SA1316" Action="None" />
+    <Rule Id="SA1400" Action="None" />
+    <Rule Id="SA1401" Action="None" />
+    <Rule Id="SA1402" Action="None" />
+    <Rule Id="SA1405" Action="None" />
+    <Rule Id="SA1406" Action="None" />
+    <Rule Id="SA1407" Action="None" />
+    <Rule Id="SA1408" Action="None" />
+    <Rule Id="SA1410" Action="None" />
+    <Rule Id="SA1411" Action="None" />
+    <Rule Id="SA1413" Action="None" />
+    <Rule Id="SA1501" Action="None" />
+    <Rule Id="SA1502" Action="None" />
+    <Rule Id="SA1503" Action="None" />
+    <Rule Id="SA1505" Action="None" />
+    <Rule Id="SA1507" Action="None" />
+    <Rule Id="SA1508" Action="None" />
+    <Rule Id="SA1512" Action="None" />
+    <Rule Id="SA1513" Action="None" />
+    <Rule Id="SA1515" Action="None" />
+    <Rule Id="SA1516" Action="None" />
+    <Rule Id="SA1518" Action="None" />
+    <Rule Id="SA1600" Action="None" />
+    <Rule Id="SA1601" Action="None" />
+    <Rule Id="SA1602" Action="None" />
+    <Rule Id="SA1604" Action="None" />
+    <Rule Id="SA1605" Action="None" />
+    <Rule Id="SA1606" Action="None" />
+    <Rule Id="SA1607" Action="None" />
+    <Rule Id="SA1623" Action="None" />
+    <Rule Id="SA1629" Action="None" />
+    <Rule Id="SA1633" Action="None" />
+    <Rule Id="SA1642" Action="None" />
+    <Rule Id="SA1643" Action="None" />
+    <Rule Id="SA1648" Action="None" />
+  </Rules>
+</RuleSet>

+ 103 - 0
samples/Directory.Build.props

@@ -0,0 +1,103 @@
+<Project>
+    <PropertyGroup>
+        <CodeAnalysisRuleSet>../Custom.ruleset</CodeAnalysisRuleSet>
+		    <AvaloniaVersion>11.3.0</AvaloniaVersion>
+    </PropertyGroup>
+    <ItemGroup>
+        <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
+    </ItemGroup>
+    <ItemGroup>
+        <AdditionalFiles Include="../stylecop.json" />
+    </ItemGroup>
+
+  <PropertyGroup Condition="$([MSBuild]::IsOsPlatform('Windows')) AND '$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'X64'">
+    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
+  </PropertyGroup>
+  <PropertyGroup Condition="$([MSBuild]::IsOsPlatform('Windows')) AND '$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'Arm64'">
+    <RuntimeIdentifier>win-arm64</RuntimeIdentifier>
+  </PropertyGroup>
+  <PropertyGroup Condition="$([MSBuild]::IsOsPlatform('Linux')) AND '$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'X64'">
+    <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
+  </PropertyGroup>
+  <PropertyGroup Condition="$([MSBuild]::IsOsPlatform('Linux')) AND '$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'Arm64'">
+    <RuntimeIdentifier>linux-arm64</RuntimeIdentifier>
+  </PropertyGroup>
+  <PropertyGroup Condition="$([MSBuild]::IsOsPlatform('OSX')) AND '$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'X64'">
+    <RuntimeIdentifier>osx-x64</RuntimeIdentifier>
+  </PropertyGroup>
+  <PropertyGroup Condition="$([MSBuild]::IsOsPlatform('OSX')) AND '$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'Arm64'">
+    <RuntimeIdentifier>osx-arm64</RuntimeIdentifier>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Platform)'=='x64'">
+    <PlatformTarget>x64</PlatformTarget>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Platform)'=='arm64'">
+    <PlatformTarget>arm64</PlatformTarget>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)'=='MSIX Debug'">
+    <DebugType>full</DebugType>
+    <DebugSymbols>true</DebugSymbols>
+    <Optimize>false</Optimize>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)'=='MSIX'">
+    <DefineConstants>TRACE;RELEASE</DefineConstants>
+    <Optimize>true</Optimize>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)'=='Release'">
+    <DefineConstants>TRACE;UPDATE</DefineConstants>
+    <Optimize>true</Optimize>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)'=='Debug'">
+    <DebugType>full</DebugType>
+    <DebugSymbols>true</DebugSymbols>
+    <WarningLevel>0</WarningLevel>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)'=='Steam'">
+    <DefineConstants>TRACE;RELEASE;STEAM</DefineConstants>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)'=='DevSteam'">
+    <DefineConstants>TRACE;RELEASE;STEAM</DefineConstants>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+
+  <PropertyGroup Condition=" '$(Configuration)' == 'DevRelease' ">
+    <DefineConstants>TRACE;UPDATE;RELEASE</DefineConstants>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(RuntimeIdentifier)'=='win-x64'">
+    <DefineConstants>WINDOWS</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(RuntimeIdentifier)'=='win-arm64'">
+    <DefineConstants>WINDOWS</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(RuntimeIdentifier)'=='linux-x64'">
+    <DefineConstants>LINUX</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(RuntimeIdentifier)'=='linux-arm64'">
+    <DefineConstants>LINUX</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(RuntimeIdentifier)'=='osx-x64'">
+    <DefineConstants>MACOS</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(RuntimeIdentifier)'=='osx-arm64'">
+    <DefineConstants>MACOS</DefineConstants>
+  </PropertyGroup>
+  
+</Project>

+ 93 - 0
samples/PixiEditorExtensionSamples.sln

@@ -0,0 +1,93 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditor.Extensions.Sdk", "..\src\PixiEditor.Extensions.Sdk\PixiEditor.Extensions.Sdk.csproj", "{FD9B4C32-4D2E-410E-BC6B-787779BEB6E2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample1_HelloWorld", "Sample1_HelloWorld\Sample1_HelloWorld.csproj", "{82A85041-A666-42DB-8F84-7D91EF9A5C9D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample2_LocalizationSample", "Sample2_LocalizationSample\Sample2_LocalizationSample.csproj", "{3201A287-5103-48F1-9005-E27B5025E6FA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample3_Preferences", "Sample3_Preferences\Sample3_Preferences.csproj", "{EF6E6827-9827-4465-AD9B-5BE6BEE5747D}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_References", "_References", "{7CC35BC4-829F-4EF4-8EB6-E1D46206E7DC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample4_CreatePopup", "Sample4_CreatePopup\Sample4_CreatePopup.csproj", "{93ADCE51-F671-4374-84AC-5AB07A098F27}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample5_Resources", "Sample5_Resources\Sample5_Resources.csproj", "{51E1742D-132F-4CE9-9313-67FF1AC785D6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample6_Palettes", "Sample6_Palettes\Sample6_Palettes.csproj", "{6FF1D3AB-358A-4D78-8877-DACC01D34B87}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample7_FlyUI", "Sample7_FlyUI\Sample7_FlyUI.csproj", "{432A224A-8035-47C1-AC41-6715021B3AA3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample8_Commands", "Sample8_Commands\Sample8_Commands.csproj", "{25DA4758-9F82-494E-96A3-B9C48637C0E0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample8_CommandLibrary", "Sample8_CommandLibrary\Sample8_CommandLibrary.csproj", "{3559A288-DF82-4429-B23C-CFF9E55B372E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample9_Document", "Sample9_Document\Sample9_Document.csproj", "{E018D2C3-2DD7-4BC7-AAAF-91DA949789E4}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample10_VisualTree", "Sample10_VisualTree\Sample10_VisualTree.csproj", "{574D7C79-8899-4A69-911A-05AEA650A275}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{FD9B4C32-4D2E-410E-BC6B-787779BEB6E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{FD9B4C32-4D2E-410E-BC6B-787779BEB6E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{FD9B4C32-4D2E-410E-BC6B-787779BEB6E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{FD9B4C32-4D2E-410E-BC6B-787779BEB6E2}.Release|Any CPU.Build.0 = Release|Any CPU
+		{82A85041-A666-42DB-8F84-7D91EF9A5C9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{82A85041-A666-42DB-8F84-7D91EF9A5C9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{82A85041-A666-42DB-8F84-7D91EF9A5C9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{82A85041-A666-42DB-8F84-7D91EF9A5C9D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{3201A287-5103-48F1-9005-E27B5025E6FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3201A287-5103-48F1-9005-E27B5025E6FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3201A287-5103-48F1-9005-E27B5025E6FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3201A287-5103-48F1-9005-E27B5025E6FA}.Release|Any CPU.Build.0 = Release|Any CPU
+		{EF6E6827-9827-4465-AD9B-5BE6BEE5747D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{EF6E6827-9827-4465-AD9B-5BE6BEE5747D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{EF6E6827-9827-4465-AD9B-5BE6BEE5747D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{EF6E6827-9827-4465-AD9B-5BE6BEE5747D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{93ADCE51-F671-4374-84AC-5AB07A098F27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{93ADCE51-F671-4374-84AC-5AB07A098F27}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{93ADCE51-F671-4374-84AC-5AB07A098F27}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{93ADCE51-F671-4374-84AC-5AB07A098F27}.Release|Any CPU.Build.0 = Release|Any CPU
+		{51E1742D-132F-4CE9-9313-67FF1AC785D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{51E1742D-132F-4CE9-9313-67FF1AC785D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{51E1742D-132F-4CE9-9313-67FF1AC785D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{51E1742D-132F-4CE9-9313-67FF1AC785D6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6FF1D3AB-358A-4D78-8877-DACC01D34B87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6FF1D3AB-358A-4D78-8877-DACC01D34B87}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6FF1D3AB-358A-4D78-8877-DACC01D34B87}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6FF1D3AB-358A-4D78-8877-DACC01D34B87}.Release|Any CPU.Build.0 = Release|Any CPU
+		{432A224A-8035-47C1-AC41-6715021B3AA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{432A224A-8035-47C1-AC41-6715021B3AA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{432A224A-8035-47C1-AC41-6715021B3AA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{432A224A-8035-47C1-AC41-6715021B3AA3}.Release|Any CPU.Build.0 = Release|Any CPU
+		{25DA4758-9F82-494E-96A3-B9C48637C0E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{25DA4758-9F82-494E-96A3-B9C48637C0E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{25DA4758-9F82-494E-96A3-B9C48637C0E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{25DA4758-9F82-494E-96A3-B9C48637C0E0}.Release|Any CPU.Build.0 = Release|Any CPU
+		{3559A288-DF82-4429-B23C-CFF9E55B372E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3559A288-DF82-4429-B23C-CFF9E55B372E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3559A288-DF82-4429-B23C-CFF9E55B372E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3559A288-DF82-4429-B23C-CFF9E55B372E}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E018D2C3-2DD7-4BC7-AAAF-91DA949789E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E018D2C3-2DD7-4BC7-AAAF-91DA949789E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E018D2C3-2DD7-4BC7-AAAF-91DA949789E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E018D2C3-2DD7-4BC7-AAAF-91DA949789E4}.Release|Any CPU.Build.0 = Release|Any CPU
+		{574D7C79-8899-4A69-911A-05AEA650A275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{574D7C79-8899-4A69-911A-05AEA650A275}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{574D7C79-8899-4A69-911A-05AEA650A275}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{574D7C79-8899-4A69-911A-05AEA650A275}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(NestedProjects) = preSolution
+		{FD9B4C32-4D2E-410E-BC6B-787779BEB6E2} = {7CC35BC4-829F-4EF4-8EB6-E1D46206E7DC}
+	EndGlobalSection
+EndGlobal

+ 8 - 0
samples/Sample10_VisualTree/Program.cs

@@ -0,0 +1,8 @@
+namespace Sample9_Commands;
+
+public static class Program
+{
+    public static void Main()
+    {
+    }
+}

+ 45 - 0
samples/Sample10_VisualTree/Sample10_VisualTree.csproj

@@ -0,0 +1,45 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>net8.0</TargetFramework>
+        <RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
+        <OutputType>Exe</OutputType>
+        <PublishTrimmed>true</PublishTrimmed>
+        <WasmSingleFileBundle>true</WasmSingleFileBundle>
+        <GenerateExtensionPackage>true</GenerateExtensionPackage>
+        <PixiExtOutputPath>..\..\src\PixiEditor.Desktop\bin\Debug\net8.0\Extensions</PixiExtOutputPath>
+        <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+        <ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
+        <RootNamespace>Sample10_VisualTree</RootNamespace>
+    </PropertyGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <ItemGroup>
+        <ProjectReference Include="..\..\src\PixiEditor.Extensions.Sdk\PixiEditor.Extensions.Sdk.csproj"/>
+    </ItemGroup>
+
+    <ItemGroup>
+        <None Remove="extension.json"/>
+        <Content Include="extension.json">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <ItemGroup>
+        <Content Include="Localization\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <ItemGroup>
+        <Content Include="Resources\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.props"/>
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.targets"/>
+
+
+</Project>

+ 44 - 0
samples/Sample10_VisualTree/VisualTreeSampleExtension.cs

@@ -0,0 +1,44 @@
+using System;
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
+using PixiEditor.Extensions.CommonApi.Windowing;
+using PixiEditor.Extensions.Sdk;
+using PixiEditor.Extensions.Sdk.Api.FlyUI;
+using PixiEditor.Extensions.Sdk.Api.Window;
+
+namespace Sample10_VisualTree;
+
+public class VisualTreeSampleExtension : PixiEditorExtension
+{
+    /// <summary>
+    ///     This method is called when extension is loaded.
+    ///  All extensions are first loaded and then initialized. This method is called before <see cref="OnInitialized"/>.
+    /// </summary>
+    public override void OnLoaded()
+    {
+    }
+
+    /// <summary>
+    ///     This method is called when extension is initialized. After this method is called, you can use Api property to access PixiEditor API.
+    /// </summary>
+    public override void OnInitialized()
+    {
+        Api.WindowProvider.SubscribeWindowOpened(BuiltInWindowType.StartupWindow, InjectButton);
+    }
+
+    private void InjectButton(PopupWindow window)
+    {
+        var button = new Button(new Text("Click me!"));
+
+        var element = Api.VisualTreeProvider.FindElement("ExampleFilesGrid", window);
+
+        if (element is NativeMultiChildElement panel)
+        {
+            panel.AppendChild(0, button);
+        }
+    }
+
+    public override void OnMainWindowLoaded()
+    {
+       // wip
+    }
+}

+ 39 - 0
samples/Sample10_VisualTree/extension.json

@@ -0,0 +1,39 @@
+{
+  "displayName": "Sample Extension - Visual Tree",
+  "uniqueName": "yourCompany.Samples.VisualTree",
+  "description": "Sample Visual Tree extension for PixiEditor",
+  "version": "1.0.0",
+  "localization": {
+    "languages": [
+      {
+        "name": "English",
+        "code": "en",
+        "localeFileName": "Localization/en.json"
+      }
+    ]
+  },
+  "author": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "publisher": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "contributors": [
+    {
+      "name": "flabbet",
+      "email": "[email protected]",
+      "website": "https://github.com/flabbet"
+    }
+  ],
+  "license": "MIT",
+  "categories": [
+    "Extension"
+  ],
+  "Permissions": [
+    "ReadUserData"
+  ]
+}

+ 22 - 0
samples/Sample1_HelloWorld/HelloWorldExtension.cs

@@ -0,0 +1,22 @@
+namespace HelloWorld;
+
+using PixiEditor.Extensions.Sdk;
+
+public class HelloWorldExtension : PixiEditorExtension
+{
+    /// <summary>
+    ///     This method is called when extension is loaded.
+    ///  All extensions are first loaded and then initialized. This method is called before <see cref="OnInitialized"/>.
+    /// </summary>
+    public override void OnLoaded()
+    {
+    }
+
+    /// <summary>
+    ///     This method is called when extension is initialized. After this method is called, you can use Api property to access PixiEditor API.
+    /// </summary>
+    public override void OnInitialized()
+    {
+        Api.Logger.Log("Hello World!");
+    }
+}

+ 14 - 0
samples/Sample1_HelloWorld/Program.cs

@@ -0,0 +1,14 @@
+namespace HelloWorld;
+
+public static class Program
+{
+    /// <summary>
+    ///     The entry point of the application. This will be executed when extension is loaded.
+    /// You can use this method, but there are special methods that are used for initialization. You won't be able to access PixiEditor Api at this point.
+    /// See <see cref="HelloWorldExtension"/> for more information.
+    /// </summary>
+    public static void Main()
+    {
+
+    }
+}

+ 30 - 0
samples/Sample1_HelloWorld/Sample1_HelloWorld.csproj

@@ -0,0 +1,30 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <TargetFramework>net8.0</TargetFramework>
+        <RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
+        <OutputType>Exe</OutputType>
+        <PublishTrimmed>true</PublishTrimmed>
+        <WasmSingleFileBundle>true</WasmSingleFileBundle>
+        <GenerateExtensionPackage>true</GenerateExtensionPackage>
+        <PixiExtOutputPath>..\..\src\PixiEditor.Desktop\bin\Debug\net8.0\win-x64\Extensions</PixiExtOutputPath>
+        <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+        <RootNamespace>HelloWorld</RootNamespace>
+        <ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
+    </PropertyGroup>
+    
+    <ItemGroup>
+        <ProjectReference Include="..\..\src\PixiEditor.Extensions.Sdk\PixiEditor.Extensions.Sdk.csproj" />
+    </ItemGroup>
+    
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.props"/>
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.targets" />
+    
+    <ItemGroup>
+        <None Remove="extension.json" />
+        <Content Include="extension.json">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+</Project>

+ 27 - 0
samples/Sample1_HelloWorld/extension.json

@@ -0,0 +1,27 @@
+{
+  "displayName": "Sample Extension - Hello World",
+  "uniqueName": "yourCompany.Samples.HelloWorld",
+  "description": "Sample extension for PixiEditor",
+  "version": "1.0.0",
+  "author": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "publisher": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "contributors": [
+    {
+      "name": "flabbet",
+      "email": "[email protected]",
+      "website": "https://github.com/flabbet"
+    }
+  ],
+  "license": "MIT",
+  "categories": [
+    "Extension"
+  ]
+}

+ 4 - 0
samples/Sample2_LocalizationSample/Localization/en.json

@@ -0,0 +1,4 @@
+{
+  "HELLO_WORLD": "Hello World!",
+  "HELLO_NAME": "Hello {0}!"
+}

+ 29 - 0
samples/Sample2_LocalizationSample/LocalizationSampleExtension.cs

@@ -0,0 +1,29 @@
+using PixiEditor.Extensions.Sdk;
+using PixiEditor.Extensions.Sdk.Api.Localization;
+
+namespace LocalizationSample;
+
+public class LocalizationSampleExtension : PixiEditorExtension
+{
+    /// <summary>
+    ///     This method is called when extension is loaded.
+    ///  All extensions are first loaded and then initialized. This method is called before <see cref="OnInitialized"/>.
+    /// </summary>
+    public override void OnLoaded()
+    {
+    }
+
+    /// <summary>
+    ///     This method is called when extension is initialized. After this method is called, you can use Api property to access PixiEditor API.
+    /// </summary>
+    public override void OnInitialized()
+    {
+        // You can either use direct key or ExtensionUniqueName:Key to access localization strings.
+        Api.Logger.Log(new LocalizedString("HELLO_WORLD"));
+        Api.Logger.Log(new LocalizedString("HELLO_NAME", "John Doe"));
+
+        // By prepending "PixiEditor:" to the key, you can access built-in PixiEditor localization strings.
+        // if you prepend any other extension unique name, you can access that extension localization strings.
+        Api.Logger.Log(new LocalizedString("PixiEditor:SHOW_IMAGE_PREVIEW_TASKBAR"));
+    }
+}

+ 14 - 0
samples/Sample2_LocalizationSample/Program.cs

@@ -0,0 +1,14 @@
+namespace LocalizationSample;
+
+public static class Program
+{
+    /// <summary>
+    ///     The entry point of the application. This will be executed when extension is loaded.
+    /// You can use this method, but there are special methods that are used for initialization. You won't be able to access PixiEditor Api at this point.
+    /// See <see cref="LocalizationSampleExtension"/> for more information.
+    /// </summary>
+    public static void Main()
+    {
+
+    }
+}

+ 13 - 0
samples/Sample2_LocalizationSample/README.md

@@ -0,0 +1,13 @@
+This sample shows how to create translations for you extension and how to 
+use it in your extension.
+
+Localization data is automatically loaded from the `Localization` folder in
+your project, make sure to copy it to output directory with
+
+```xml
+    <ItemGroup>
+        <Content Include="Localization\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+```

+ 37 - 0
samples/Sample2_LocalizationSample/Sample2_LocalizationSample.csproj

@@ -0,0 +1,37 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <TargetFramework>net8.0</TargetFramework>
+        <RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
+        <OutputType>Exe</OutputType>
+        <PublishTrimmed>true</PublishTrimmed>
+        <WasmSingleFileBundle>true</WasmSingleFileBundle>
+        <GenerateExtensionPackage>true</GenerateExtensionPackage>
+        <PixiExtOutputPath>..\..\src\PixiEditor.Desktop\bin\Debug\net8.0\win-x64\Extensions</PixiExtOutputPath>
+        <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+        <RootNamespace>LocalizationSample</RootNamespace>
+        <ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <None Remove="extension.json"/>
+        <Content Include="extension.json">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <ItemGroup>
+        <Content Include="Localization\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <ItemGroup>
+        <ProjectReference Include="..\..\src\PixiEditor.Extensions.Sdk\PixiEditor.Extensions.Sdk.csproj"/>
+    </ItemGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.props"/>
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.targets"/>
+
+</Project>

+ 36 - 0
samples/Sample2_LocalizationSample/extension.json

@@ -0,0 +1,36 @@
+{
+  "displayName": "Sample Extension - Localization",
+  "uniqueName": "yourCompany.Samples.Localization",
+  "description": "Sample localization extension for PixiEditor",
+  "localization": {
+    "languages": [
+      {
+        "name": "English",
+        "code": "en",
+        "localeFileName": "Localization/en.json"
+      }
+    ]
+  },
+  "version": "1.0.0",
+  "author": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "publisher": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "contributors": [
+    {
+      "name": "flabbet",
+      "email": "[email protected]",
+      "website": "https://github.com/flabbet"
+    }
+  ],
+  "license": "MIT",
+  "categories": [
+    "Extension"
+  ]
+}

+ 45 - 0
samples/Sample3_Preferences/PreferencesSampleExtension.cs

@@ -0,0 +1,45 @@
+using PixiEditor.Extensions.Sdk;
+
+namespace Preferences;
+
+public class PreferencesSampleExtension : PixiEditorExtension
+{
+    /// <summary>
+    ///     This method is called when extension is loaded.
+    ///  All extensions are first loaded and then initialized. This method is called before <see cref="OnInitialized"/>.
+    /// </summary>
+    public override void OnLoaded()
+    {
+    }
+
+    /// <summary>
+    ///     This method is called when extension is initialized. After this method is called, you can use Api property to access PixiEditor API.
+    /// </summary>
+    public override void OnInitialized()
+    {
+        Api.Preferences.AddCallback<int>("HelloCount", (name, value) => Api.Logger.Log($"Hello count changed to {value}!"));
+        Api.Preferences.AddCallback<double>("TestDouble", (name, value) => Api.Logger.Log($"Test double changed to {value}!"));
+        Api.Preferences.AddCallback<string>("TestString", (name, value) => Api.Logger.Log($"Test string changed to {value}!"));
+        Api.Preferences.AddCallback<bool>("TestBool", (name, value) => Api.Logger.Log($"Test bool changed to {value}!"));
+        
+        // Internally this preference will have name "yourCompany.Samples.Preferences:HelloCount".
+        int helloCount = Api.Preferences.GetPreference<int>("HelloCount");
+
+        Api.Preferences.UpdatePreference("HelloCount", helloCount + 1);
+        
+        Api.Preferences.UpdatePreference("TestDouble", 3.14);
+        Api.Preferences.UpdatePreference("TestString", "Hello, World!");
+        Api.Preferences.UpdatePreference("TestBool", true);
+
+        // This will overwrite built-in PixiEditor preference. Extension must have WriteNonOwnedPreferences permission.
+        // Prepending "PixiEditor:" to preference name will access built-in PixiEditor preferences. If you set it to other extension unique name,
+        // it will access extension preferences.
+        // You can do analogous thing with UpdatePreference.
+        Api.Preferences.UpdateLocalPreference(
+            "PixiEditor:OverwrittenPixiEditorPreference",
+            "This is overwritten value of preference that is built-in in PixiEditor.");
+
+        // You don't need any special permission for reading any kind of preference.
+        Api.Logger.Log(Api.Preferences.GetLocalPreference<string>("PixiEditor:OverwrittenPixiEditorPreference"));
+    }
+}

+ 14 - 0
samples/Sample3_Preferences/Program.cs

@@ -0,0 +1,14 @@
+namespace Preferences;
+
+public static class Program
+{
+    /// <summary>
+    ///     The entry point of the application. This will be executed when extension is loaded.
+    /// You can use this method, but there are special methods that are used for initialization. You won't be able to access PixiEditor Api at this point.
+    /// See <see cref="PreferencesSampleExtension"/> for more information.
+    /// </summary>
+    public static void Main()
+    {
+
+    }
+}

+ 0 - 0
samples/Sample3_Preferences/README.md


+ 37 - 0
samples/Sample3_Preferences/Sample3_Preferences.csproj

@@ -0,0 +1,37 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <TargetFramework>net8.0</TargetFramework>
+        <RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
+        <OutputType>Exe</OutputType>
+        <PublishTrimmed>true</PublishTrimmed>
+        <WasmSingleFileBundle>true</WasmSingleFileBundle>
+        <GenerateExtensionPackage>true</GenerateExtensionPackage>
+        <PixiExtOutputPath>..\..\src\PixiEditor.Desktop\bin\Debug\net8.0\win-x64\Extensions</PixiExtOutputPath>
+        <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+        <RootNamespace>Preferences</RootNamespace>
+        <ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <None Remove="extension.json" />
+        <Content Include="extension.json">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <ItemGroup>
+        <Content Include="Localization\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <ItemGroup>
+        <ProjectReference Include="..\..\src\PixiEditor.Extensions.Sdk\PixiEditor.Extensions.Sdk.csproj" />
+    </ItemGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.props"/>
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.targets" />
+
+</Project>

+ 30 - 0
samples/Sample3_Preferences/extension.json

@@ -0,0 +1,30 @@
+{
+  "displayName": "Sample Extension - Preferences",
+  "uniqueName": "yourCompany.Samples.Preferences",
+  "description": "Sample preferences extension for PixiEditor",
+  "version": "1.0.0",
+  "author": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "publisher": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "contributors": [
+    {
+      "name": "flabbet",
+      "email": "[email protected]",
+      "website": "https://github.com/flabbet"
+    }
+  ],
+  "permissions": [
+    "WriteNonOwnedPreferences"
+  ],
+  "license": "MIT",
+  "categories": [
+    "Extension"
+  ]
+}

+ 30 - 0
samples/Sample4_CreatePopup/CreatePopupSampleExtension.cs

@@ -0,0 +1,30 @@
+using System.Threading.Tasks;
+using PixiEditor.Extensions.Sdk;
+using PixiEditor.Extensions.Sdk.Api.FlyUI;
+
+namespace CreatePopupSample;
+
+public class CreatePopupSampleExtension : PixiEditorExtension
+{
+    /// <summary>
+    ///     This method is called when extension is loaded.
+    ///  All extensions are first loaded and then initialized. This method is called before <see cref="OnInitialized"/>.
+    /// </summary>
+    public override void OnLoaded()
+    {
+
+    }
+
+    /// <summary>
+    ///     This method is called when extension is initialized. After this method is called, you can use Api property to access PixiEditor API.
+    /// </summary>
+    public override void OnInitialized()
+    {
+        var popup = Api.WindowProvider.CreatePopupWindow("Hello World", new Text("Hello from popup!"));
+        popup.ShowDialog().Completed += (result) =>
+        {
+            string resultStr = result.HasValue ? result.Value.ToString() : "null";
+            Api.Logger.Log($"Popup closed with result: {resultStr}");
+        };
+    }
+}

+ 14 - 0
samples/Sample4_CreatePopup/Program.cs

@@ -0,0 +1,14 @@
+namespace CreatePopupSample;
+
+public static class Program
+{
+    /// <summary>
+    ///     The entry point of the application. This will be executed when extension is loaded.
+    /// You can use this method, but there are special methods that are used for initialization. You won't be able to access PixiEditor Api at this point.
+    /// See <see cref="CreatePopupSampleExtension"/> for more information.
+    /// </summary>
+    public static void Main()
+    {
+
+    }
+}

+ 0 - 0
samples/Sample4_CreatePopup/README.md


+ 37 - 0
samples/Sample4_CreatePopup/Sample4_CreatePopup.csproj

@@ -0,0 +1,37 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <TargetFramework>net8.0</TargetFramework>
+        <RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
+        <OutputType>Exe</OutputType>
+        <PublishTrimmed>true</PublishTrimmed>
+        <WasmSingleFileBundle>true</WasmSingleFileBundle>
+        <GenerateExtensionPackage>true</GenerateExtensionPackage>
+        <PixiExtOutputPath>..\..\src\PixiEditor.Desktop\bin\Debug\net8.0\win-x64\Extensions</PixiExtOutputPath>
+        <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+        <RootNamespace>CreatePopupSample</RootNamespace>
+        <ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <None Remove="extension.json" />
+        <Content Include="extension.json">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <ItemGroup>
+        <Content Include="Localization\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <ItemGroup>
+        <ProjectReference Include="..\..\src\PixiEditor.Extensions.Sdk\PixiEditor.Extensions.Sdk.csproj" />
+    </ItemGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.props"/>
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.targets" />
+
+</Project>

+ 27 - 0
samples/Sample4_CreatePopup/extension.json

@@ -0,0 +1,27 @@
+{
+  "displayName": "Sample Extension - Create Popup",
+  "uniqueName": "yourCompany.Samples.CreatePopup",
+  "description": "Sample localization extension for PixiEditor",
+  "version": "1.0.0",
+  "author": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "publisher": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "contributors": [
+    {
+      "name": "flabbet",
+      "email": "[email protected]",
+      "website": "https://github.com/flabbet"
+    }
+  ],
+  "license": "MIT",
+  "categories": [
+    "Extension"
+  ]
+}

+ 16 - 0
samples/Sample5_Resources/Program.cs

@@ -0,0 +1,16 @@
+using ResourcesSample;
+
+namespace CreatePopupSample;
+
+public static class Program
+{
+    /// <summary>
+    ///     The entry point of the application. This will be executed when extension is loaded.
+    /// You can use this method, but there are special methods that are used for initialization. You won't be able to access PixiEditor Api at this point.
+    /// See <see cref="ResourcesSampleExtension"/> for more information.
+    /// </summary>
+    public static void Main()
+    {
+
+    }
+}

+ 1 - 0
samples/Sample5_Resources/Resources/ExampleFile.txt

@@ -0,0 +1 @@
+I am loaded from resources

+ 37 - 0
samples/Sample5_Resources/ResourcesSampleExtension.cs

@@ -0,0 +1,37 @@
+using System.IO;
+using PixiEditor.Extensions.Sdk;
+using PixiEditor.Extensions.Sdk.Api.Resources;
+using PixiEditor.Extensions.Sdk.Bridge;
+
+namespace ResourcesSample;
+
+public class ResourcesSampleExtension : PixiEditorExtension
+{
+    /// <summary>
+    ///     This method is called when extension is loaded.
+    ///  All extensions are first loaded and then initialized. This method is called before <see cref="OnInitialized"/>.
+    /// </summary>
+    public override void OnLoaded()
+    {
+
+    }
+
+    /// <summary>
+    ///     This method is called when extension is initialized. After this method is called, you can use Api property to access PixiEditor API.
+    /// </summary>
+    public override void OnInitialized()
+    {
+        // By default, you can't access any files from the file system, however you can access files from the Resources folder.
+        // This folder contains files that you put in the Resources folder in the extension project.
+        // You can use System.File calls to access files in the Resources folder.
+        // However, if you want to access files that are encrypted, you should use the Resources methods.
+        // Adding <EncryptResources>true</EncryptResources> to the .csproj file will encrypt the resources in the Resources folder.
+        Api.Logger.Log(Resources.ReadAllText("Resources/ExampleFile.txt"));
+
+        Api.Logger.Log("Writing to file...");
+
+        Resources.WriteAllText("Resources/ExampleFile.txt", "Hello from extension!");
+
+        Api.Logger.Log(Resources.ReadAllText("Resources/ExampleFile.txt"));
+    }
+}

+ 44 - 0
samples/Sample5_Resources/Sample5_Resources.csproj

@@ -0,0 +1,44 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <TargetFramework>net8.0</TargetFramework>
+        <RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
+        <OutputType>Exe</OutputType>
+        <PublishTrimmed>true</PublishTrimmed>
+        <WasmSingleFileBundle>true</WasmSingleFileBundle>
+        <GenerateExtensionPackage>true</GenerateExtensionPackage>
+        <PixiExtOutputPath>..\..\src\PixiEditor.Desktop\bin\Debug\net8.0\Extensions</PixiExtOutputPath>
+        <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+        <ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
+        <RootNamespace>ResourcesSample</RootNamespace>
+        <EncryptResources>true</EncryptResources>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <None Remove="extension.json" />
+        <Content Include="extension.json">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <ItemGroup>
+        <Content Include="Localization\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <ItemGroup>
+        <Content Include="Resources\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <ItemGroup>
+        <ProjectReference Include="..\..\src\PixiEditor.Extensions.Sdk\PixiEditor.Extensions.Sdk.csproj" />
+    </ItemGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.props"/>
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.targets" />
+
+</Project>

+ 27 - 0
samples/Sample5_Resources/extension.json

@@ -0,0 +1,27 @@
+{
+  "displayName": "Sample Extension - Resources",
+  "uniqueName": "yourCompany.Samples.Resources",
+  "description": "Sample localization extension for PixiEditor",
+  "version": "1.0.0",
+  "author": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "publisher": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "contributors": [
+    {
+      "name": "flabbet",
+      "email": "[email protected]",
+      "website": "https://github.com/flabbet"
+    }
+  ],
+  "license": "MIT",
+  "categories": [
+    "Extension"
+  ]
+}

+ 25 - 0
samples/Sample6_Palettes/ExamplePaletteDataSource.cs

@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using PixiEditor.Extensions.CommonApi.Async;
+using PixiEditor.Extensions.CommonApi.Palettes;
+using PixiEditor.Extensions.Sdk;
+
+namespace PalettesSample;
+
+public class ExamplePaletteDataSource : PaletteListDataSource
+{
+    public ExamplePaletteDataSource(string name) : base(name)
+    {
+    }
+
+    public override AsyncCall<List<IPalette>> FetchPaletteList(int startIndex, int items, FilteringSettings filtering)
+    {
+        return AsyncCall<List<IPalette>>.FromResult([
+            new ExtensionPalette("Example Palette", new List<PaletteColor>
+            {
+                new PaletteColor(255, 0, 0),
+                new PaletteColor(0, 255, 0),
+                new PaletteColor(0, 0, 255)
+            }, this)
+        ]);
+    }
+}

+ 27 - 0
samples/Sample6_Palettes/PalettesSampleExtension.cs

@@ -0,0 +1,27 @@
+using System.IO;
+using PixiEditor.Extensions.Sdk;
+
+namespace PalettesSample;
+
+public class PalettesSampleExtension : PixiEditorExtension
+{
+    /// <summary>
+    ///     This method is called when extension is loaded.
+    ///  All extensions are first loaded and then initialized. This method is called before <see cref="OnInitialized"/>.
+    /// </summary>
+    public override void OnLoaded()
+    {
+
+    }
+
+    /// <summary>
+    ///     This method is called when extension is initialized. After this method is called, you can use Api property to access PixiEditor API.
+    /// </summary>
+    public override void OnInitialized()
+    {
+        ExamplePaletteDataSource dataSource = new ExamplePaletteDataSource("Palettes Sample");
+        Api.Palettes.RegisterDataSource(dataSource);
+        
+        Api.Logger.Log("Palettes registered!");
+    }
+}

+ 14 - 0
samples/Sample6_Palettes/Program.cs

@@ -0,0 +1,14 @@
+namespace PalettesSample;
+
+public static class Program
+{
+    /// <summary>
+    ///     The entry point of the application. This will be executed when extension is loaded.
+    /// You can use this method, but there are special methods that are used for initialization. You won't be able to access PixiEditor Api at this point.
+    /// See <see cref="PalettesSampleExtension"/> for more information.
+    /// </summary>
+    public static void Main()
+    {
+
+    }
+}

+ 43 - 0
samples/Sample6_Palettes/Sample6_Palettes.csproj

@@ -0,0 +1,43 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <TargetFramework>net8.0</TargetFramework>
+        <RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
+        <OutputType>Exe</OutputType>
+        <PublishTrimmed>true</PublishTrimmed>
+        <WasmSingleFileBundle>true</WasmSingleFileBundle>
+        <GenerateExtensionPackage>true</GenerateExtensionPackage>
+        <PixiExtOutputPath>..\..\src\PixiEditor.Desktop\bin\Debug\net8.0\win-x64\Extensions</PixiExtOutputPath>
+        <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+        <ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
+        <RootNamespace>PalettesSample</RootNamespace>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <None Remove="extension.json" />
+        <Content Include="extension.json">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <ItemGroup>
+        <Content Include="Localization\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <ItemGroup>
+        <Content Include="Resources\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <ItemGroup>
+        <ProjectReference Include="..\..\src\PixiEditor.Extensions.Sdk\PixiEditor.Extensions.Sdk.csproj" />
+    </ItemGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.props"/>
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.targets" />
+
+</Project>

+ 27 - 0
samples/Sample6_Palettes/extension.json

@@ -0,0 +1,27 @@
+{
+  "displayName": "Sample Extension - Palettes",
+  "uniqueName": "yourCompany.Samples.Palettes",
+  "description": "Sample palettes extension for PixiEditor",
+  "version": "1.0.0",
+  "author": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "publisher": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "contributors": [
+    {
+      "name": "flabbet",
+      "email": "[email protected]",
+      "website": "https://github.com/flabbet"
+    }
+  ],
+  "license": "MIT",
+  "categories": [
+    "Extension"
+  ]
+}

+ 18 - 0
samples/Sample7_FlyUI/FlyUISampleExtension.cs

@@ -0,0 +1,18 @@
+using PixiEditor.Extensions.Sdk;
+
+namespace FlyUISample;
+
+public class FlyUiSampleExtension : PixiEditorExtension
+{
+    public override void OnInitialized()
+    {
+        WindowContentElement content = new WindowContentElement();
+        var popup = Api.WindowProvider.CreatePopupWindow("Sample Window", content);
+        content.Window = popup;
+
+        popup.Width = 800;
+        popup.Height = 720;
+        
+        popup.Show();
+    }
+}

+ 9 - 0
samples/Sample7_FlyUI/Program.cs

@@ -0,0 +1,9 @@
+namespace FlyUISample;
+
+public static class Program
+{
+    public static void Main()
+    {
+        
+    }
+}

BIN
samples/Sample7_FlyUI/Resources/Pizza.png


+ 43 - 0
samples/Sample7_FlyUI/Sample7_FlyUI.csproj

@@ -0,0 +1,43 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <TargetFramework>net8.0</TargetFramework>
+        <RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
+        <OutputType>Exe</OutputType>
+        <PublishTrimmed>true</PublishTrimmed>
+        <WasmSingleFileBundle>true</WasmSingleFileBundle>
+        <GenerateExtensionPackage>true</GenerateExtensionPackage>
+        <PixiExtOutputPath>..\..\src\PixiEditor.Desktop\bin\Debug\net8.0\Extensions</PixiExtOutputPath>
+        <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+        <ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
+        <RootNamespace>FlyUISample</RootNamespace>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <None Remove="extension.json" />
+        <Content Include="extension.json">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <ItemGroup>
+        <Content Include="Localization\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <ItemGroup>
+        <Content Include="Resources\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <ItemGroup>
+        <ProjectReference Include="..\..\src\PixiEditor.Extensions.Sdk\PixiEditor.Extensions.Sdk.csproj" />
+    </ItemGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.props"/>
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.targets" />
+
+</Project>

+ 69 - 0
samples/Sample7_FlyUI/WindowContentElement.cs

@@ -0,0 +1,69 @@
+using System.Diagnostics.CodeAnalysis;
+using PixiEditor.Extensions.CommonApi.FlyUI.Events;
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
+using PixiEditor.Extensions.Sdk;
+using PixiEditor.Extensions.Sdk.Api.FlyUI;
+using PixiEditor.Extensions.Sdk.Api.Window;
+
+namespace FlyUISample;
+
+[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:Parameter should not span multiple lines",
+    Justification = "FlyUI style")]
+public class WindowContentElement : StatelessElement
+{
+    public PopupWindow Window { get; set; }
+
+    public override ControlDefinition BuildNative()
+    {
+        SizeInputField field = new SizeInputField();
+        field.SizeChanged += args =>
+        {
+            PixiEditorExtension.Api.Logger.Log(field.Value.ToString());
+        };
+
+        Layout layout = new Layout(body:
+            new Container(margin: Edges.All(25), child:
+                new Column(
+                    crossAxisAlignment: CrossAxisAlignment.Center,
+                    mainAxisAlignment: MainAxisAlignment.SpaceEvenly,
+                    children:
+                    [
+                        new Center(
+                            new Text(
+                                "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vitae neque nibh. Duis sed pharetra dolor. Donec dui sapien, aliquam id sodales in, ornare et urna. Mauris nunc odio, sagittis eget lectus at, imperdiet ornare quam. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam euismod pellentesque blandit. Vestibulum sagittis, ligula non finibus lobortis, dolor lacus consectetur turpis, id facilisis ligula dolor vitae augue.",
+                                wrap: TextWrap.Wrap,
+                                textStyle: new TextStyle(fontSize: 16))
+                        ),
+                        new Align(
+                            alignment: Alignment.CenterRight,
+                            child: new Text("- Paulo Coelho, The Alchemist (1233)",
+                                textStyle: new TextStyle(fontStyle: FontStyle.Italic))
+                        ),
+                        new Container(
+                            margin: Edges.Symmetric(25, 0),
+                            backgroundColor: Color.FromRgba(25, 25, 25, 255),
+                            child: new Column(
+                                new Image(
+                                    "/Pizza.png",
+                                    filterQuality: FilterQuality.None,
+                                    width: 256, height: 256))
+                        ),
+                        new CheckBox(new Text("heloo"),
+                            onCheckedChanged: args =>
+                            {
+                                PixiEditorExtension.Api.Logger.Log(((CheckBox)args.Sender).IsChecked
+                                    ? "Checked"
+                                    : "Unchecked");
+                            }),
+                        field,
+                        new Center(
+                            new Button(
+                                child: new Text("Close"), onClick: _ => { Window.Close(); }))
+                    ]
+                )
+            )
+        );
+
+        return layout.BuildNative();
+    }
+}

+ 27 - 0
samples/Sample7_FlyUI/extension.json

@@ -0,0 +1,27 @@
+{
+  "displayName": "Sample Extension - FlyUI",
+  "uniqueName": "yourCompany.Samples.FlyUI",
+  "description": "Sample FlyUI extension for PixiEditor",
+  "version": "1.0.0",
+  "author": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "publisher": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "contributors": [
+    {
+      "name": "flabbet",
+      "email": "[email protected]",
+      "website": "https://github.com/flabbet"
+    }
+  ],
+  "license": "MIT",
+  "categories": [
+    "Extension"
+  ]
+}

+ 55 - 0
samples/Sample8_CommandLibrary/CommandLibraryExtension.cs

@@ -0,0 +1,55 @@
+using PixiEditor.Extensions.CommonApi.Commands;
+using PixiEditor.Extensions.Sdk;
+
+namespace Sample8_CommandLibrary;
+
+public class CommandLibraryExtension : PixiEditorExtension
+{
+    public override void OnInitialized()
+    {
+        CommandMetadata publicCommand = new CommandMetadata("PrintHelloWorld")
+        {
+            // All extensions can invoke this command
+            InvokePermissions = InvokePermissions.Public
+        };
+
+        CommandMetadata internalCommand = new CommandMetadata("PrintHelloWorldFamily")
+        {
+            // All extensions with unique name starting with "yourCompany" can invoke this command
+            InvokePermissions = InvokePermissions.Family
+        };
+
+        CommandMetadata privateCommand = new CommandMetadata("PrintHelloWorldPrivate")
+        {
+            // Only this extension can invoke this command
+            InvokePermissions = InvokePermissions.Owner
+        };
+
+        CommandMetadata explicitCommand = new CommandMetadata("PrintHelloWorldExplicit")
+        {
+            // Only this extension and the ones listed in ExplicitlyAllowedExtensions can invoke this command
+            InvokePermissions = InvokePermissions.Explicit,
+            ExplicitlyAllowedExtensions = "yourCompany.Samples.Commands" // You can put multiple extensions by separating with ;
+        };
+
+        Api.Commands.RegisterCommand(publicCommand, () =>
+        {
+            Api.Logger.Log("Hello World from public command!");
+        });
+
+        Api.Commands.RegisterCommand(internalCommand, () =>
+        {
+            Api.Logger.Log("Hello World from internal command!");
+        });
+
+        Api.Commands.RegisterCommand(privateCommand, () =>
+        {
+            Api.Logger.Log("Hello World from private command!");
+        });
+
+        Api.Commands.RegisterCommand(explicitCommand, () =>
+        {
+            Api.Logger.Log("Hello World from explicit command!");
+        });
+    }
+}

+ 9 - 0
samples/Sample8_CommandLibrary/Program.cs

@@ -0,0 +1,9 @@
+namespace Sample8_CommandLibrary;
+
+public static class Program
+{
+    public static void Main()
+    {
+
+    }
+}

+ 39 - 0
samples/Sample8_CommandLibrary/Sample8_CommandLibrary.csproj

@@ -0,0 +1,39 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>net8.0</TargetFramework>
+        <RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
+        <OutputType>Exe</OutputType>
+        <PublishTrimmed>true</PublishTrimmed>
+        <WasmSingleFileBundle>true</WasmSingleFileBundle>
+        <GenerateExtensionPackage>true</GenerateExtensionPackage>
+        <PixiExtOutputPath>..\..\src\PixiEditor.Desktop\bin\Debug\net8.0\Extensions</PixiExtOutputPath>
+        <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+        <ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
+        <RootNamespace>Sample8_CommandLibrary</RootNamespace>
+    </PropertyGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <ItemGroup>
+        <ProjectReference Include="..\..\src\PixiEditor.Extensions.Sdk\PixiEditor.Extensions.Sdk.csproj"/>
+    </ItemGroup>
+
+    <ItemGroup>
+        <None Remove="extension.json"/>
+        <Content Include="extension.json">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <ItemGroup>
+        <Content Include="Localization\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.props"/>
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.targets"/>
+
+
+</Project>

+ 41 - 0
samples/Sample8_CommandLibrary/extension.json

@@ -0,0 +1,41 @@
+{
+  "displayName": "Sample Extension - Command Library",
+  "uniqueName": "yourCompany.Samples.CommandLibrary",
+  "description": "Commands Library that can be invoked by other extensions for PixiEditor",
+  "version": "1.0.0",
+  "localization": {
+    "languages": [
+      {
+        "name": "English",
+        "code": "en",
+        "localeFileName": "Localization/en.json"
+      },
+      {
+        "name": "Polish",
+        "code": "pl",
+        "localeFileName": "Localization/pl.json"
+      }
+    ]
+  },
+  "author": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "publisher": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "contributors": [
+    {
+      "name": "flabbet",
+      "email": "[email protected]",
+      "website": "https://github.com/flabbet"
+    }
+  ],
+  "license": "MIT",
+  "categories": [
+    "Extension"
+  ]
+}

+ 75 - 0
samples/Sample8_Commands/CommandsSampleExtension.cs

@@ -0,0 +1,75 @@
+using PixiEditor.Extensions.CommonApi.Commands;
+using PixiEditor.Extensions.Sdk;
+
+namespace Sample8_Menu;
+
+public class CommandsSampleExtension : PixiEditorExtension
+{
+    /// <summary>
+    ///     This method is called when extension is loaded.
+    ///  All extensions are first loaded and then initialized. This method is called before <see cref="OnInitialized"/>.
+    /// </summary>
+    public override void OnLoaded()
+    {
+    }
+
+    /// <summary>
+    ///     This method is called when extension is initialized. After this method is called, you can use Api property to access PixiEditor API.
+    /// </summary>
+    public override void OnInitialized()
+    {
+        // A good practice is to use localization keys instead of hardcoded strings.
+        // And add them to the localization file. Check Sample2_LocalizationSample for more information.
+
+        CommandMetadata firstCommand = new CommandMetadata("Loggers.WriteHello");
+        firstCommand.DisplayName = "Write Hello"; // can be localized
+        firstCommand.Description = "Writes Hello to the log"; // can be localized
+
+        // Either an icon key (https://github.com/PixiEditor/PixiEditor/blob/master/src/PixiEditor.UI.Common/Fonts/PixiPerfectIcons.axaml)
+        // or unicode character
+        firstCommand.Icon = "icon-terminal";
+        firstCommand.MenuItemPath = "AWESOME_LOGGER/Write Hello"; // AWESOME_LOGGER is taken from localization, same can be done for the rest
+        firstCommand.Shortcut = new Shortcut(Key.H, KeyModifiers.Control | KeyModifiers.Alt);
+
+        Api.Commands.RegisterCommand(firstCommand, () => { Api.Logger.Log("Hello from the command!"); });
+
+        int clickedCount = 0;
+        CommandMetadata secondCommand = new CommandMetadata("Loggers.WriteClickedCount");
+        secondCommand.DisplayName = "Write Clicked Count";
+        secondCommand.Description = "Writes clicked count to the log";
+        secondCommand.Icon = "icon-terminal";
+        secondCommand.MenuItemPath = "EDIT/Write Clicked Count"; // append to EDIT menu
+        secondCommand.Order = 1000; // Last
+
+        secondCommand.Shortcut = new Shortcut(Key.C, KeyModifiers.Control | KeyModifiers.Alt);
+        Api.Commands.RegisterCommand(secondCommand, () =>
+        {
+            clickedCount++;
+            Api.Logger.Log($"Clicked {clickedCount} times");
+        });
+
+
+        Api.Commands.InvokeCommand("PixiEditor.File.New");
+
+        if (Api.Commands.CommandExists("yourCompany.Samples.CommandLibrary:PrintHelloWorld"))
+        {
+            Api.Commands.InvokeCommand("yourCompany.Samples.CommandLibrary:PrintHelloWorld");
+        }
+
+        if (Api.Commands.CommandExists("yourCompany.Samples.CommandLibrary:PrintHelloWorldFamily"))
+        {
+            Api.Commands.InvokeCommand("yourCompany.Samples.CommandLibrary:PrintHelloWorldFamily");
+        }
+
+        if (Api.Commands.CommandExists("yourCompany.Samples.CommandLibrary:PrintHelloWorldPrivate"))
+        {
+            // This will log an error.
+            Api.Commands.InvokeCommand("yourCompany.Samples.CommandLibrary:PrintHelloWorldPrivate");
+        }
+
+        if (Api.Commands.CommandExists("yourCompany.Samples.CommandLibrary:PrintHelloWorldExplicit"))
+        {
+            Api.Commands.InvokeCommand("yourCompany.Samples.CommandLibrary:PrintHelloWorldExplicit");
+        }
+    }
+}

+ 3 - 0
samples/Sample8_Commands/Localization/en.json

@@ -0,0 +1,3 @@
+{
+  "AWESOME_LOGGER": "Awesome Logger"
+}

+ 3 - 0
samples/Sample8_Commands/Localization/pl.json

@@ -0,0 +1,3 @@
+{
+  "AWESOME_LOGGER": "Znakomity rejestrator"
+}

+ 9 - 0
samples/Sample8_Commands/Program.cs

@@ -0,0 +1,9 @@
+namespace Sample8_Commands;
+
+public static class Program
+{
+    public static void Main()
+    {
+        
+    }
+}

+ 39 - 0
samples/Sample8_Commands/Sample8_Commands.csproj

@@ -0,0 +1,39 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>net8.0</TargetFramework>
+        <RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
+        <OutputType>Exe</OutputType>
+        <PublishTrimmed>true</PublishTrimmed>
+        <WasmSingleFileBundle>true</WasmSingleFileBundle>
+        <GenerateExtensionPackage>true</GenerateExtensionPackage>
+        <PixiExtOutputPath>..\..\src\PixiEditor.Desktop\bin\Debug\net8.0\Extensions</PixiExtOutputPath>
+        <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+        <ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
+        <RootNamespace>Sample8_Commands</RootNamespace>
+    </PropertyGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <ItemGroup>
+        <ProjectReference Include="..\..\src\PixiEditor.Extensions.Sdk\PixiEditor.Extensions.Sdk.csproj"/>
+    </ItemGroup>
+
+    <ItemGroup>
+        <None Remove="extension.json"/>
+        <Content Include="extension.json">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <ItemGroup>
+        <Content Include="Localization\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.props"/>
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.targets"/>
+
+
+</Project>

+ 41 - 0
samples/Sample8_Commands/extension.json

@@ -0,0 +1,41 @@
+{
+  "displayName": "Sample Extension - Commands",
+  "uniqueName": "yourCompany.Samples.Commands",
+  "description": "Sample Commands extension for PixiEditor",
+  "version": "1.0.0",
+  "localization": {
+    "languages": [
+      {
+        "name": "English",
+        "code": "en",
+        "localeFileName": "Localization/en.json"
+      },
+      {
+        "name": "Polish",
+        "code": "pl",
+        "localeFileName": "Localization/pl.json"
+      }
+    ]
+  },
+  "author": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "publisher": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "contributors": [
+    {
+      "name": "flabbet",
+      "email": "[email protected]",
+      "website": "https://github.com/flabbet"
+    }
+  ],
+  "license": "MIT",
+  "categories": [
+    "Extension"
+  ]
+}

+ 24 - 0
samples/Sample9_Document/CommandsSampleExtension.cs

@@ -0,0 +1,24 @@
+using PixiEditor.Extensions.CommonApi.Commands;
+using PixiEditor.Extensions.Sdk;
+
+namespace Sample9_Commands;
+
+public class CommandsSampleExtension : PixiEditorExtension
+{
+    /// <summary>
+    ///     This method is called when extension is loaded.
+    ///  All extensions are first loaded and then initialized. This method is called before <see cref="OnInitialized"/>.
+    /// </summary>
+    public override void OnLoaded()
+    {
+    }
+
+    /// <summary>
+    ///     This method is called when extension is initialized. After this method is called, you can use Api property to access PixiEditor API.
+    /// </summary>
+    public override void OnInitialized()
+    {
+        var doc = Api.Documents.ImportFile("Resources/cs.png", true); // Open file from the extension resources
+        doc?.Resize(128, 128); // Resizes whole document
+    }
+}

+ 8 - 0
samples/Sample9_Document/Program.cs

@@ -0,0 +1,8 @@
+namespace Sample9_Commands;
+
+public static class Program
+{
+    public static void Main()
+    {
+    }
+}

BIN
samples/Sample9_Document/Resources/cs.png


+ 45 - 0
samples/Sample9_Document/Sample9_Document.csproj

@@ -0,0 +1,45 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>net8.0</TargetFramework>
+        <RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
+        <OutputType>Exe</OutputType>
+        <PublishTrimmed>true</PublishTrimmed>
+        <WasmSingleFileBundle>true</WasmSingleFileBundle>
+        <GenerateExtensionPackage>true</GenerateExtensionPackage>
+        <PixiExtOutputPath>..\..\src\PixiEditor.Desktop\bin\Debug\net8.0\Extensions</PixiExtOutputPath>
+        <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+        <ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
+        <RootNamespace>Sample9_Commands</RootNamespace>
+    </PropertyGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <ItemGroup>
+        <ProjectReference Include="..\..\src\PixiEditor.Extensions.Sdk\PixiEditor.Extensions.Sdk.csproj"/>
+    </ItemGroup>
+
+    <ItemGroup>
+        <None Remove="extension.json"/>
+        <Content Include="extension.json">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <ItemGroup>
+        <Content Include="Localization\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <ItemGroup>
+        <Content Include="Resources\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.props"/>
+    <Import Project="..\..\src\PixiEditor.Extensions.Sdk\build\PixiEditor.Extensions.Sdk.targets"/>
+
+
+</Project>

+ 44 - 0
samples/Sample9_Document/extension.json

@@ -0,0 +1,44 @@
+{
+  "displayName": "Sample Extension - Document",
+  "uniqueName": "yourCompany.Samples.Document",
+  "description": "Sample Document extension for PixiEditor",
+  "version": "1.0.0",
+  "localization": {
+    "languages": [
+      {
+        "name": "English",
+        "code": "en",
+        "localeFileName": "Localization/en.json"
+      },
+      {
+        "name": "Polish",
+        "code": "pl",
+        "localeFileName": "Localization/pl.json"
+      }
+    ]
+  },
+  "author": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "publisher": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "contributors": [
+    {
+      "name": "flabbet",
+      "email": "[email protected]",
+      "website": "https://github.com/flabbet"
+    }
+  ],
+  "license": "MIT",
+  "categories": [
+    "Extension"
+  ],
+  "permissions": [
+    "OpenDocuments"
+  ]
+}

+ 7 - 0
samples/global.json

@@ -0,0 +1,7 @@
+{
+  "sdk": {
+    "version": "8.0.405",
+    "rollForward": "latestMinor",
+    "allowPrerelease": false
+  }
+}

+ 20 - 0
samples/stylecop.json

@@ -0,0 +1,20 @@
+{
+  "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
+  "settings": {
+    "indentation": {
+      "indentationSize": 4
+    },
+    "maintainabilityRules": {
+      "topLevelTypes": [ "class", "interface", "enum", "struct" ]
+    },
+    "readabilityRules": {
+      "allowBuiltInTypeAliases": false
+    },
+    "documentationRules": {
+      "xmlHeader": false,
+      "documentInterfaces": false,
+      "documentExposedElements": false,
+      "documentInternalElements": false
+    }
+  }
+}

+ 13 - 0
src/.config/dotnet-tools.json

@@ -0,0 +1,13 @@
+{
+  "version": 1,
+  "isRoot": true,
+  "tools": {
+    "dotnet-dump": {
+      "version": "9.0.607501",
+      "commands": [
+        "dotnet-dump"
+      ],
+      "rollForward": false
+    }
+  }
+}

+ 47 - 18
src/ChunkyImageLib/Chunk.cs

@@ -1,7 +1,10 @@
 using ChunkyImageLib.DataHolders;
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using Drawie.Backend.Core;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Numerics;
 
 namespace ChunkyImageLib;
 
@@ -20,7 +23,18 @@ public class Chunk : IDisposable
     /// <summary>
     /// The surface of the chunk
     /// </summary>
-    public Surface Surface { get; }
+    public Surface Surface
+    {
+        get
+        {
+            if (returned)
+            {
+                throw new ObjectDisposedException("Chunk has been disposed");
+            }
+
+            return internalSurface;
+        }
+    }
 
     /// <summary>
     /// The size of the chunk
@@ -31,21 +45,34 @@ public class Chunk : IDisposable
     /// The resolution of the chunk
     /// </summary>
     public ChunkResolution Resolution { get; }
-    private Chunk(ChunkResolution resolution)
+
+    public ColorSpace ColorSpace { get; }
+
+    public bool Disposed => returned;
+
+    private Surface internalSurface;
+
+    private Chunk(ChunkResolution resolution, ColorSpace colorSpace)
     {
         int size = resolution.PixelSize();
 
         Resolution = resolution;
+        ColorSpace = colorSpace;
         PixelSize = new(size, size);
-        Surface = new Surface(PixelSize);
+        internalSurface = new Surface(new ImageInfo(size, size, ColorType.RgbaF16, AlphaType.Premul, colorSpace));
     }
 
     /// <summary>
     /// Tries to take a chunk with the <paramref name="resolution"/> from the pool, or creates a new one
     /// </summary>
-    public static Chunk Create(ChunkResolution resolution = ChunkResolution.Full)
+    public static Chunk Create(ColorSpace chunkCs, ChunkResolution resolution = ChunkResolution.Full)
     {
-        var chunk = ChunkPool.Instance.Get(resolution) ?? new Chunk(resolution);
+        var chunk = ChunkPool.Instance.Get(resolution, chunkCs);
+        if (chunk == null || chunk.Disposed)
+        {
+            chunk = new Chunk(resolution, chunkCs);
+        }
+
         chunk.returned = false;
         Interlocked.Increment(ref chunkCounter);
         return chunk;
@@ -56,22 +83,24 @@ public class Chunk : IDisposable
     /// </summary>
     /// <param name="pos">The destination for the <paramref name="surface"/></param>
     /// <param name="paint">The paint to use while drawing</param>
-    public void DrawOnSurface(DrawingSurface surface, VecI pos, Paint? paint = null)
+    public void DrawChunkOn(DrawingSurface surface, VecD pos, Paint? paint = null)
     {
-        surface.Canvas.DrawSurface(Surface.DrawingSurface, pos.X, pos.Y, paint);
+        surface.Canvas.DrawSurface(Surface.DrawingSurface, (float)pos.X, (float)pos.Y, paint);
     }
-    
+
     public unsafe RectI? FindPreciseBounds(RectI? passedSearchRegion = null)
     {
         RectI? bounds = null;
-        if (returned) 
+        if (returned)
             return bounds;
 
-        if (passedSearchRegion is not null && !new RectI(VecI.Zero, Surface.Size).ContainsInclusive(passedSearchRegion.Value))
-            throw new ArgumentException("Passed search region lies outside of the chunk's surface", nameof(passedSearchRegion));
+        if (passedSearchRegion is not null &&
+            !new RectI(VecI.Zero, Surface.Size).ContainsInclusive(passedSearchRegion.Value))
+            throw new ArgumentException("Passed search region lies outside of the chunk's surface",
+                nameof(passedSearchRegion));
 
         RectI searchRegion = passedSearchRegion ?? new RectI(VecI.Zero, Surface.Size);
-        
+
         ulong* ptr = (ulong*)Surface.PixelBuffer;
         for (int y = searchRegion.Top; y < searchRegion.Bottom; y++)
         {
@@ -87,7 +116,7 @@ public class Chunk : IDisposable
                 }
             }
         }
-        
+
         return bounds;
     }
 
@@ -98,9 +127,9 @@ public class Chunk : IDisposable
     {
         if (returned)
             return;
-        returned = true;
         Interlocked.Decrement(ref chunkCounter);
-        Surface.DrawingSurface.Canvas.RestoreToCount(-1);
+        Surface.DrawingSurface.Canvas.Clear();
         ChunkPool.Instance.Push(this);
+        returned = true;
     }
 }

+ 19 - 14
src/ChunkyImageLib/ChunkPool.cs

@@ -1,5 +1,6 @@
 using ChunkyImageLib.DataHolders;
 using System.Collections.Concurrent;
+using Drawie.Backend.Core.Surfaces.ImageData;
 
 namespace ChunkyImageLib;
 
@@ -10,6 +11,7 @@ internal class ChunkPool
 
     private static object lockObj = new();
     private static ChunkPool? instance;
+
     /// <summary>
     /// The instance of the <see cref="ChunkPool"/>
     /// </summary>
@@ -24,30 +26,33 @@ internal class ChunkPool
                     instance ??= new ChunkPool();
                 }
             }
+
             return instance;
         }
     }
 
-    private readonly ConcurrentBag<Chunk> fullChunks = new();
-    private readonly ConcurrentBag<Chunk> halfChunks = new();
-    private readonly ConcurrentBag<Chunk> quarterChunks = new();
-    private readonly ConcurrentBag<Chunk> eighthChunks = new();
-    
+    private readonly ConcurrentDictionary<ColorSpace, ConcurrentBag<Chunk>> fullChunks = new();
+    private readonly ConcurrentDictionary<ColorSpace, ConcurrentBag<Chunk>> halfChunks = new();
+    private readonly ConcurrentDictionary<ColorSpace, ConcurrentBag<Chunk>> quarterChunks = new();
+    private readonly ConcurrentDictionary<ColorSpace, ConcurrentBag<Chunk>> eighthChunks = new();
+
     /// <summary>
     /// Tries to take a chunk from the pool, returns null if there's no Chunk available
     /// </summary>
     /// <param name="resolution">The resolution for the chunk</param>
-    internal Chunk? Get(ChunkResolution resolution) => GetBag(resolution).TryTake(out Chunk? item) ? item : null;
+    /// <param name="chunkCs"></param>
+    internal Chunk? Get(ChunkResolution resolution, ColorSpace chunkCs) =>
+        GetBag(resolution, chunkCs).TryTake(out Chunk? item) ? item : null;
 
-    private ConcurrentBag<Chunk> GetBag(ChunkResolution resolution)
+    private ConcurrentBag<Chunk> GetBag(ChunkResolution resolution, ColorSpace colorSpace)
     {
         return resolution switch
         {
-            ChunkResolution.Full => fullChunks,
-            ChunkResolution.Half => halfChunks,
-            ChunkResolution.Quarter => quarterChunks,
-            ChunkResolution.Eighth => eighthChunks,
-            _ => fullChunks
+            ChunkResolution.Full => fullChunks.GetOrAdd(colorSpace, _ => new ConcurrentBag<Chunk>()),
+            ChunkResolution.Half => halfChunks.GetOrAdd(colorSpace, _ => new ConcurrentBag<Chunk>()),
+            ChunkResolution.Quarter => quarterChunks.GetOrAdd(colorSpace, _ => new ConcurrentBag<Chunk>()),
+            ChunkResolution.Eighth => eighthChunks.GetOrAdd(colorSpace, _ => new ConcurrentBag<Chunk>()),
+            _ => fullChunks.GetOrAdd(colorSpace, _ => new ConcurrentBag<Chunk>()),
         };
     }
 
@@ -56,9 +61,9 @@ internal class ChunkPool
     /// </summary>
     internal void Push(Chunk chunk)
     {
-        var chunks = GetBag(chunk.Resolution);
+        var chunks = GetBag(chunk.Resolution, chunk.ColorSpace);
         //a race condition can cause the count to go above 200, but likely not by much
-        if (chunks.Count < 200)
+        if (chunks.Count <= 32)
             chunks.Add(chunk);
         else
             chunk.Surface.Dispose();

+ 286 - 77
src/ChunkyImageLib/ChunkyImage.cs

@@ -4,11 +4,16 @@ using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
 using OneOf;
 using OneOf.Types;
-using PixiEditor.DrawingApi.Core.ColorsImpl;
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
-using PixiEditor.DrawingApi.Core.Surface.Vector;
+using PixiEditor.Common;
+using Drawie.Backend.Core;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Backend.Core.Vector;
+using Drawie.Numerics;
 
 [assembly: InternalsVisibleTo("ChunkyImageLibTest")]
 
@@ -39,7 +44,7 @@ namespace ChunkyImageLib;
 ///     - Any other blend mode: the latest chunks contain only the things drawn by the queued operations.
 ///         They need to be drawn over the committed chunks to obtain the final image. In this case, operations won't have access to the existing pixels. 
 /// </summary>
-public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
+public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICacheable
 {
     private struct LatestChunkData
     {
@@ -57,14 +62,24 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     private readonly object lockObject = new();
     private int commitCounter = 0;
 
+    private RectI cachedPreciseBounds = RectI.Empty;
+    private int lastBoundsCacheHash = -1;
+
     public const int FullChunkSize = ChunkPool.FullChunkSize;
     private static Paint ClippingPaint { get; } = new Paint() { BlendMode = BlendMode.DstIn };
     private static Paint InverseClippingPaint { get; } = new Paint() { BlendMode = BlendMode.DstOut };
     private static Paint ReplacingPaint { get; } = new Paint() { BlendMode = BlendMode.Src };
-    private static Paint SmoothReplacingPaint { get; } = new Paint() { BlendMode = BlendMode.Src, FilterQuality = FilterQuality.Medium };
+
+    private static Paint SmoothReplacingPaint { get; } =
+        new Paint() { BlendMode = BlendMode.Src, FilterQuality = FilterQuality.Medium };
+
     private static Paint AddingPaint { get; } = new Paint() { BlendMode = BlendMode.Plus };
     private readonly Paint blendModePaint = new Paint() { BlendMode = BlendMode.Src };
 
+    public ColorSpace ProcessingColorSpace { get; set; }
+
+    public int CommitCounter => commitCounter;
+
     public VecI CommittedSize { get; private set; }
     public VecI LatestSize { get; private set; }
 
@@ -85,11 +100,13 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     private double? horizontalSymmetryAxis = null;
     private double? verticalSymmetryAxis = null;
 
+    private int operationCounter = 0;
+
     private readonly Dictionary<ChunkResolution, Dictionary<VecI, Chunk>> committedChunks;
     private readonly Dictionary<ChunkResolution, Dictionary<VecI, Chunk>> latestChunks;
     private readonly Dictionary<ChunkResolution, Dictionary<VecI, LatestChunkData>> latestChunksData;
 
-    public ChunkyImage(VecI size)
+    public ChunkyImage(VecI size, ColorSpace colorSpace)
     {
         CommittedSize = size;
         LatestSize = size;
@@ -114,6 +131,14 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             [ChunkResolution.Quarter] = new(),
             [ChunkResolution.Eighth] = new(),
         };
+
+        ProcessingColorSpace = colorSpace;
+    }
+
+    public ChunkyImage(Surface image, ColorSpace colorSpace) : this(image.Size, colorSpace)
+    {
+        EnqueueDrawImage(VecI.Zero, image);
+        CommitChanges();
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
@@ -129,6 +154,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 rect ??= chunkBounds;
                 rect = rect.Value.Union(chunkBounds);
             }
+
             foreach (var operation in queuedOperations)
             {
                 foreach (var pos in operation.affectedArea.Chunks)
@@ -138,6 +164,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                     rect = rect.Value.Union(chunkBounds);
                 }
             }
+
             return rect;
         }
     }
@@ -155,6 +182,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 rect ??= chunkBounds;
                 rect = rect.Value.Union(chunkBounds);
             }
+
             return rect;
         }
     }
@@ -163,22 +191,29 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     /// Finds the precise bounds in <paramref name="suggestedResolution"/>. If there are no chunks rendered for that resolution, full res chunks are used instead.
     /// </summary>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public RectI? FindTightCommittedBounds(ChunkResolution suggestedResolution = ChunkResolution.Full)
+    public RectI? FindTightCommittedBounds(ChunkResolution suggestedResolution = ChunkResolution.Full, bool fallbackToChunkAligned = false)
     {
         lock (lockObject)
         {
             ThrowIfDisposed();
 
+            if (lastBoundsCacheHash == GetCacheHash())
+            {
+                return cachedPreciseBounds;
+            }
+
             var chunkSize = suggestedResolution.PixelSize();
             var multiplier = suggestedResolution.Multiplier();
             RectI scaledCommittedSize = (RectI)(new RectD(VecI.Zero, CommittedSize * multiplier)).RoundOutwards();
 
             RectI? preciseBounds = null;
+
             foreach (var (chunkPos, fullResChunk) in committedChunks[ChunkResolution.Full])
             {
                 if (committedChunks[suggestedResolution].TryGetValue(chunkPos, out Chunk? requestedResChunk))
                 {
-                    RectI visibleArea = new RectI(chunkPos * chunkSize, new VecI(chunkSize)).Intersect(scaledCommittedSize).Translate(-chunkPos * chunkSize);
+                    RectI visibleArea = new RectI(chunkPos * chunkSize, new VecI(chunkSize))
+                        .Intersect(scaledCommittedSize).Translate(-chunkPos * chunkSize);
 
                     RectI? chunkPreciseBounds = requestedResChunk.FindPreciseBounds(visibleArea);
                     if (chunkPreciseBounds is null)
@@ -190,20 +225,31 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 }
                 else
                 {
-                    RectI visibleArea = new RectI(chunkPos * FullChunkSize, new VecI(FullChunkSize)).Intersect(new RectI(VecI.Zero, CommittedSize)).Translate(-chunkPos * FullChunkSize);
+                    if (fallbackToChunkAligned)
+                    {
+                        return FindChunkAlignedCommittedBounds();
+                    }
+
+                    RectI visibleArea = new RectI(chunkPos * FullChunkSize, new VecI(FullChunkSize))
+                        .Intersect(new RectI(VecI.Zero, CommittedSize)).Translate(-chunkPos * FullChunkSize);
 
                     RectI? chunkPreciseBounds = fullResChunk.FindPreciseBounds(visibleArea);
                     if (chunkPreciseBounds is null)
                         continue;
-                    RectI globalChunkBounds = (RectI)chunkPreciseBounds.Value.Scale(multiplier).Offset(chunkPos * chunkSize).RoundOutwards();
+                    RectI globalChunkBounds = (RectI)chunkPreciseBounds.Value.Scale(multiplier)
+                        .Offset(chunkPos * chunkSize).RoundOutwards();
 
                     preciseBounds ??= globalChunkBounds;
                     preciseBounds = preciseBounds.Value.Union(globalChunkBounds);
                 }
             }
+
             preciseBounds = (RectI?)preciseBounds?.Scale(suggestedResolution.InvertedMultiplier()).RoundOutwards();
             preciseBounds = preciseBounds?.Intersect(new RectI(preciseBounds.Value.Pos, CommittedSize));
 
+            cachedPreciseBounds = preciseBounds.GetValueOrDefault();
+            lastBoundsCacheHash = GetCacheHash();
+
             return preciseBounds;
         }
     }
@@ -214,7 +260,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         lock (lockObject)
         {
             ThrowIfDisposed();
-            ChunkyImage output = new(LatestSize);
+            ChunkyImage output = new(LatestSize, ProcessingColorSpace);
             var chunks = FindCommittedChunks();
             foreach (var chunk in chunks)
             {
@@ -240,7 +286,23 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             return MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full) switch
             {
                 null => Colors.Transparent,
-                var chunk => chunk.Surface.GetSRGBPixel(posInChunk)
+                var chunk => chunk.Surface.GetSrgbPixel(posInChunk)
+            };
+        }
+    }
+
+    /// <exception cref="ObjectDisposedException">This image is disposed</exception>
+    public Color GetCommittedPixelRaw(VecI posOnImage)
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            var chunkPos = OperationHelper.GetChunkPos(posOnImage, FullChunkSize);
+            var posInChunk = posOnImage - chunkPos * FullChunkSize;
+            return MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full) switch
+            {
+                null => Colors.Transparent,
+                var chunk => chunk.Surface.GetRawPixel(posInChunk)
             };
         }
     }
@@ -261,7 +323,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 return committedChunk switch
                 {
                     null => Colors.Transparent,
-                    _ => committedChunk.Surface.GetSRGBPixel(posInChunk)
+                    _ => committedChunk.Surface.GetSrgbPixel(posInChunk)
                 };
             }
 
@@ -272,7 +334,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 return latestChunk switch
                 {
                     null => Colors.Transparent,
-                    _ => latestChunk.Surface.GetSRGBPixel(posInChunk)
+                    _ => latestChunk.Surface.GetSrgbPixel(posInChunk)
                 };
             }
 
@@ -280,20 +342,20 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             {
                 Chunk? committedChunk = MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full);
                 Chunk? latestChunk = GetLatestChunk(chunkPos, ChunkResolution.Full);
-                Color committedColor = committedChunk is null ?
-                    Colors.Transparent :
-                    committedChunk.Surface.GetSRGBPixel(posInChunk);
-                Color latestColor = latestChunk is null ?
-                    Colors.Transparent :
-                    latestChunk.Surface.GetSRGBPixel(posInChunk);
+                Color committedColor = committedChunk is null
+                    ? Colors.Transparent
+                    : committedChunk.Surface.GetSrgbPixel(posInChunk);
+                Color latestColor = latestChunk is null
+                    ? Colors.Transparent
+                    : latestChunk.Surface.GetSrgbPixel(posInChunk);
                 // using a whole chunk just to draw 1 pixel is kinda dumb,
                 // but this should be faster than any approach that requires allocations
-                using Chunk tempChunk = Chunk.Create(ChunkResolution.Eighth);
+                using Chunk tempChunk = Chunk.Create(ProcessingColorSpace, ChunkResolution.Eighth);
                 using Paint committedPaint = new Paint() { Color = committedColor, BlendMode = BlendMode.Src };
                 using Paint latestPaint = new Paint() { Color = latestColor, BlendMode = this.blendMode };
                 tempChunk.Surface.DrawingSurface.Canvas.DrawPixel(VecI.Zero, committedPaint);
                 tempChunk.Surface.DrawingSurface.Canvas.DrawPixel(VecI.Zero, latestPaint);
-                return tempChunk.Surface.GetSRGBPixel(VecI.Zero);
+                return tempChunk.Surface.GetSrgbPixel(VecI.Zero);
             }
         }
     }
@@ -302,7 +364,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     /// True if the chunk existed and was drawn, otherwise false
     /// </returns>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null)
+    public bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos,
+        Paint? paint = null)
     {
         lock (lockObject)
         {
@@ -327,7 +390,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             {
                 if (committedChunk is null)
                     return false;
-                committedChunk.DrawOnSurface(surface, pos, paint);
+                committedChunk.DrawChunkOn(surface, pos, paint);
                 return true;
             }
 
@@ -336,7 +399,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             {
                 if (latestChunk.IsT2)
                 {
-                    latestChunk.AsT2.DrawOnSurface(surface, pos, paint);
+                    latestChunk.AsT2.DrawChunkOn(surface, pos, paint);
                     return true;
                 }
 
@@ -344,13 +407,15 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             }
 
             // combine with committed and then draw
-            using var tempChunk = Chunk.Create(resolution);
-            tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(committedChunk.Surface.DrawingSurface, 0, 0, ReplacingPaint);
+            using var tempChunk = Chunk.Create(ProcessingColorSpace, resolution);
+            tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(committedChunk.Surface.DrawingSurface, 0, 0,
+                ReplacingPaint);
             blendModePaint.BlendMode = blendMode;
-            tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(latestChunk.AsT2.Surface.DrawingSurface, 0, 0, blendModePaint);
+            tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(latestChunk.AsT2.Surface.DrawingSurface, 0, 0,
+                blendModePaint);
             if (lockTransparency)
                 OperationHelper.ClampAlpha(tempChunk.Surface.DrawingSurface, committedChunk.Surface.DrawingSurface);
-            tempChunk.DrawOnSurface(surface, pos, paint);
+            tempChunk.DrawChunkOn(surface, pos, paint);
 
             return true;
         }
@@ -375,8 +440,25 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
     }
 
+    public bool LatestOrCommittedChunkExists()
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            var chunks = FindAllChunks();
+            foreach (var chunk in chunks)
+            {
+                if (LatestOrCommittedChunkExists(chunk))
+                    return true;
+            }
+        }
+
+        return false;
+    }
+
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null)
+    public bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos,
+        Paint? paint = null)
     {
         lock (lockObject)
         {
@@ -384,7 +466,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             var chunk = GetCommittedChunk(chunkPos, resolution);
             if (chunk is null)
                 return false;
-            chunk.DrawOnSurface(surface, pos, paint);
+            chunk.DrawChunkOn(surface, pos, paint);
             return true;
         }
     }
@@ -414,7 +496,6 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 
     /// <summary>
     /// Tries it's best to return a committed chunk, either if it exists or if it can be created from it's high res version. Returns null if it can't.
-    /// </summary>
     private Chunk? GetCommittedChunk(VecI pos, ChunkResolution resolution)
     {
         var maybeSameRes = MaybeGetCommittedChunk(pos, resolution);
@@ -441,7 +522,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         {
             ThrowIfDisposed();
             if (queuedOperations.Count > 0)
-                throw new InvalidOperationException("This function can only be executed when there are no queued operations");
+                throw new InvalidOperationException(
+                    "This function can only be executed when there are no queued operations");
             activeClips.Add(clippingMask);
         }
     }
@@ -453,7 +535,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         {
             ThrowIfDisposed();
             if (queuedOperations.Count > 0)
-                throw new InvalidOperationException("This function can only be executed when there are no queued operations");
+                throw new InvalidOperationException(
+                    "This function can only be executed when there are no queued operations");
             this.clippingPath = clippingPath;
         }
     }
@@ -468,7 +551,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         {
             ThrowIfDisposed();
             if (queuedOperations.Count > 0)
-                throw new InvalidOperationException("This function can only be executed when there are no queued operations");
+                throw new InvalidOperationException(
+                    "This function can only be executed when there are no queued operations");
             blendMode = mode;
         }
     }
@@ -480,7 +564,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         {
             ThrowIfDisposed();
             if (queuedOperations.Count > 0)
-                throw new InvalidOperationException("This function can only be executed when there are no queued operations");
+                throw new InvalidOperationException(
+                    "This function can only be executed when there are no queued operations");
             horizontalSymmetryAxis = position;
         }
     }
@@ -492,7 +577,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         {
             ThrowIfDisposed();
             if (queuedOperations.Count > 0)
-                throw new InvalidOperationException("This function can only be executed when there are no queued operations");
+                throw new InvalidOperationException(
+                    "This function can only be executed when there are no queued operations");
             verticalSymmetryAxis = position;
         }
     }
@@ -530,12 +616,15 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawEllipse(RectI location, Color strokeColor, Color fillColor, int strokeWidth, Paint? paint = null)
+    public void EnqueueDrawEllipse(RectD location, Paintable? strokeColor, Paintable? fillColor, float strokeWidth,
+        double rotationRad = 0, bool antiAliased = false,
+        Paint? paint = null)
     {
         lock (lockObject)
         {
             ThrowIfDisposed();
-            EllipseOperation operation = new(location, strokeColor, fillColor, strokeWidth, paint);
+            EllipseOperation operation = new(location, strokeColor, fillColor, strokeWidth, rotationRad, antiAliased,
+                paint);
             EnqueueOperation(operation);
         }
     }
@@ -584,7 +673,52 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             EnqueueOperation(operation);
         }
     }
-    
+
+    /// <summary>
+    /// Be careful about the copyImage argument. The default is true, and this is a thread safe version without any side effects. 
+    /// It will however copy the surface right away which can be slow (in updateable changes especially). 
+    /// If copyImage is set to false, the image won't be copied and instead a reference will be stored.
+    /// Texture is NOT THREAD SAFE, so if you pass a Texture here with copyImage == false you must not do anything with that texture anywhere (not even read) until CommitChanges/CancelChanges is called.
+    /// </summary>
+    /// <exception cref="ObjectDisposedException">This image is disposed</exception>
+    public void EnqueueDrawTexture(Matrix3X3 transformMatrix, Texture image, Paint? paint = null, bool copyImage = true)
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            TextureOperation operation = new(transformMatrix, image, paint, copyImage);
+            EnqueueOperation(operation);
+        }
+    }
+
+    /// <summary>
+    /// Be careful about the copyImage argument, see other overload for details
+    /// </summary>
+    /// <exception cref="ObjectDisposedException">This image is disposed</exception>
+    public void EnqueueDrawTexture(ShapeCorners corners, Texture image, Paint? paint = null, bool copyImage = true)
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            TextureOperation operation = new(corners, image, paint, copyImage);
+            EnqueueOperation(operation);
+        }
+    }
+
+    /// <summary>
+    /// Be careful about the copyImage argument, see other overload for details
+    /// </summary>
+    /// <exception cref="ObjectDisposedException">This image is disposed</exception>
+    public void EnqueueDrawTexture(VecI pos, Texture image, Paint? paint = null, bool copyImage = true)
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            TextureOperation operation = new(pos, image, paint, copyImage);
+            EnqueueOperation(operation);
+        }
+    }
+
     public void EnqueueApplyMask(ChunkyImage mask)
     {
         lock (lockObject)
@@ -597,7 +731,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 
     /// <param name="customBounds">Bounds used for affected chunks, will be computed from path in O(n) if null is passed</param>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawPath(VectorPath path, Color color, float strokeWidth, StrokeCap strokeCap, BlendMode blendMode, RectI? customBounds = null)
+    public void EnqueueDrawPath(VectorPath path, Color color, float strokeWidth, StrokeCap strokeCap,
+        BlendMode blendMode, RectI? customBounds = null)
     {
         lock (lockObject)
         {
@@ -608,18 +743,19 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawBresenhamLine(VecI from, VecI to, Color color, BlendMode blendMode)
+    public void EnqueueDrawBresenhamLine(VecI from, VecI to, Paintable paintable, BlendMode blendMode)
     {
         lock (lockObject)
         {
             ThrowIfDisposed();
-            BresenhamLineOperation operation = new(from, to, color, blendMode);
+            BresenhamLineOperation operation = new(from, to, paintable, blendMode);
             EnqueueOperation(operation);
         }
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawSkiaLine(VecI from, VecI to, StrokeCap strokeCap, float strokeWidth, Color color, BlendMode blendMode)
+    public void EnqueueDrawSkiaLine(VecD from, VecD to, StrokeCap strokeCap, float strokeWidth, Color color,
+        BlendMode blendMode)
     {
         lock (lockObject)
         {
@@ -629,6 +765,16 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
     }
 
+    public void EnqueueDrawSkiaLine(VecD from, VecD to, Paint paint)
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            DrawingSurfaceLineOperation operation = new(from, to, paint);
+            EnqueueOperation(operation);
+        }
+    }
+
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     public void EnqueueDrawPixels(IEnumerable<VecI> pixels, Color color, BlendMode blendMode)
     {
@@ -663,16 +809,23 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawChunkyImage(VecI pos, ChunkyImage image, bool flipHor = false, bool flipVer = false)
+    public void EnqueueDrawCommitedChunkyImage(VecI pos, ChunkyImage image, bool flipHor = false, bool flipVer = false)
     {
         lock (lockObject)
         {
             ThrowIfDisposed();
-            ChunkyImageOperation operation = new(image, pos, flipHor, flipVer);
+            ChunkyImageOperation operation = new(image, pos, flipHor, flipVer, false);
             EnqueueOperation(operation);
         }
     }
 
+    public void EnqueueDrawUpToDateChunkyImage(VecI pos, ChunkyImage image, bool flipHor = false, bool flipVer = false)
+    {
+        ThrowIfDisposed();
+        ChunkyImageOperation operation = new(image, pos, flipHor, flipVer, true);
+        EnqueueOperation(operation);
+    }
+
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     public void EnqueueClearRegion(RectI region)
     {
@@ -718,6 +871,17 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
     }
 
+
+    public void EnqueueDrawPaint(Paint paint)
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            PaintOperation operation = new(paint);
+            EnqueueOperation(operation);
+        }
+    }
+
     private void EnqueueOperation(IDrawOperation operation)
     {
         List<IDrawOperation> operations = new(4) { operation };
@@ -740,6 +904,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             if (operation.IgnoreEmptyChunks)
                 area.Chunks.IntersectWith(FindAllChunks());
             EnqueueOperation(op, area);
+            operationCounter++;
         }
     }
 
@@ -835,7 +1000,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 {
                     if (resolution == ChunkResolution.Full)
                     {
-                        throw new InvalidOperationException("Trying to commit a full res chunk that wasn't fully processed");
+                        throw new InvalidOperationException(
+                            "Trying to commit a full res chunk that wasn't fully processed");
                     }
                     else
                     {
@@ -883,14 +1049,18 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                     blendModePaint.BlendMode = blendMode;
                     if (lockTransparency)
                     {
-                        using Chunk tempChunk = Chunk.Create(resolution);
-                        tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(maybeCommitted.Surface.DrawingSurface, 0, 0, ReplacingPaint);
-                        maybeCommitted.Surface.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, 0, 0, blendModePaint);
-                        OperationHelper.ClampAlpha(maybeCommitted.Surface.DrawingSurface, tempChunk.Surface.DrawingSurface);
+                        using Chunk tempChunk = Chunk.Create(ProcessingColorSpace, resolution);
+                        tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(maybeCommitted.Surface.DrawingSurface, 0, 0,
+                            ReplacingPaint);
+                        maybeCommitted.Surface.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, 0, 0,
+                            blendModePaint);
+                        OperationHelper.ClampAlpha(maybeCommitted.Surface.DrawingSurface,
+                            tempChunk.Surface.DrawingSurface);
                     }
                     else
                     {
-                        maybeCommitted.Surface.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, 0, 0, blendModePaint);
+                        maybeCommitted.Surface.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, 0, 0,
+                            blendModePaint);
                     }
 
                     chunk.Dispose();
@@ -905,7 +1075,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             {
                 if (resolution == ChunkResolution.Full)
                     continue;
-                if (!latestChunksData[resolution].TryGetValue(pos, out var halfChunk) || halfChunk.QueueProgress != queuedOperations.Count)
+                if (!latestChunksData[resolution].TryGetValue(pos, out var halfChunk) ||
+                    halfChunk.QueueProgress != queuedOperations.Count)
                 {
                     if (committedChunks[resolution].TryGetValue(pos, out var committedLowResChunk))
                     {
@@ -964,7 +1135,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             ThrowIfDisposed();
             var chunks = new HashSet<VecI>();
             RectI? rect = null;
-            
+
             for (int i = fromOperationIndex; i < queuedOperations.Count; i++)
             {
                 var (_, area) = queuedOperations[i];
@@ -979,13 +1150,25 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
     }
 
+    public void SetCommitedChunk(Chunk chunk, VecI pos, ChunkResolution resolution)
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            committedChunks[resolution][pos] = chunk;
+        }
+    }
+
     /// <summary>
     /// Applies all operations queued for a specific (latest) chunk. If the latest chunk doesn't exist yet, creates it. If none of the existing operations affect the chunk does nothing.
     /// </summary>
     private void MaybeCreateAndProcessQueueForChunk(VecI chunkPos, ChunkResolution resolution)
     {
         if (!latestChunksData[resolution].TryGetValue(chunkPos, out LatestChunkData chunkData))
-            chunkData = new() { QueueProgress = 0, IsDeleted = !committedChunks[ChunkResolution.Full].ContainsKey(chunkPos) };
+            chunkData = new()
+            {
+                QueueProgress = 0, IsDeleted = !committedChunks[ChunkResolution.Full].ContainsKey(chunkPos)
+            };
         if (chunkData.QueueProgress == queuedOperations.Count)
             return;
 
@@ -1008,12 +1191,14 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             }
 
             if (chunkData.QueueProgress <= i)
-                chunkData.IsDeleted = ApplyOperationToChunk(operation, affArea, combinedRasterClips, targetChunk!, chunkPos, resolution, chunkData);
+                chunkData.IsDeleted = ApplyOperationToChunk(operation, affArea, combinedRasterClips, targetChunk!,
+                    chunkPos, resolution, chunkData);
         }
 
         if (initialized)
         {
-            if (lockTransparency && !chunkData.IsDeleted && MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full) is not null)
+            if (lockTransparency && !chunkData.IsDeleted &&
+                MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full) is not null)
             {
                 var committed = GetCommittedChunk(chunkPos, resolution);
                 OperationHelper.ClampAlpha(targetChunk!.Surface.DrawingSurface, committed!.Surface.DrawingSurface);
@@ -1039,14 +1224,15 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             return new FilledChunk();
         }
 
-        var intersection = Chunk.Create(resolution);
+        var intersection = Chunk.Create(ProcessingColorSpace, resolution);
         intersection.Surface.DrawingSurface.Canvas.Clear(Colors.White);
 
         foreach (var mask in activeClips)
         {
             if (mask.CommittedChunkExists(chunkPos))
             {
-                mask.DrawCommittedChunkOn(chunkPos, resolution, intersection.Surface.DrawingSurface, VecI.Zero, ClippingPaint);
+                mask.DrawCommittedChunkOn(chunkPos, resolution, intersection.Surface.DrawingSurface, VecI.Zero,
+                    ClippingPaint);
             }
             else
             {
@@ -1091,15 +1277,15 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             // drawing with raster clipping
             var clip = combinedRasterClips.AsT2;
 
-            using var tempChunk = Chunk.Create(targetChunk.Resolution);
-            targetChunk.DrawOnSurface(tempChunk.Surface.DrawingSurface, VecI.Zero, ReplacingPaint);
+            using var tempChunk = Chunk.Create(ProcessingColorSpace, targetChunk.Resolution);
+            targetChunk.DrawChunkOn(tempChunk.Surface.DrawingSurface, VecI.Zero, ReplacingPaint);
 
             CallDrawWithClip(chunkOperation, operationAffectedArea.GlobalArea, tempChunk, resolution, chunkPos);
 
-            clip.DrawOnSurface(tempChunk.Surface.DrawingSurface, VecI.Zero, ClippingPaint);
-            clip.DrawOnSurface(targetChunk.Surface.DrawingSurface, VecI.Zero, InverseClippingPaint);
+            clip.DrawChunkOn(tempChunk.Surface.DrawingSurface, VecI.Zero, ClippingPaint);
+            clip.DrawChunkOn(targetChunk.Surface.DrawingSurface, VecI.Zero, InverseClippingPaint);
 
-            tempChunk.DrawOnSurface(targetChunk.Surface.DrawingSurface, VecI.Zero, AddingPaint);
+            tempChunk.DrawChunkOn(targetChunk.Surface.DrawingSurface, VecI.Zero, AddingPaint);
             return false;
         }
 
@@ -1111,7 +1297,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         return chunkData.IsDeleted;
     }
 
-    private void CallDrawWithClip(IDrawOperation operation, RectI? operationAffectedArea, Chunk targetChunk, ChunkResolution resolution, VecI chunkPos)
+    private void CallDrawWithClip(IDrawOperation operation, RectI? operationAffectedArea, Chunk targetChunk,
+        ChunkResolution resolution, VecI chunkPos)
     {
         if (operationAffectedArea is null)
             return;
@@ -1123,7 +1310,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         {
             using VectorPath transformedPath = new(clippingPath);
             VecD trans = -chunkPos * FullChunkSize * scale;
-            
+
             transformedPath.Transform(Matrix3X3.CreateScaleTranslation(scale, scale, (float)trans.X, (float)trans.Y));
             targetChunk.Surface.DrawingSurface.Canvas.ClipPath(transformedPath);
         }
@@ -1149,7 +1336,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         {
             ThrowIfDisposed();
             if (queuedOperations.Count > 0)
-                throw new InvalidOperationException("This function can only be used when there are no queued operations");
+                throw new InvalidOperationException(
+                    "This function can only be used when there are no queued operations");
             FindAndDeleteEmptyCommittedChunks();
             return committedChunks[ChunkResolution.Full].Count == 0;
         }
@@ -1164,7 +1352,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 
     private static bool IsOutsideBounds(VecI chunkPos, VecI imageSize)
     {
-        return chunkPos.X < 0 || chunkPos.Y < 0 || chunkPos.X * FullChunkSize >= imageSize.X || chunkPos.Y * FullChunkSize >= imageSize.Y;
+        return chunkPos.X < 0 || chunkPos.Y < 0 || chunkPos.X * FullChunkSize >= imageSize.X ||
+               chunkPos.Y * FullChunkSize >= imageSize.Y;
     }
 
     private void FindAndDeleteEmptyCommittedChunks()
@@ -1203,7 +1392,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         // for full res chunks: nothing exists, create brand new chunk
         if (resolution == ChunkResolution.Full)
         {
-            var newChunk = Chunk.Create(resolution);
+            var newChunk = Chunk.Create(ProcessingColorSpace, resolution);
             committedChunks[resolution][chunkPos] = newChunk;
             return newChunk;
         }
@@ -1212,11 +1401,12 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         Chunk? existingFullResChunk = MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full);
         if (existingFullResChunk is not null)
         {
-            var newChunk = Chunk.Create(resolution);
+            var newChunk = Chunk.Create(ProcessingColorSpace, resolution);
             newChunk.Surface.DrawingSurface.Canvas.Save();
             newChunk.Surface.DrawingSurface.Canvas.Scale((float)resolution.Multiplier());
 
-            newChunk.Surface.DrawingSurface.Canvas.DrawSurface(existingFullResChunk.Surface.DrawingSurface, 0, 0, SmoothReplacingPaint);
+            newChunk.Surface.DrawingSurface.Canvas.DrawSurface(existingFullResChunk.Surface.DrawingSurface, 0, 0,
+                SmoothReplacingPaint);
             newChunk.Surface.DrawingSurface.Canvas.Restore();
             committedChunks[resolution][chunkPos] = newChunk;
             return newChunk;
@@ -1225,7 +1415,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         // for low res chunks: full res version doesn't exist
         {
             GetOrCreateCommittedChunk(chunkPos, ChunkResolution.Full);
-            var newChunk = Chunk.Create(resolution);
+            var newChunk = Chunk.Create(ProcessingColorSpace, resolution);
             committedChunks[resolution][chunkPos] = newChunk;
             return newChunk;
         }
@@ -1245,7 +1435,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         var maybeCommittedAnyRes = MaybeGetCommittedChunk(chunkPos, resolution);
         if (maybeCommittedAnyRes is not null)
         {
-            Chunk newChunk = Chunk.Create(resolution);
+            Chunk newChunk = Chunk.Create(ProcessingColorSpace, resolution);
             if (blendMode == BlendMode.Src)
                 maybeCommittedAnyRes.Surface.CopyTo(newChunk.Surface);
             else
@@ -1261,14 +1451,14 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             //create low res committed chunk
             var committedChunkLowRes = GetOrCreateCommittedChunk(chunkPos, resolution);
             //create latest based on it
-            Chunk newChunk = Chunk.Create(resolution);
+            Chunk newChunk = Chunk.Create(ProcessingColorSpace, resolution);
             committedChunkLowRes.Surface.CopyTo(newChunk.Surface);
             latestChunks[resolution][chunkPos] = newChunk;
             return newChunk;
         }
 
         // no previous chunks exist
-        var newLatestChunk = Chunk.Create(resolution);
+        var newLatestChunk = Chunk.Create(ProcessingColorSpace, resolution);
         newLatestChunk.Surface.DrawingSurface.Canvas.Clear();
         latestChunks[resolution][chunkPos] = newLatestChunk;
         return newLatestChunk;
@@ -1313,4 +1503,23 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 
         disposed = true;
     }
+
+    public object Clone()
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            ChunkyImage clone = CloneFromCommitted();
+            return clone;
+        }
+    }
+
+    public int GetCacheHash()
+    {
+        return commitCounter + queuedOperations.Count + operationCounter + activeClips.Count
+               + (int)blendMode + (lockTransparency ? 1 : 0)
+               + (horizontalSymmetryAxis is not null ? (int)(horizontalSymmetryAxis * 100) : 0)
+               + (verticalSymmetryAxis is not null ? (int)(verticalSymmetryAxis * 100) : 0)
+               + (clippingPath is not null ? 1 : 0);
+    }
 }

+ 10 - 9
src/ChunkyImageLib/ChunkyImageEx.cs

@@ -1,8 +1,9 @@
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Numerics;
 
 namespace ChunkyImageLib;
 public static class IReadOnlyChunkyImageEx
@@ -18,7 +19,7 @@ public static class IReadOnlyChunkyImageEx
     /// <param name="pos">Starting position on the surface</param>
     /// <param name="paint">Paint to use for drawing</param>
     public static void DrawMostUpToDateRegionOn
-        (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null)
+        (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null)
     {
         DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawMostUpToDateChunkOn, paint);
     }
@@ -43,12 +44,12 @@ public static class IReadOnlyChunkyImageEx
         RectI fullResRegion,
         ChunkResolution resolution,
         DrawingSurface surface,
-        VecI pos,
-        Func<VecI, ChunkResolution, DrawingSurface, VecI, Paint?, bool> drawingFunc,
+        VecD pos,
+        Func<VecI, ChunkResolution, DrawingSurface, VecD, Paint?, bool> drawingFunc,
         Paint? paint = null)
     {
-        surface.Canvas.Save();
-        surface.Canvas.ClipRect(RectD.Create(pos, fullResRegion.Size));
+        int count = surface.Canvas.Save();
+        surface.Canvas.ClipRect(new RectD(pos, fullResRegion.Size));
 
         VecI chunkTopLeft = OperationHelper.GetChunkPos(fullResRegion.TopLeft, ChunkyImage.FullChunkSize);
         VecI chunkBotRight = OperationHelper.GetChunkPos(fullResRegion.BottomRight, ChunkyImage.FullChunkSize);
@@ -64,6 +65,6 @@ public static class IReadOnlyChunkyImageEx
             }
         }
 
-        surface.Canvas.Restore();
+        surface.Canvas.RestoreToCount(count);
     }
 }

+ 6 - 46
src/ChunkyImageLib/ChunkyImageLib.csproj

@@ -1,64 +1,24 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <ImplicitUsings>enable</ImplicitUsings>
     <Nullable>enable</Nullable>
-    <WarningsAsErrors>Nullable</WarningsAsErrors>
     <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
-    <Configurations>Debug;Release;Steam;DevRelease</Configurations>
-    <Platforms>AnyCPU</Platforms>
   </PropertyGroup>
-
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Steam|AnyCPU'">
-    <Optimize>True</Optimize>
-  </PropertyGroup>
-
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Steam|x64'">
-    <Optimize>True</Optimize>
-    <PlatformTarget>AnyCPU</PlatformTarget>
-  </PropertyGroup>
-
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Steam|x86'">
-    <Optimize>True</Optimize>
-    <PlatformTarget>AnyCPU</PlatformTarget>
-  </PropertyGroup>
-
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
-  </PropertyGroup>
-
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
-  </PropertyGroup>
-
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'DevRelease|x64' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
-  </PropertyGroup>
-
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'DevRelease|x86' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
-  </PropertyGroup>
-
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
-  </PropertyGroup>
-
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
-  </PropertyGroup>
-
+  
   <ItemGroup>
-    <PackageReference Include="OneOf" Version="3.0.223" />
+    <PackageReference Include="OneOf" Version="3.0.271" />
     <PackageReference Update="StyleCop.Analyzers" Version="1.2.0-beta.435">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
-    <PackageReference Include="WriteableBitmapEx" Version="1.6.8" />
   </ItemGroup>
 
   <ItemGroup>
-    <ProjectReference Include="..\PixiEditor.DrawingApi.Core\PixiEditor.DrawingApi.Core.csproj" />
+    <ProjectReference Include="..\Drawie\src\Drawie.Backend.Core\Drawie.Backend.Core.csproj" />
+    <ProjectReference Include="..\Drawie\src\Drawie.Numerics\Drawie.Numerics.csproj" />
+    <ProjectReference Include="..\PixiEditor.Common\PixiEditor.Common.csproj" />
   </ItemGroup>
 
 </Project>

+ 12 - 1
src/ChunkyImageLib/ColorEx.cs

@@ -1,4 +1,4 @@
-using PixiEditor.DrawingApi.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl;
 
 namespace ChunkyImageLib;
 public static class ColorEx
@@ -14,4 +14,15 @@ public static class ColorEx
         ptr[3] = (Half)(normalizedAlpha);
         return result;
     }
+
+    public static unsafe ulong ToULong(this ColorF colorF)
+    {
+        ulong result = 0;
+        Half* ptr = (Half*)&result;
+        ptr[0] = (Half)colorF.R;
+        ptr[1] = (Half)colorF.G;
+        ptr[2] = (Half)colorF.B;
+        ptr[3] = (Half)colorF.A;
+        return result;
+    }
 }

+ 5 - 4
src/ChunkyImageLib/CommittedChunkStorage.cs

@@ -1,7 +1,8 @@
 using ChunkyImageLib.DataHolders;
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Numerics;
 
 namespace ChunkyImageLib;
 
@@ -15,7 +16,7 @@ public class CommittedChunkStorage : IDisposable
     {
         foreach (var chunkPos in committedChunksToSave)
         {
-            Chunk copy = Chunk.Create();
+            Chunk copy = Chunk.Create(image.ProcessingColorSpace);
             if (!image.DrawCommittedChunkOn(chunkPos, ChunkResolution.Full, copy.Surface.DrawingSurface, VecI.Zero, ReplacingPaint))
             {
                 copy.Dispose();

+ 2 - 1
src/ChunkyImageLib/DataHolders/AffectedArea.cs

@@ -3,7 +3,8 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
-using PixiEditor.DrawingApi.Core.Numerics;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Numerics;
 
 namespace ChunkyImageLib.DataHolders;
 

+ 20 - 5
src/ChunkyImageLib/DataHolders/ColorBounds.cs

@@ -1,5 +1,5 @@
 using System.Runtime.CompilerServices;
-using PixiEditor.DrawingApi.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl;
 
 namespace ChunkyImageLib.DataHolders;
 
@@ -21,13 +21,17 @@ public struct ColorBounds
 
     public float UpperA { get; set; }
 
-    public ColorBounds(Color color)
+    public ColorBounds(Color color, double tolerance = 0)
     {
         static (float lower, float upper) FindInclusiveBoundaryPremul(byte channel, float alpha)
         {
-            float subHalf = channel > 0 ? channel - .5f : channel;
-            float addHalf = channel < 255 ? channel + .5f : channel;
-            return (subHalf * alpha / 255f, addHalf * alpha / 255f);
+            float subHalf = channel > 0 ? channel - 1f : channel;
+            float addHalf = channel < 255 ? channel + 1f : channel;
+            
+            var lower = subHalf * alpha / 255f;
+            var upper = addHalf * alpha / 255f;
+            
+            return (lower, upper);
         }
 
         static (float lower, float upper) FindInclusiveBoundary(byte channel)
@@ -40,9 +44,20 @@ public struct ColorBounds
         float a = color.A / 255f;
 
         (LowerR, UpperR) = FindInclusiveBoundaryPremul(color.R, a);
+        LowerR -= (float)tolerance;
+        UpperR += (float)tolerance;
+        
         (LowerG, UpperG) = FindInclusiveBoundaryPremul(color.G, a);
+        LowerG -= (float)tolerance;
+        UpperG += (float)tolerance;
+        
         (LowerB, UpperB) = FindInclusiveBoundaryPremul(color.B, a);
+        LowerB -= (float)tolerance;
+        UpperB += (float)tolerance;
+        
         (LowerA, UpperA) = FindInclusiveBoundary(color.A);
+        LowerA -= (float)tolerance;
+        UpperA += (float)tolerance;
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]

+ 0 - 225
src/ChunkyImageLib/DataHolders/ShapeCorners.cs

@@ -1,225 +0,0 @@
-using System.Diagnostics;
-using PixiEditor.DrawingApi.Core.Numerics;
-
-namespace ChunkyImageLib.DataHolders;
-
-[DebuggerDisplay("TL: {TopLeft}, TR: {TopRight}, BL: {BottomLeft}, BR: {BottomRight}")]
-public struct ShapeCorners
-{
-    private const double epsilon = 0.001;
-    public ShapeCorners(VecD center, VecD size)
-    {
-        TopLeft = center - size / 2;
-        TopRight = center + new VecD(size.X / 2, -size.Y / 2);
-        BottomRight = center + size / 2;
-        BottomLeft = center + new VecD(-size.X / 2, size.Y / 2);
-    }
-    public ShapeCorners(RectD rect)
-    {
-        TopLeft = rect.TopLeft;
-        TopRight = rect.TopRight;
-        BottomRight = rect.BottomRight;
-        BottomLeft = rect.BottomLeft;
-    }
-    public VecD TopLeft { get; set; }
-    public VecD TopRight { get; set; }
-    public VecD BottomLeft { get; set; }
-    public VecD BottomRight { get; set; }
-    public bool IsInverted
-    {
-        get
-        {
-            var top = TopLeft - TopRight;
-            var right = TopRight - BottomRight;
-            var bottom = BottomRight - BottomLeft;
-            var left = BottomLeft - TopLeft;
-            return Math.Sign(top.Cross(right)) + Math.Sign(right.Cross(bottom)) + Math.Sign(bottom.Cross(left)) + Math.Sign(left.Cross(top)) < 0;
-        }
-    }
-    public bool IsLegal
-    {
-        get
-        {
-            if (HasNaNOrInfinity)
-                return false;
-            var top = TopLeft - TopRight;
-            var right = TopRight - BottomRight;
-            var bottom = BottomRight - BottomLeft;
-            var left = BottomLeft - TopLeft;
-            var topRight = Math.Sign(top.Cross(right));
-            return topRight == Math.Sign(right.Cross(bottom)) && topRight == Math.Sign(bottom.Cross(left)) && topRight == Math.Sign(left.Cross(top));
-        }
-    }
-
-    /// <summary>
-    /// Checks if two or more corners are in the same position
-    /// </summary>
-    public bool IsPartiallyDegenerate
-    {
-        get
-        {
-            Span<VecD> lengths = stackalloc[] 
-            {
-                TopLeft - TopRight,
-                TopRight - BottomRight,
-                BottomRight - BottomLeft,
-                BottomLeft - TopLeft,
-                TopLeft - BottomRight,
-                TopRight - BottomLeft
-            };
-            foreach (VecD vec in lengths)
-            {
-                if (vec.LengthSquared < epsilon * epsilon)
-                    return true;
-            }
-            return false;
-        }
-    }
-    public bool HasNaNOrInfinity => TopLeft.IsNaNOrInfinity() || TopRight.IsNaNOrInfinity() || BottomLeft.IsNaNOrInfinity() || BottomRight.IsNaNOrInfinity();
-    public bool IsRect => Math.Abs((TopLeft - BottomRight).Length - (TopRight - BottomLeft).Length) < epsilon;
-    public VecD RectSize => new((TopLeft - TopRight).Length, (TopLeft - BottomLeft).Length);
-    public VecD RectCenter => (TopLeft - BottomRight) / 2 + BottomRight;
-    public double RectRotation =>
-        (TopLeft - TopRight).Cross(TopLeft - BottomLeft) > 0 ?
-        RectSize.CCWAngleTo(BottomRight - TopLeft) :
-        RectSize.CCWAngleTo(BottomLeft - TopRight);
-    public bool IsSnappedToPixels
-    {
-        get
-        {
-            double epsilon = 0.01;
-            return
-                (TopLeft - TopLeft.Round()).TaxicabLength < epsilon &&
-                (TopRight - TopRight.Round()).TaxicabLength < epsilon &&
-                (BottomLeft - BottomLeft.Round()).TaxicabLength < epsilon &&
-                (BottomRight - BottomRight.Round()).TaxicabLength < epsilon;
-        }
-    }
-    public RectD AABBBounds
-    {
-        get
-        {
-            double minX = Math.Min(Math.Min(TopLeft.X, TopRight.X), Math.Min(BottomLeft.X, BottomRight.X));
-            double minY = Math.Min(Math.Min(TopLeft.Y, TopRight.Y), Math.Min(BottomLeft.Y, BottomRight.Y));
-            double maxX = Math.Max(Math.Max(TopLeft.X, TopRight.X), Math.Max(BottomLeft.X, BottomRight.X));
-            double maxY = Math.Max(Math.Max(TopLeft.Y, TopRight.Y), Math.Max(BottomLeft.Y, BottomRight.Y));
-            return RectD.FromTwoPoints(new VecD(minX, minY), new VecD(maxX, maxY));
-        }
-    }
-
-    public bool IsPointInside(VecD point)
-    {
-        var top = TopLeft - TopRight;
-        var right = TopRight - BottomRight;
-        var bottom = BottomRight - BottomLeft;
-        var left = BottomLeft - TopLeft;
-
-        var deltaTopLeft = point - TopLeft;
-        var deltaTopRight = point - TopRight;
-        var deltaBottomRight = point - BottomRight;
-        var deltaBottomLeft = point - BottomLeft;
-
-        if (deltaTopRight.IsNaNOrInfinity() || deltaTopLeft.IsNaNOrInfinity() || deltaBottomRight.IsNaNOrInfinity() || deltaBottomRight.IsNaNOrInfinity())
-            return false;
-
-        var crossTop = Math.Sign(top.Cross(deltaTopLeft));
-        var crossRight = Math.Sign(right.Cross(deltaTopRight));
-        var crossBottom = Math.Sign(bottom.Cross(deltaBottomRight));
-        var crossLeft = Math.Sign(left.Cross(deltaBottomLeft));
-
-        return crossTop == crossRight && crossTop == crossLeft && crossTop == crossBottom;
-    }
-
-    public ShapeCorners AsMirroredAcrossHorAxis(double horAxisY) => new ShapeCorners
-    {
-        BottomLeft = BottomLeft.ReflectY(horAxisY),
-        BottomRight = BottomRight.ReflectY(horAxisY),
-        TopLeft = TopLeft.ReflectY(horAxisY),
-        TopRight = TopRight.ReflectY(horAxisY)
-    };
-
-    public ShapeCorners AsMirroredAcrossVerAxis(double verAxisX) => new ShapeCorners
-    {
-        BottomLeft = BottomLeft.ReflectX(verAxisX),
-        BottomRight = BottomRight.ReflectX(verAxisX),
-        TopLeft = TopLeft.ReflectX(verAxisX),
-        TopRight = TopRight.ReflectX(verAxisX)
-    };
-
-    public ShapeCorners AsRotated(double angle, VecD around) => new ShapeCorners
-    {
-        BottomLeft = BottomLeft.Rotate(angle, around),
-        BottomRight = BottomRight.Rotate(angle, around),
-        TopLeft = TopLeft.Rotate(angle, around),
-        TopRight = TopRight.Rotate(angle, around)
-    };
-
-    public ShapeCorners AsTranslated(VecD delta) => new ShapeCorners
-    {
-        BottomLeft = BottomLeft + delta,
-        BottomRight = BottomRight + delta,
-        TopLeft = TopLeft + delta,
-        TopRight = TopRight + delta
-    };
-
-    public static bool operator !=(ShapeCorners left, ShapeCorners right) => !(left == right);
-    public static bool operator == (ShapeCorners left, ShapeCorners right)
-    {
-        return 
-           left.TopLeft == right.TopLeft &&
-           left.TopRight == right.TopRight &&
-           left.BottomLeft == right.BottomLeft &&
-           left.BottomRight == right.BottomRight;
-    }
-
-    public bool AlmostEquals(ShapeCorners other, double epsilon = 0.001)
-    {
-        return
-            TopLeft.AlmostEquals(other.TopLeft, epsilon) &&
-            TopRight.AlmostEquals(other.TopRight, epsilon) &&
-            BottomLeft.AlmostEquals(other.BottomLeft, epsilon) &&
-            BottomRight.AlmostEquals(other.BottomRight, epsilon);
-    }
-    
-    public bool Intersects(RectD rect)
-    {
-        // Get all corners
-        VecD[] corners1 = { TopLeft, TopRight, BottomRight, BottomLeft };
-        VecD[] corners2 = { rect.TopLeft, rect.TopRight, rect.BottomRight, rect.BottomLeft };
-
-        // For each pair of corners
-        for (int i = 0; i < 4; i++)
-        {
-            VecD axis = corners1[i] - corners1[(i + 1) % 4]; // Create an edge
-            axis = new VecD(-axis.Y, axis.X); // Get perpendicular axis
-
-            // Project corners of first shape onto axis
-            double min1 = double.MaxValue, max1 = double.MinValue;
-            foreach (VecD corner in corners1)
-            {
-                double projection = corner.Dot(axis);
-                min1 = Math.Min(min1, projection);
-                max1 = Math.Max(max1, projection);
-            }
-
-            // Project corners of second shape onto axis
-            double min2 = double.MaxValue, max2 = double.MinValue;
-            foreach (VecD corner in corners2)
-            {
-                double projection = corner.Dot(axis);
-                min2 = Math.Min(min2, projection);
-                max2 = Math.Max(max2, projection);
-            }
-
-            // Check for overlap
-            if (max1 < min2 || max2 < min1)
-            {
-                // The projections do not overlap, so the shapes do not intersect
-                return false;
-            }
-        }
-
-        // All projections overlap, so the shapes intersect
-        return true;
-    }
-}

+ 18 - 11
src/ChunkyImageLib/DataHolders/ShapeData.cs

@@ -1,34 +1,41 @@
-using PixiEditor.DrawingApi.Core.ColorsImpl;
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Numerics;
 
 namespace ChunkyImageLib.DataHolders;
 
 public record struct ShapeData
 {
-    public ShapeData(VecD center, VecD size, double rotation, int strokeWidth, Color strokeColor, Color fillColor, BlendMode blendMode = BlendMode.SrcOver)
+    public ShapeData(VecD center, VecD size, double cornerRadius, double rotation, float strokeWidth, Paintable stroke, Paintable fillPaintable, BlendMode blendMode = BlendMode.SrcOver)
     {
-        StrokeColor = strokeColor;
-        FillColor = fillColor;
+        Stroke = stroke;
+        FillPaintable = fillPaintable;
         Center = center;
         Size = size;
         Angle = rotation;
         StrokeWidth = strokeWidth;
+        CornerRadius = cornerRadius;
         BlendMode = blendMode;
     }
-    public Color StrokeColor { get; }
-    public Color FillColor { get; }
+    public Paintable Stroke { get; }
+    public Paintable FillPaintable { get; }
     public BlendMode BlendMode { get; }
     public VecD Center { get; }
 
     /// <summary>Can be negative to show flipping </summary>
     public VecD Size { get; }
+
+    public double CornerRadius { get; }
     public double Angle { get; }
-    public int StrokeWidth { get; }
+    public float StrokeWidth { get; }
+    public bool AntiAliasing { get; set; } = false;
+    
 
     public ShapeData AsMirroredAcrossHorAxis(double horAxisY)
-        => new ShapeData(Center.ReflectY(horAxisY), new(Size.X, -Size.Y), -Angle, StrokeWidth, StrokeColor, FillColor, BlendMode);
+        => new ShapeData(Center.ReflectY(horAxisY), new(Size.X, -Size.Y), CornerRadius, -Angle, StrokeWidth, Stroke, FillPaintable, BlendMode);
     public ShapeData AsMirroredAcrossVerAxis(double verAxisX)
-        => new ShapeData(Center.ReflectX(verAxisX), new(-Size.X, Size.Y), -Angle, StrokeWidth, StrokeColor, FillColor, BlendMode);
+        => new ShapeData(Center.ReflectX(verAxisX), new(-Size.X, Size.Y), CornerRadius, -Angle, StrokeWidth, Stroke, FillPaintable, BlendMode);
 
 }

+ 12 - 7
src/ChunkyImageLib/IReadOnlyChunkyImage.cs

@@ -1,22 +1,27 @@
 using ChunkyImageLib.DataHolders;
-using PixiEditor.DrawingApi.Core.ColorsImpl;
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Numerics;
 
 namespace ChunkyImageLib;
 
 public interface IReadOnlyChunkyImage
 {
-    bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null);
-    bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null);
+    bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null);
+    bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null);
     RectI? FindChunkAlignedMostUpToDateBounds();
     RectI? FindChunkAlignedCommittedBounds();
-    RectI? FindTightCommittedBounds(ChunkResolution precision = ChunkResolution.Full);
+    RectI? FindTightCommittedBounds(ChunkResolution precision = ChunkResolution.Full, bool fallbackToChunkAligned = false);
     Color GetCommittedPixel(VecI posOnImage);
     Color GetMostUpToDatePixel(VecI posOnImage);
     bool LatestOrCommittedChunkExists(VecI chunkPos);
     AffectedArea FindAffectedArea(int fromOperationIndex = 0);
     HashSet<VecI> FindCommittedChunks();
     HashSet<VecI> FindAllChunks();
+    VecI CommittedSize { get; }
+    VecI LatestSize { get; }
+    public ColorSpace ProcessingColorSpace { get; }
 }

+ 4 - 3
src/ChunkyImageLib/Operations/ApplyMaskOperation.cs

@@ -1,7 +1,8 @@
 using ChunkyImageLib.DataHolders;
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Numerics;
 
 namespace ChunkyImageLib.Operations;
 

+ 11 - 10
src/ChunkyImageLib/Operations/BresenhamLineHelper.cs

@@ -1,20 +1,21 @@
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Numerics;
 
 namespace ChunkyImageLib.Operations;
+
 public static class BresenhamLineHelper
 {
-    public static Point[] GetBresenhamLine(VecI start, VecI end)
+    public static VecI[] GetBresenhamLine(VecI start, VecI end)
     {
         int count = Math.Abs((start - end).LongestAxis) + 1;
         if (count > 100000)
-            return Array.Empty<Point>();
-        Point[] output = new Point[count];
+            return [];
+        VecI[] output = new VecI[count];
         CalculateBresenhamLine(start, end, output);
         return output;
     }
 
-    private static void CalculateBresenhamLine(VecI start, VecI end, Point[] output)
+    private static void CalculateBresenhamLine(VecI start, VecI end, VecI[] output)
     {
         int index = 0;
 
@@ -25,7 +26,7 @@ public static class BresenhamLineHelper
 
         if (x1 == x2 && y1 == y2)
         {
-            output[index] = new Point(start);
+            output[index] = new VecF(start);
             return;
         }
 
@@ -54,7 +55,7 @@ public static class BresenhamLineHelper
             dy = y1 - y2;
         }
 
-        output[index] = new Point(x, y);
+        output[index] = new VecF(x, y);
         index++;
 
         if (dx > dy)
@@ -77,7 +78,7 @@ public static class BresenhamLineHelper
                     x += xi;
                 }
 
-                output[index] = new Point(x, y);
+                output[index] = new VecF(x, y);
                 index++;
             }
         }
@@ -101,7 +102,7 @@ public static class BresenhamLineHelper
                     y += yi;
                 }
 
-                output[index] = new Point(x, y);
+                output[index] = new VecF(x, y);
                 index++;
             }
         }

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است