├── .dockerignore ├── .github └── workflows │ ├── build and deploy to dev.yml │ └── build and deploy to prod.yml ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── AppImageGenerator.csproj ├── AppImageGenerator.sln ├── App_Data ├── androidImages.json ├── chromeImages.json ├── firefoxImages.json ├── iosImages.json ├── msteamsImages.json ├── pwabuilderImages.json ├── windows10Images.json ├── windows11Images.json └── windowsImages.json ├── Controllers └── ImageController.cs ├── Dockerfile ├── Models ├── ImageGenerationModel.cs ├── Profile.cs └── WebManifestIcon.cs ├── Program.cs ├── Properties └── launchSettings.json ├── README.md ├── appsettings.Development.json ├── appsettings.json ├── docker-compose.debug.yml └── docker-compose.yml /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/bin 15 | **/charts 16 | **/docker-compose* 17 | **/compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md 26 | -------------------------------------------------------------------------------- /.github/workflows/build and deploy to dev.yml: -------------------------------------------------------------------------------- 1 | # Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy 2 | # More GitHub Actions for Azure: https://github.com/Azure/actions 3 | 4 | name: appimagegenerator-prod to dev slot 5 | 6 | on: 7 | push: 8 | branches: 9 | - dev 10 | pull_request: 11 | branches: 12 | - dev 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - name: Set up .NET Core 22 | uses: actions/setup-dotnet@v2 23 | with: 24 | dotnet-version: '7.x' 25 | include-prerelease: true 26 | 27 | - name: Build with dotnet 28 | run: dotnet build --configuration Release 29 | 30 | - name: dotnet publish 31 | run: dotnet publish -c Release -o ${{env.DOTNET_ROOT}}/myapp 32 | 33 | - name: Upload artifact for deployment job 34 | uses: actions/upload-artifact@v3 35 | with: 36 | name: .net-app 37 | path: ${{env.DOTNET_ROOT}}/myapp 38 | 39 | deploy: 40 | runs-on: ubuntu-latest 41 | needs: build 42 | environment: 43 | name: 'dev' 44 | url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} 45 | 46 | steps: 47 | - name: 'Login via Azure CLI' 48 | uses: azure/login@v1 49 | with: 50 | creds: ${{ secrets.AZURE_CREDENTIALS_PRINCIPAL }} 51 | 52 | - name: Download artifact from build job 53 | uses: actions/download-artifact@v3 54 | with: 55 | name: .net-app 56 | 57 | - name: Deploy to Azure Web App 58 | id: deploy-to-webapp 59 | uses: azure/webapps-deploy@v2 60 | with: 61 | app-name: 'appimagegenerator-prod' 62 | slot-name: 'dev' 63 | package: . 64 | -------------------------------------------------------------------------------- /.github/workflows/build and deploy to prod.yml: -------------------------------------------------------------------------------- 1 | # Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy 2 | # More GitHub Actions for Azure: https://github.com/Azure/actions 3 | 4 | name: appimagegenerator-prod to prod slot 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - name: Set up .NET Core 19 | uses: actions/setup-dotnet@v2 20 | with: 21 | dotnet-version: '7.x' 22 | include-prerelease: true 23 | 24 | - name: Build with dotnet 25 | run: dotnet build --configuration Release 26 | 27 | - name: dotnet publish 28 | run: dotnet publish -c Release -o ${{env.DOTNET_ROOT}}/myapp 29 | 30 | - name: Upload artifact for deployment job 31 | uses: actions/upload-artifact@v3 32 | with: 33 | name: .net-app 34 | path: ${{env.DOTNET_ROOT}}/myapp 35 | 36 | deploy: 37 | runs-on: ubuntu-latest 38 | needs: build 39 | environment: 40 | name: 'dev' 41 | url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} 42 | 43 | steps: 44 | - name: 'Login via Azure CLI' 45 | uses: azure/login@v1 46 | with: 47 | creds: ${{ secrets.AZURE_CREDENTIALS_PRINCIPAL }} 48 | 49 | - name: Download artifact from build job 50 | uses: actions/download-artifact@v3 51 | with: 52 | name: .net-app 53 | 54 | - name: Deploy to Azure Web App 55 | id: deploy-to-webapp 56 | uses: azure/webapps-deploy@v2 57 | with: 58 | app-name: 'appimagegenerator-prod' 59 | package: . 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # git-ls-files --others --exclude-from=.git/info/exclude 2 | # Lines that start with '#' are comments. 3 | # For a project mostly in C, the following would be a good set of 4 | # exclude patterns (uncomment them if you want to use them): 5 | # *.[oa] 6 | # *~ 7 | *.o 8 | *.sdf 9 | *.lo 10 | *.la 11 | *.al 12 | .libs 13 | *.so 14 | *.a 15 | *.pyc 16 | *.pyo 17 | *.rej 18 | *.suo 19 | *.user 20 | *.cache 21 | *.xap 22 | *.log 23 | *.vspcc 24 | *.vspscc 25 | *.vscc 26 | *.vssscc 27 | *~ 28 | *.*~ 29 | .*.swp 30 | .DS_Store 31 | bin 32 | Bin 33 | obj 34 | Obj 35 | Debug 36 | Release 37 | *.suo 38 | *.user 39 | TestResults 40 | *.Cache 41 | _ReSharper.* 42 | .vs 43 | ClientBin 44 | stylecop.* 45 | pkgobj 46 | pkg 47 | .svn 48 | .builds 49 | ~$* 50 | *.dbmdl 51 | coverage 52 | packages 53 | /App_Data/*.zip 54 | /App_Data/*.tmp 55 | /App_Data/BodyPart_* 56 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": ".NET Core Launch (web)", 6 | "type": "coreclr", 7 | "request": "launch", 8 | "preLaunchTask": "build", 9 | "program": "${workspaceFolder}/bin/Debug/net7.0/AppImageGenerator.dll", 10 | "args": [], 11 | "cwd": "${workspaceFolder}", 12 | "stopAtEntry": false, 13 | "serverReadyAction": { 14 | "action": "openExternally", 15 | "pattern": "\\bNow listening on:\\s+(https?://\\S+)" 16 | }, 17 | "env": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | }, 20 | "sourceFileMap": { 21 | "/Views": "${workspaceFolder}/Views" 22 | } 23 | }, 24 | { 25 | "name": ".NET Core Attach", 26 | "type": "coreclr", 27 | "request": "attach" 28 | }, 29 | { 30 | "name": "Docker .NET Core Launch", 31 | "type": "docker", 32 | "request": "launch", 33 | "preLaunchTask": "docker-run: debug", 34 | "netCore": { 35 | "appProject": "${workspaceFolder}/AppImageGenerator.csproj" 36 | } 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/AppImageGenerator.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/AppImageGenerator.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "--project", 36 | "${workspaceFolder}/AppImageGenerator.csproj" 37 | ], 38 | "problemMatcher": "$msCompile" 39 | }, 40 | { 41 | "type": "docker-build", 42 | "label": "docker-build: debug", 43 | "dependsOn": [ 44 | "build" 45 | ], 46 | "dockerBuild": { 47 | "tag": "pwabuilderimagegenerator:dev", 48 | "target": "base", 49 | "dockerfile": "${workspaceFolder}/Dockerfile", 50 | "context": "${workspaceFolder}", 51 | "pull": true 52 | }, 53 | "netCore": { 54 | "appProject": "${workspaceFolder}/AppImageGenerator.csproj" 55 | } 56 | }, 57 | { 58 | "type": "docker-build", 59 | "label": "docker-build: release", 60 | "dependsOn": [ 61 | "build" 62 | ], 63 | "dockerBuild": { 64 | "tag": "pwabuilderimagegenerator:latest", 65 | "dockerfile": "${workspaceFolder}/Dockerfile", 66 | "context": "${workspaceFolder}", 67 | "pull": true 68 | }, 69 | "netCore": { 70 | "appProject": "${workspaceFolder}/AppImageGenerator.csproj" 71 | } 72 | }, 73 | { 74 | "type": "docker-run", 75 | "label": "docker-run: debug", 76 | "dependsOn": [ 77 | "docker-build: debug" 78 | ], 79 | "dockerRun": {}, 80 | "netCore": { 81 | "appProject": "${workspaceFolder}/AppImageGenerator.csproj", 82 | "enableDebugging": true 83 | } 84 | }, 85 | { 86 | "type": "docker-run", 87 | "label": "docker-run: release", 88 | "dependsOn": [ 89 | "docker-build: release" 90 | ], 91 | "dockerRun": {}, 92 | "netCore": { 93 | "appProject": "${workspaceFolder}/AppImageGenerator.csproj" 94 | } 95 | } 96 | ] 97 | } -------------------------------------------------------------------------------- /AppImageGenerator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 9423d0e8-1eb9-474b-9ce8-a383ce07fab7 8 | Linux 9 | . 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /AppImageGenerator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33205.214 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppImageGenerator", "AppImageGenerator.csproj", "{ABE74024-2AEC-4CA9-8D34-0E4608FF3C23}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {ABE74024-2AEC-4CA9-8D34-0E4608FF3C23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {ABE74024-2AEC-4CA9-8D34-0E4608FF3C23}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {ABE74024-2AEC-4CA9-8D34-0E4608FF3C23}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {ABE74024-2AEC-4CA9-8D34-0E4608FF3C23}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {D0EF26B5-F3FD-4067-B530-29C7D4EDA8A5} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /App_Data/androidImages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "width": 512, 4 | "height": 512, 5 | "name": "android-launchericon-512-512", 6 | "desc": "android-launchericon-512-512", 7 | "folder": "android/" 8 | }, 9 | { 10 | "width": 192, 11 | "height": 192, 12 | "name": "android-launchericon-192-192", 13 | "desc": "android-launchericon-192-192", 14 | "folder": "android/" 15 | }, 16 | { 17 | "width": 144, 18 | "height": 144, 19 | "name": "android-launchericon-144-144", 20 | "desc": "android-launchericon-144-144", 21 | "folder": "android/" 22 | }, 23 | { 24 | "width": 96, 25 | "height": 96, 26 | "name": "android-launchericon-96-96", 27 | "desc": "android-launchericon-96-96", 28 | "folder": "android/" 29 | }, 30 | { 31 | "width": 72, 32 | "height": 72, 33 | "name": "android-launchericon-72-72", 34 | "desc": "android-launchericon-72-72", 35 | "folder": "android/" 36 | }, 37 | { 38 | "width": 48, 39 | "height": 48, 40 | "name": "android-launchericon-48-48", 41 | "desc": "android-launchericon-48-48", 42 | "folder": "android/" 43 | } 44 | ] 45 | -------------------------------------------------------------------------------- /App_Data/chromeImages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "width": 48, 4 | "height": 48, 5 | "name": "chrome-extensionmanagementpage-48-48", 6 | "desc": "chrome-extensionmanagementpage-48-48", 7 | "folder": "chrome/" 8 | }, 9 | { 10 | "width": 16, 11 | "height": 16, 12 | "name": "chrome-favicon-16-16", 13 | "desc": "chrome-favicon-16-16", 14 | "folder": "chrome/" 15 | }, 16 | { 17 | "width": 128, 18 | "height": 128, 19 | "name": "chrome-installprocess-128-128", 20 | "desc": "chrome-installprocess-128-128", 21 | "folder": "chrome/" 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /App_Data/firefoxImages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "width": 512, "height": 512, "name": "firefox-marketplace-512-512", "desc": "firefox-marketplace-512-512", "folder": "firefox/" }, 3 | { "width": 128, "height": 128, "name": "firefox-marketplace-128-128", "desc": "firefox-marketplace-128-128", "folder": "firefox/" }, 4 | 5 | { "width": 256, "height": 256, "name": "firefox-general-256-256", "desc": "firefox-general-256-256", "folder": "firefox/" }, 6 | { "width": 128, "height": 128, "name": "firefox-general-128-128", "desc": "firefox-general-128-128", "folder": "firefox/" }, 7 | { "width": 90, "height": 90, "name": "firefox-general-90-90", "desc": "firefox-general-90-90", "folder": "firefox/" }, 8 | { "width": 64, "height": 64, "name": "firefox-general-64-64", "desc": "firefox-general-64-64", "folder": "firefox/" }, 9 | { "width": 48, "height": 48, "name": "firefox-general-48-48", "desc": "firefox-general-48-48", "folder": "firefox/" }, 10 | { "width": 32, "height": 32, "name": "firefox-general-32-32", "desc": "firefox-general-32-32", "folder": "firefox/" }, 11 | { "width": 16, "height": 16, "name": "firefox-general-16-16", "desc": "firefox-general-16-16", "folder": "firefox/" } 12 | ] 13 | -------------------------------------------------------------------------------- /App_Data/iosImages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "width": 16, 4 | "height": 16, 5 | "name": "16", 6 | "desc": "16", 7 | "folder": "ios/" 8 | }, 9 | { 10 | "width": 20, 11 | "height": 20, 12 | "name": "20", 13 | "desc": "20", 14 | "folder": "ios/" 15 | }, 16 | { 17 | "width": 29, 18 | "height": 29, 19 | "name": "29", 20 | "desc": "29", 21 | "folder": "ios/" 22 | }, 23 | { 24 | "width": 32, 25 | "height": 32, 26 | "name": "32", 27 | "desc": "32", 28 | "folder": "ios/" 29 | }, 30 | { 31 | "width": 40, 32 | "height": 40, 33 | "name": "40", 34 | "desc": "40", 35 | "folder": "ios/" 36 | }, 37 | { 38 | "width": 50, 39 | "height": 50, 40 | "name": "50", 41 | "desc": "50", 42 | "folder": "ios/" 43 | }, 44 | { 45 | "width": 57, 46 | "height": 57, 47 | "name": "57", 48 | "desc": "57", 49 | "folder": "ios/" 50 | }, 51 | { 52 | "width": 58, 53 | "height": 58, 54 | "name": "58", 55 | "desc": "58", 56 | "folder": "ios/" 57 | }, 58 | { 59 | "width": 60, 60 | "height": 60, 61 | "name": "60", 62 | "desc": "60", 63 | "folder": "ios/" 64 | }, 65 | { 66 | "width": 64, 67 | "height": 64, 68 | "name": "64", 69 | "desc": "64", 70 | "folder": "ios/" 71 | }, 72 | { 73 | "width": 72, 74 | "height": 72, 75 | "name": "72", 76 | "desc": "72", 77 | "folder": "ios/" 78 | 79 | }, 80 | { 81 | "width": 76, 82 | "height": 76, 83 | "name": "76", 84 | "desc": "76", 85 | "folder": "ios/" 86 | }, 87 | { 88 | "width": 80, 89 | "height": 80, 90 | "name": "80", 91 | "desc": "80", 92 | "folder": "ios/" 93 | }, 94 | { 95 | "width": 87, 96 | "height": 87, 97 | "name": "87", 98 | "desc": "87", 99 | "folder": "ios/" 100 | }, 101 | { 102 | "width": 100, 103 | "height": 100, 104 | "name": "100", 105 | "desc": "100", 106 | "folder": "ios/" 107 | }, 108 | { 109 | "width": 114, 110 | "height": 114, 111 | "name": "114", 112 | "desc": "114", 113 | "folder": "ios/" 114 | }, 115 | { 116 | "width": 120, 117 | "height": 120, 118 | "name": "120", 119 | "desc": "120", 120 | "folder": "ios/" 121 | }, 122 | { 123 | "width": 128, 124 | "height": 128, 125 | "name": "128", 126 | "desc": "128", 127 | "folder": "ios/" 128 | }, 129 | { 130 | "width": 144, 131 | "height": 144, 132 | "name": "144", 133 | "desc": "144", 134 | "folder": "ios/" 135 | }, 136 | { 137 | "width": 152, 138 | "height": 152, 139 | "name": "152", 140 | "desc": "152", 141 | "folder": "ios/" 142 | }, 143 | { 144 | "width": 167, 145 | "height": 167, 146 | "name": "167", 147 | "desc": "167", 148 | "folder": "ios/" 149 | }, 150 | { 151 | "width": 180, 152 | "height": 180, 153 | "name": "180", 154 | "desc": "180", 155 | "folder": "ios/" 156 | }, 157 | { 158 | "width": 192, 159 | "height": 192, 160 | "name": "192", 161 | "desc": "192", 162 | "folder": "ios/" 163 | }, 164 | { 165 | "width": 256, 166 | "height": 256, 167 | "name": "256", 168 | "desc": "256", 169 | "folder": "ios/" 170 | }, 171 | { 172 | "width": 512, 173 | "height": 512, 174 | "name": "512", 175 | "desc": "512", 176 | "folder": "ios/" 177 | }, 178 | { 179 | "width": 1024, 180 | "height": 1024, 181 | "name": "1024", 182 | "desc": "1024", 183 | "folder": "ios/" 184 | } 185 | 186 | ] 187 | -------------------------------------------------------------------------------- /App_Data/msteamsImages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "width": 192, 4 | "height": 192, 5 | "name": "msteams-192-192", 6 | "desc": "msteams-192-192", 7 | "folder": "msteams/" 8 | }, 9 | { 10 | "width": 32, 11 | "height": 32, 12 | "name": "msteams-silhouette-32-32", 13 | "desc": "msteams-silhouette-32-32", 14 | "folder": "msteams/" 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /App_Data/pwabuilderImages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "width": 44, 4 | "height": 44, 5 | "name": "Square44x44Logo.scale-100.png", 6 | "desc": "Square44x44Logo.scale-100.png", 7 | "folder": "windows10/" 8 | }, 9 | { 10 | "width": 48, 11 | "height": 48, 12 | "name": "LockScreenLogo.scale-200.png", 13 | "desc": "LockScreenLogo.scale-200.png", 14 | "folder": "windows10/" 15 | }, 16 | { 17 | "width": 1240, 18 | "height": 600, 19 | "name": "SplashScreen.scale-200.png", 20 | "desc": "SplashScreen.scale-200.png", 21 | "folder": "windows10/" 22 | }, 23 | { 24 | "width": 300, 25 | "height": 300, 26 | "name": "Square150x150Logo.scale-200.png", 27 | "desc": "Square150x150Logo.scale-200.png", 28 | "folder": "windows10/" 29 | }, 30 | { 31 | "width": 150, 32 | "height": 150, 33 | "name": "Square150x150Logo.scale-100.png", 34 | "desc": "Square150x150Logo.scale-100.png", 35 | "folder": "windows10/" 36 | }, 37 | { 38 | "width": 88, 39 | "height": 88, 40 | "name": "Square44x44Logo.scale-200.png", 41 | "desc": "Square44x44Logo.scale-200.png", 42 | "folder": "windows10/" 43 | }, 44 | { 45 | "width": 24, 46 | "height": 24, 47 | "name": "Square44x44Logo.targetsize-24_altform-unplated.png", 48 | "desc": "Square44x44Logo.targetsize-24_altform-unplated.png", 49 | "folder": "windows10/" 50 | }, 51 | { 52 | "width": 50, 53 | "height": 50, 54 | "name": "StoreLogo.png", 55 | "desc": "StoreLogo.png", 56 | "folder": "windows10/" 57 | }, 58 | { 59 | "width": 620, 60 | "height": 300, 61 | "name": "Wide310x150Logo.scale-200.png", 62 | "desc": "Wide310x150Logo.scale-200.png", 63 | "folder": "windows10/" 64 | }, 65 | { 66 | "width": 192, 67 | "height": 192, 68 | "name": "android-launchericon-192-192", 69 | "desc": "android-launchericon-192-192", 70 | "folder": "android/" 71 | }, 72 | { 73 | "width": 144, 74 | "height": 144, 75 | "name": "android-launchericon-144-144", 76 | "desc": "android-launchericon-144-144", 77 | "folder": "android/" 78 | }, 79 | { 80 | "width": 96, 81 | "height": 96, 82 | "name": "android-launchericon-96-96", 83 | "desc": "android-launchericon-96-96", 84 | "folder": "android/" 85 | }, 86 | { 87 | "width": 72, 88 | "height": 72, 89 | "name": "android-launchericon-72-72", 90 | "desc": "android-launchericon-72-72", 91 | "folder": "android/" 92 | }, 93 | { 94 | "width": 48, 95 | "height": 48, 96 | "name": "android-launchericon-48-48", 97 | "desc": "android-launchericon-48-48", 98 | "folder": "android/" 99 | }, 100 | { 101 | "width": 36, 102 | "height": 36, 103 | "name": "android-launchericon-36-36", 104 | "desc": "android-launchericon-36-36", 105 | "folder": "android/" 106 | }, 107 | { 108 | "width": 1024, 109 | "height": 1024, 110 | "name": "ios-appicon-1024-1024", 111 | "desc": "ios-appicon-1024-1024", 112 | "folder": "ios/" 113 | }, 114 | { 115 | "width": 180, 116 | "height": 180, 117 | "name": "ios-appicon-180-180", 118 | "desc": "ios-appicon-180-180", 119 | "folder": "ios/" 120 | }, 121 | { 122 | "width": 152, 123 | "height": 152, 124 | "name": "ios-appicon-152-152", 125 | "desc": "ios-appicon-152-152", 126 | "folder": "ios/" 127 | }, 128 | { 129 | "width": 120, 130 | "height": 120, 131 | "name": "ios-appicon-120-120", 132 | "desc": "ios-appicon-120-120", 133 | "folder": "ios/" 134 | }, 135 | { 136 | "width": 76, 137 | "height": 76, 138 | "name": "ios-appicon-76-76", 139 | "desc": "ios-appicon-76-76", 140 | "folder": "ios/" 141 | }, 142 | { 143 | "width": 192, 144 | "height": 192, 145 | "name": "msteams-192-192", 146 | "desc": "msteams-192-192", 147 | "folder": "msteams/" 148 | }, 149 | { 150 | "width": 32, 151 | "height": 32, 152 | "name": "msteams-silhouette-32-32", 153 | "desc": "msteams-silhouette-32-32", 154 | "folder": "msteams/" 155 | } 156 | ] 157 | -------------------------------------------------------------------------------- /App_Data/windows10Images.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "width": 71, 4 | "height": 71, 5 | "name": "SmallTile.scale-100", 6 | "desc": "SmallTile.scale-100", 7 | "folder": "windows10/" 8 | }, 9 | { 10 | "width": 89, 11 | "height": 89, 12 | "name": "SmallTile.scale-125", 13 | "desc": "SmallTile.scale-125", 14 | "folder": "windows10/" 15 | }, 16 | { 17 | "width": 107, 18 | "height": 107, 19 | "name": "SmallTile.scale-150", 20 | "desc": "SmallTile.scale-150", 21 | "folder": "windows10/" 22 | }, 23 | { 24 | "width": 142, 25 | "height": 142, 26 | "name": "SmallTile.scale-200", 27 | "desc": "SmallTile.scale-200", 28 | "folder": "windows10/" 29 | }, 30 | { 31 | "width": 284, 32 | "height": 284, 33 | "name": "SmallTile.scale-400", 34 | "desc": "SmallTile.scale-400", 35 | "folder": "windows10/" 36 | }, 37 | 38 | { 39 | "width": 150, 40 | "height": 150, 41 | "name": "Square150x150Logo.scale-100", 42 | "desc": "Square150x150Logo.scale-100", 43 | "folder": "windows10/" 44 | }, 45 | { 46 | "width": 188, 47 | "height": 188, 48 | "name": "Square150x150Logo.scale-125", 49 | "desc": "Square150x150Logo.scale-125", 50 | "folder": "windows10/" 51 | }, 52 | { 53 | "width": 225, 54 | "height": 225, 55 | "name": "Square150x150Logo.scale-150", 56 | "desc": "Square150x150Logo.scale-150", 57 | "folder": "windows10/" 58 | }, 59 | { 60 | "width": 300, 61 | "height": 300, 62 | "name": "Square150x150Logo.scale-200", 63 | "desc": "Square150x150Logo.scale-200", 64 | "folder": "windows10/" 65 | }, 66 | { 67 | "width": 600, 68 | "height": 600, 69 | "name": "Square150x150Logo.scale-400", 70 | "desc": "Square150x150Logo.scale-400", 71 | "folder": "windows10/" 72 | }, 73 | 74 | { 75 | "width": 310, 76 | "height": 150, 77 | "name": "Wide310x150Logo.scale-100", 78 | "desc": "Wide310x150Logo.scale-100", 79 | "folder": "windows10/" 80 | }, 81 | { 82 | "width": 388, 83 | "height": 188, 84 | "name": "Wide310x150Logo.scale-125", 85 | "desc": "Wide310x150Logo.scale-125", 86 | "folder": "windows10/" 87 | }, 88 | { 89 | "width": 465, 90 | "height": 225, 91 | "name": "Wide310x150Logo.scale-150", 92 | "desc": "Wide310x150Logo.scale-150", 93 | "folder": "windows10/" 94 | }, 95 | { 96 | "width": 620, 97 | "height": 300, 98 | "name": "Wide310x150Logo.scale-200", 99 | "desc": "Wide310x150Logo.scale-200", 100 | "folder": "windows10/" 101 | }, 102 | { 103 | "width": 1240, 104 | "height": 600, 105 | "name": "Wide310x150Logo.scale-400", 106 | "desc": "Wide310x150Logo.scale-400", 107 | "folder": "windows10/" 108 | }, 109 | 110 | { 111 | "width": 310, 112 | "height": 310, 113 | "name": "LargeTile.scale-100", 114 | "desc": "LargeTile.scale-100", 115 | "folder": "windows10/" 116 | }, 117 | { 118 | "width": 388, 119 | "height": 388, 120 | "name": "LargeTile.scale-125", 121 | "desc": "LargeTile.scale-125", 122 | "folder": "windows10/" 123 | }, 124 | { 125 | "width": 465, 126 | "height": 465, 127 | "name": "LargeTile.scale-150", 128 | "desc": "LargeTile.scale-150", 129 | "folder": "windows10/" 130 | }, 131 | { 132 | "width": 620, 133 | "height": 620, 134 | "name": "LargeTile.scale-200", 135 | "desc": "LargeTile.scale-200", 136 | "folder": "windows10/" 137 | }, 138 | { 139 | "width": 1240, 140 | "height": 1240, 141 | "name": "LargeTile.scale-400", 142 | "desc": "LargeTile.scale-400", 143 | "folder": "windows10/" 144 | }, 145 | 146 | { 147 | "width": 44, 148 | "height": 44, 149 | "name": "Square44x44Logo.scale-100", 150 | "desc": "Square44x44Logo.scale-100", 151 | "folder": "windows10/" 152 | }, 153 | { 154 | "width": 55, 155 | "height": 55, 156 | "name": "Square44x44Logo.scale-125", 157 | "desc": "Square44x44Logo.scale-125", 158 | "folder": "windows10/" 159 | }, 160 | { 161 | "width": 66, 162 | "height": 66, 163 | "name": "Square44x44Logo.scale-150", 164 | "desc": "Square44x44Logo.scale-150", 165 | "folder": "windows10/" 166 | }, 167 | { 168 | "width": 88, 169 | "height": 88, 170 | "name": "Square44x44Logo.scale-200", 171 | "desc": "Square44x44Logo.scale-200", 172 | "folder": "windows10/" 173 | }, 174 | { 175 | "width": 176, 176 | "height": 176, 177 | "name": "Square44x44Logo.scale-400", 178 | "desc": "Square44x44Logo.scale-400", 179 | "folder": "windows10/" 180 | }, 181 | 182 | { 183 | "width": 50, 184 | "height": 50, 185 | "name": "StoreLogo.scale-100", 186 | "desc": "StoreLogo.scale-100", 187 | "folder": "windows10/" 188 | }, 189 | { 190 | "width": 63, 191 | "height": 63, 192 | "name": "StoreLogo.scale-125", 193 | "desc": "StoreLogo.scale-125", 194 | "folder": "windows10/" 195 | }, 196 | { 197 | "width": 75, 198 | "height": 75, 199 | "name": "StoreLogo.scale-150", 200 | "desc": "StoreLogo.scale-150", 201 | "folder": "windows10/" 202 | }, 203 | { 204 | "width": 100, 205 | "height": 100, 206 | "name": "StoreLogo.scale-200", 207 | "desc": "StoreLogo.scale-200", 208 | "folder": "windows10/" 209 | }, 210 | { 211 | "width": 200, 212 | "height": 200, 213 | "name": "StoreLogo.scale-400", 214 | "desc": "StoreLogo.scale-400", 215 | "folder": "windows10/" 216 | }, 217 | 218 | { 219 | "width": 620, 220 | "height": 300, 221 | "name": "SplashScreen.scale-100", 222 | "desc": "SplashScreen.scale-100", 223 | "folder": "windows10/" 224 | }, 225 | { 226 | "width": 775, 227 | "height": 375, 228 | "name": "SplashScreen.scale-125", 229 | "desc": "SplashScreen.scale-125", 230 | "folder": "windows10/" 231 | }, 232 | { 233 | "width": 930, 234 | "height": 450, 235 | "name": "SplashScreen.scale-150", 236 | "desc": "SplashScreen.scale-150", 237 | "folder": "windows10/" 238 | }, 239 | { 240 | "width": 1240, 241 | "height": 600, 242 | "name": "SplashScreen.scale-200", 243 | "desc": "SplashScreen.scale-200", 244 | "folder": "windows10/" 245 | }, 246 | { 247 | "width": 2480, 248 | "height": 1200, 249 | "name": "SplashScreen.scale-400", 250 | "desc": "SplashScreen.scale-400", 251 | "folder": "windows10/" 252 | }, 253 | 254 | { 255 | "width": 16, 256 | "height": 16, 257 | "name": "Square44x44Logo.targetsize-16", 258 | "desc": "Square44x44Logo.targetsize-16", 259 | "folder": "windows10/" 260 | }, 261 | { 262 | "width": 20, 263 | "height": 20, 264 | "name": "Square44x44Logo.targetsize-20", 265 | "desc": "Square44x44Logo.targetsize-20", 266 | "folder": "windows10/" 267 | }, 268 | { 269 | "width": 24, 270 | "height": 24, 271 | "name": "Square44x44Logo.targetsize-24", 272 | "desc": "Square44x44Logo.targetsize-24", 273 | "folder": "windows10/" 274 | }, 275 | { 276 | "width": 30, 277 | "height": 30, 278 | "name": "Square44x44Logo.targetsize-30", 279 | "desc": "Square44x44Logo.targetsize-30", 280 | "folder": "windows10/" 281 | }, 282 | { 283 | "width": 32, 284 | "height": 32, 285 | "name": "Square44x44Logo.targetsize-32", 286 | "desc": "Square44x44Logo.targetsize-32", 287 | "folder": "windows10/" 288 | }, 289 | { 290 | "width": 36, 291 | "height": 36, 292 | "name": "Square44x44Logo.targetsize-36", 293 | "desc": "Square44x44Logo.targetsize-36", 294 | "folder": "windows10/" 295 | }, 296 | { 297 | "width": 40, 298 | "height": 40, 299 | "name": "Square44x44Logo.targetsize-40", 300 | "desc": "Square44x44Logo.targetsize-40", 301 | "folder": "windows10/" 302 | }, 303 | { 304 | "width": 44, 305 | "height": 44, 306 | "name": "Square44x44Logo.targetsize-44", 307 | "desc": "Square44x44Logo.targetsize-44", 308 | "folder": "windows10/" 309 | }, 310 | { 311 | "width": 48, 312 | "height": 48, 313 | "name": "Square44x44Logo.targetsize-48", 314 | "desc": "Square44x44Logo.targetsize-48", 315 | "folder": "windows10/" 316 | }, 317 | { 318 | "width": 60, 319 | "height": 60, 320 | "name": "Square44x44Logo.targetsize-60", 321 | "desc": "Square44x44Logo.targetsize-60", 322 | "folder": "windows10/" 323 | }, 324 | { 325 | "width": 64, 326 | "height": 64, 327 | "name": "Square44x44Logo.targetsize-64", 328 | "desc": "Square44x44Logo.targetsize-64", 329 | "folder": "windows10/" 330 | }, 331 | { 332 | "width": 72, 333 | "height": 72, 334 | "name": "Square44x44Logo.targetsize-72", 335 | "desc": "Square44x44Logo.targetsize-72", 336 | "folder": "windows10/" 337 | }, 338 | { 339 | "width": 80, 340 | "height": 80, 341 | "name": "Square44x44Logo.targetsize-80", 342 | "desc": "Square44x44Logo.targetsize-80", 343 | "folder": "windows10/" 344 | }, 345 | { 346 | "width": 96, 347 | "height": 96, 348 | "name": "Square44x44Logo.targetsize-96", 349 | "desc": "Square44x44Logo.targetsize-96", 350 | "folder": "windows10/" 351 | }, 352 | { 353 | "width": 256, 354 | "height": 256, 355 | "name": "Square44x44Logo.targetsize-256", 356 | "desc": "Square44x44Logo.targetsize-256", 357 | "folder": "windows10/" 358 | }, 359 | 360 | { 361 | "width": 16, 362 | "height": 16, 363 | "name": "Square44x44Logo.altform-unplated_targetsize-16", 364 | "desc": "Square44x44Logo.altform-unplated_targetsize-16", 365 | "folder": "windows10/" 366 | }, 367 | { 368 | "width": 20, 369 | "height": 20, 370 | "name": "Square44x44Logo.altform-unplated_targetsize-20", 371 | "desc": "Square44x44Logo.altform-unplated_targetsize-20", 372 | "folder": "windows10/" 373 | }, 374 | { 375 | "width": 24, 376 | "height": 24, 377 | "name": "Square44x44Logo.altform-unplated_targetsize-24", 378 | "desc": "Square44x44Logo.altform-unplated_targetsize-24", 379 | "folder": "windows10/" 380 | }, 381 | { 382 | "width": 30, 383 | "height": 30, 384 | "name": "Square44x44Logo.altform-unplated_targetsize-30", 385 | "desc": "Square44x44Logo.altform-unplated_targetsize-30", 386 | "folder": "windows10/" 387 | }, 388 | { 389 | "width": 32, 390 | "height": 32, 391 | "name": "Square44x44Logo.altform-unplated_targetsize-32", 392 | "desc": "Square44x44Logo.altform-unplated_targetsize-32", 393 | "folder": "windows10/" 394 | }, 395 | { 396 | "width": 36, 397 | "height": 36, 398 | "name": "Square44x44Logo.altform-unplated_targetsize-36", 399 | "desc": "Square44x44Logo.altform-unplated_targetsize-36", 400 | "folder": "windows10/" 401 | }, 402 | { 403 | "width": 40, 404 | "height": 40, 405 | "name": "Square44x44Logo.altform-unplated_targetsize-40", 406 | "desc": "Square44x44Logo.altform-unplated_targetsize-40", 407 | "folder": "windows10/" 408 | }, 409 | { 410 | "width": 44, 411 | "height": 44, 412 | "name": "Square44x44Logo.altform-unplated_targetsize-44", 413 | "desc": "Square44x44Logo.altform-unplated_targetsize-44", 414 | "folder": "windows10/" 415 | }, 416 | { 417 | "width": 48, 418 | "height": 48, 419 | "name": "Square44x44Logo.altform-unplated_targetsize-48", 420 | "desc": "Square44x44Logo.altform-unplated_targetsize-48", 421 | "folder": "windows10/" 422 | }, 423 | { 424 | "width": 60, 425 | "height": 60, 426 | "name": "Square44x44Logo.altform-unplated_targetsize-60", 427 | "desc": "Square44x44Logo.altform-unplated_targetsize-60", 428 | "folder": "windows10/" 429 | }, 430 | { 431 | "width": 64, 432 | "height": 64, 433 | "name": "Square44x44Logo.altform-unplated_targetsize-64", 434 | "desc": "Square44x44Logo.altform-unplated_targetsize-64", 435 | "folder": "windows10/" 436 | }, 437 | { 438 | "width": 72, 439 | "height": 72, 440 | "name": "Square44x44Logo.altform-unplated_targetsize-72", 441 | "desc": "Square44x44Logo.altform-unplated_targetsize-72", 442 | "folder": "windows10/" 443 | }, 444 | { 445 | "width": 80, 446 | "height": 80, 447 | "name": "Square44x44Logo.altform-unplated_targetsize-80", 448 | "desc": "Square44x44Logo.altform-unplated_targetsize-80", 449 | "folder": "windows10/" 450 | }, 451 | { 452 | "width": 96, 453 | "height": 96, 454 | "name": "Square44x44Logo.altform-unplated_targetsize-96", 455 | "desc": "Square44x44Logo.altform-unplated_targetsize-96", 456 | "folder": "windows10/" 457 | }, 458 | { 459 | "width": 256, 460 | "height": 256, 461 | "name": "Square44x44Logo.altform-unplated_targetsize-256", 462 | "desc": "Square44x44Logo.altform-unplated_targetsize-256", 463 | "folder": "windows10/" 464 | }, 465 | 466 | { 467 | "width": 16, 468 | "height": 16, 469 | "name": "Square44x44Logo.altform-lightunplated_targetsize-16", 470 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-16", 471 | "folder": "windows10/" 472 | }, 473 | { 474 | "width": 20, 475 | "height": 20, 476 | "name": "Square44x44Logo.altform-lightunplated_targetsize-20", 477 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-20", 478 | "folder": "windows10/" 479 | }, 480 | { 481 | "width": 24, 482 | "height": 24, 483 | "name": "Square44x44Logo.altform-lightunplated_targetsize-24", 484 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-24", 485 | "folder": "windows10/" 486 | }, 487 | { 488 | "width": 30, 489 | "height": 30, 490 | "name": "Square44x44Logo.altform-lightunplated_targetsize-30", 491 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-30", 492 | "folder": "windows10/" 493 | }, 494 | { 495 | "width": 32, 496 | "height": 32, 497 | "name": "Square44x44Logo.altform-lightunplated_targetsize-32", 498 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-32", 499 | "folder": "windows10/" 500 | }, 501 | { 502 | "width": 36, 503 | "height": 36, 504 | "name": "Square44x44Logo.altform-lightunplated_targetsize-36", 505 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-36", 506 | "folder": "windows10/" 507 | }, 508 | { 509 | "width": 40, 510 | "height": 40, 511 | "name": "Square44x44Logo.altform-lightunplated_targetsize-40", 512 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-40", 513 | "folder": "windows10/" 514 | }, 515 | { 516 | "width": 44, 517 | "height": 44, 518 | "name": "Square44x44Logo.altform-lightunplated_targetsize-44", 519 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-44", 520 | "folder": "windows10/" 521 | }, 522 | { 523 | "width": 48, 524 | "height": 48, 525 | "name": "Square44x44Logo.altform-lightunplated_targetsize-48", 526 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-48", 527 | "folder": "windows10/" 528 | }, 529 | { 530 | "width": 60, 531 | "height": 60, 532 | "name": "Square44x44Logo.altform-lightunplated_targetsize-60", 533 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-60", 534 | "folder": "windows10/" 535 | }, 536 | { 537 | "width": 64, 538 | "height": 64, 539 | "name": "Square44x44Logo.altform-lightunplated_targetsize-64", 540 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-64", 541 | "folder": "windows10/" 542 | }, 543 | { 544 | "width": 72, 545 | "height": 72, 546 | "name": "Square44x44Logo.altform-lightunplated_targetsize-72", 547 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-72", 548 | "folder": "windows10/" 549 | }, 550 | { 551 | "width": 80, 552 | "height": 80, 553 | "name": "Square44x44Logo.altform-lightunplated_targetsize-80", 554 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-80", 555 | "folder": "windows10/" 556 | }, 557 | { 558 | "width": 96, 559 | "height": 96, 560 | "name": "Square44x44Logo.altform-lightunplated_targetsize-96", 561 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-96", 562 | "folder": "windows10/" 563 | }, 564 | { 565 | "width": 256, 566 | "height": 256, 567 | "name": "Square44x44Logo.altform-lightunplated_targetsize-256", 568 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-256", 569 | "folder": "windows10/" 570 | } 571 | ] 572 | -------------------------------------------------------------------------------- /App_Data/windows11Images.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "width": 71, 4 | "height": 71, 5 | "name": "SmallTile.scale-100", 6 | "desc": "SmallTile.scale-100", 7 | "folder": "windows11/", 8 | "padding": 0.3 9 | }, 10 | { 11 | "width": 89, 12 | "height": 89, 13 | "name": "SmallTile.scale-125", 14 | "desc": "SmallTile.scale-125", 15 | "folder": "windows11/", 16 | "padding": 0.3 17 | }, 18 | { 19 | "width": 107, 20 | "height": 107, 21 | "name": "SmallTile.scale-150", 22 | "desc": "SmallTile.scale-150", 23 | "folder": "windows11/", 24 | "padding": 0.3 25 | }, 26 | { 27 | "width": 142, 28 | "height": 142, 29 | "name": "SmallTile.scale-200", 30 | "desc": "SmallTile.scale-200", 31 | "folder": "windows11/", 32 | "padding": 0.3 33 | }, 34 | { 35 | "width": 284, 36 | "height": 284, 37 | "name": "SmallTile.scale-400", 38 | "desc": "SmallTile.scale-400", 39 | "folder": "windows11/", 40 | "padding": 0.3 41 | }, 42 | 43 | { 44 | "width": 150, 45 | "height": 150, 46 | "name": "Square150x150Logo.scale-100", 47 | "desc": "Square150x150Logo.scale-100", 48 | "folder": "windows11/", 49 | "padding": 0.3 50 | }, 51 | { 52 | "width": 188, 53 | "height": 188, 54 | "name": "Square150x150Logo.scale-125", 55 | "desc": "Square150x150Logo.scale-125", 56 | "folder": "windows11/", 57 | "padding": 0.3 58 | }, 59 | { 60 | "width": 225, 61 | "height": 225, 62 | "name": "Square150x150Logo.scale-150", 63 | "desc": "Square150x150Logo.scale-150", 64 | "folder": "windows11/", 65 | "padding": 0.3 66 | }, 67 | { 68 | "width": 300, 69 | "height": 300, 70 | "name": "Square150x150Logo.scale-200", 71 | "desc": "Square150x150Logo.scale-200", 72 | "folder": "windows11/", 73 | "padding": 0.3 74 | }, 75 | { 76 | "width": 600, 77 | "height": 600, 78 | "name": "Square150x150Logo.scale-400", 79 | "desc": "Square150x150Logo.scale-400", 80 | "folder": "windows11/", 81 | "padding": 0.3 82 | }, 83 | 84 | { 85 | "width": 310, 86 | "height": 150, 87 | "name": "Wide310x150Logo.scale-100", 88 | "desc": "Wide310x150Logo.scale-100", 89 | "folder": "windows11/", 90 | "padding": 0.3 91 | }, 92 | { 93 | "width": 388, 94 | "height": 188, 95 | "name": "Wide310x150Logo.scale-125", 96 | "desc": "Wide310x150Logo.scale-125", 97 | "folder": "windows11/", 98 | "padding": 0.3 99 | }, 100 | { 101 | "width": 465, 102 | "height": 225, 103 | "name": "Wide310x150Logo.scale-150", 104 | "desc": "Wide310x150Logo.scale-150", 105 | "folder": "windows11/", 106 | "padding": 0.3 107 | }, 108 | { 109 | "width": 620, 110 | "height": 300, 111 | "name": "Wide310x150Logo.scale-200", 112 | "desc": "Wide310x150Logo.scale-200", 113 | "folder": "windows11/", 114 | "padding": 0.3 115 | }, 116 | { 117 | "width": 1240, 118 | "height": 600, 119 | "name": "Wide310x150Logo.scale-400", 120 | "desc": "Wide310x150Logo.scale-400", 121 | "folder": "windows11/", 122 | "padding": 0.3 123 | }, 124 | 125 | { 126 | "width": 310, 127 | "height": 310, 128 | "name": "LargeTile.scale-100", 129 | "desc": "LargeTile.scale-100", 130 | "folder": "windows11/", 131 | "padding": 0.3 132 | }, 133 | { 134 | "width": 388, 135 | "height": 388, 136 | "name": "LargeTile.scale-125", 137 | "desc": "LargeTile.scale-125", 138 | "folder": "windows11/", 139 | "padding": 0.3 140 | }, 141 | { 142 | "width": 465, 143 | "height": 465, 144 | "name": "LargeTile.scale-150", 145 | "desc": "LargeTile.scale-150", 146 | "folder": "windows11/", 147 | "padding": 0.3 148 | }, 149 | { 150 | "width": 620, 151 | "height": 620, 152 | "name": "LargeTile.scale-200", 153 | "desc": "LargeTile.scale-200", 154 | "folder": "windows11/", 155 | "padding": 0.3 156 | }, 157 | { 158 | "width": 1240, 159 | "height": 1240, 160 | "name": "LargeTile.scale-400", 161 | "desc": "LargeTile.scale-400", 162 | "folder": "windows11/", 163 | "padding": 0.3 164 | }, 165 | 166 | { 167 | "width": 44, 168 | "height": 44, 169 | "name": "Square44x44Logo.scale-100", 170 | "desc": "Square44x44Logo.scale-100", 171 | "folder": "windows11/", 172 | "padding": 0.0 173 | }, 174 | { 175 | "width": 55, 176 | "height": 55, 177 | "name": "Square44x44Logo.scale-125", 178 | "desc": "Square44x44Logo.scale-125", 179 | "folder": "windows11/", 180 | "padding": 0.0 181 | }, 182 | { 183 | "width": 66, 184 | "height": 66, 185 | "name": "Square44x44Logo.scale-150", 186 | "desc": "Square44x44Logo.scale-150", 187 | "folder": "windows11/", 188 | "padding": 0.0 189 | }, 190 | { 191 | "width": 88, 192 | "height": 88, 193 | "name": "Square44x44Logo.scale-200", 194 | "desc": "Square44x44Logo.scale-200", 195 | "folder": "windows11/", 196 | "padding": 0.0 197 | }, 198 | { 199 | "width": 176, 200 | "height": 176, 201 | "name": "Square44x44Logo.scale-400", 202 | "desc": "Square44x44Logo.scale-400", 203 | "folder": "windows11/", 204 | "padding": 0.0 205 | }, 206 | 207 | { 208 | "width": 50, 209 | "height": 50, 210 | "name": "StoreLogo.scale-100", 211 | "desc": "StoreLogo.scale-100", 212 | "folder": "windows11/", 213 | "padding": 0.3 214 | }, 215 | { 216 | "width": 63, 217 | "height": 63, 218 | "name": "StoreLogo.scale-125", 219 | "desc": "StoreLogo.scale-125", 220 | "folder": "windows11/", 221 | "padding": 0.3 222 | }, 223 | { 224 | "width": 75, 225 | "height": 75, 226 | "name": "StoreLogo.scale-150", 227 | "desc": "StoreLogo.scale-150", 228 | "folder": "windows11/", 229 | "padding": 0.3 230 | }, 231 | { 232 | "width": 100, 233 | "height": 100, 234 | "name": "StoreLogo.scale-200", 235 | "desc": "StoreLogo.scale-200", 236 | "folder": "windows11/", 237 | "padding": 0.3 238 | }, 239 | { 240 | "width": 200, 241 | "height": 200, 242 | "name": "StoreLogo.scale-400", 243 | "desc": "StoreLogo.scale-400", 244 | "folder": "windows11/", 245 | "padding": 0.3 246 | }, 247 | 248 | { 249 | "width": 620, 250 | "height": 300, 251 | "name": "SplashScreen.scale-100", 252 | "desc": "SplashScreen.scale-100", 253 | "folder": "windows11/", 254 | "padding": 0.3 255 | }, 256 | { 257 | "width": 775, 258 | "height": 375, 259 | "name": "SplashScreen.scale-125", 260 | "desc": "SplashScreen.scale-125", 261 | "folder": "windows11/", 262 | "padding": 0.3 263 | }, 264 | { 265 | "width": 930, 266 | "height": 450, 267 | "name": "SplashScreen.scale-150", 268 | "desc": "SplashScreen.scale-150", 269 | "folder": "windows11/", 270 | "padding": 0.3 271 | }, 272 | { 273 | "width": 1240, 274 | "height": 600, 275 | "name": "SplashScreen.scale-200", 276 | "desc": "SplashScreen.scale-200", 277 | "folder": "windows11/", 278 | "padding": 0.3 279 | }, 280 | { 281 | "width": 2480, 282 | "height": 1200, 283 | "name": "SplashScreen.scale-400", 284 | "desc": "SplashScreen.scale-400", 285 | "folder": "windows11/", 286 | "padding": 0.3 287 | }, 288 | 289 | { 290 | "width": 16, 291 | "height": 16, 292 | "name": "Square44x44Logo.targetsize-16", 293 | "desc": "Square44x44Logo.targetsize-16", 294 | "folder": "windows11/", 295 | "padding": 0.0 296 | }, 297 | { 298 | "width": 20, 299 | "height": 20, 300 | "name": "Square44x44Logo.targetsize-20", 301 | "desc": "Square44x44Logo.targetsize-20", 302 | "folder": "windows11/", 303 | "padding": 0.0 304 | }, 305 | { 306 | "width": 24, 307 | "height": 24, 308 | "name": "Square44x44Logo.targetsize-24", 309 | "desc": "Square44x44Logo.targetsize-24", 310 | "folder": "windows11/", 311 | "padding": 0.0 312 | }, 313 | { 314 | "width": 30, 315 | "height": 30, 316 | "name": "Square44x44Logo.targetsize-30", 317 | "desc": "Square44x44Logo.targetsize-30", 318 | "folder": "windows11/", 319 | "padding": 0.0 320 | }, 321 | { 322 | "width": 32, 323 | "height": 32, 324 | "name": "Square44x44Logo.targetsize-32", 325 | "desc": "Square44x44Logo.targetsize-32", 326 | "folder": "windows11/", 327 | "padding": 0.0 328 | }, 329 | { 330 | "width": 36, 331 | "height": 36, 332 | "name": "Square44x44Logo.targetsize-36", 333 | "desc": "Square44x44Logo.targetsize-36", 334 | "folder": "windows11/", 335 | "padding": 0.0 336 | }, 337 | { 338 | "width": 40, 339 | "height": 40, 340 | "name": "Square44x44Logo.targetsize-40", 341 | "desc": "Square44x44Logo.targetsize-40", 342 | "folder": "windows11/", 343 | "padding": 0.0 344 | }, 345 | { 346 | "width": 44, 347 | "height": 44, 348 | "name": "Square44x44Logo.targetsize-44", 349 | "desc": "Square44x44Logo.targetsize-44", 350 | "folder": "windows11/", 351 | "padding": 0.0 352 | }, 353 | { 354 | "width": 48, 355 | "height": 48, 356 | "name": "Square44x44Logo.targetsize-48", 357 | "desc": "Square44x44Logo.targetsize-48", 358 | "folder": "windows11/", 359 | "padding": 0.0 360 | }, 361 | { 362 | "width": 60, 363 | "height": 60, 364 | "name": "Square44x44Logo.targetsize-60", 365 | "desc": "Square44x44Logo.targetsize-60", 366 | "folder": "windows11/", 367 | "padding": 0.0 368 | }, 369 | { 370 | "width": 64, 371 | "height": 64, 372 | "name": "Square44x44Logo.targetsize-64", 373 | "desc": "Square44x44Logo.targetsize-64", 374 | "folder": "windows11/", 375 | "padding": 0.0 376 | }, 377 | { 378 | "width": 72, 379 | "height": 72, 380 | "name": "Square44x44Logo.targetsize-72", 381 | "desc": "Square44x44Logo.targetsize-72", 382 | "folder": "windows11/", 383 | "padding": 0.0 384 | }, 385 | { 386 | "width": 80, 387 | "height": 80, 388 | "name": "Square44x44Logo.targetsize-80", 389 | "desc": "Square44x44Logo.targetsize-80", 390 | "folder": "windows11/", 391 | "padding": 0.0 392 | }, 393 | { 394 | "width": 96, 395 | "height": 96, 396 | "name": "Square44x44Logo.targetsize-96", 397 | "desc": "Square44x44Logo.targetsize-96", 398 | "folder": "windows11/", 399 | "padding": 0.0 400 | }, 401 | { 402 | "width": 256, 403 | "height": 256, 404 | "name": "Square44x44Logo.targetsize-256", 405 | "desc": "Square44x44Logo.targetsize-256", 406 | "folder": "windows11/", 407 | "padding": 0.0 408 | }, 409 | 410 | { 411 | "width": 16, 412 | "height": 16, 413 | "name": "Square44x44Logo.altform-unplated_targetsize-16", 414 | "desc": "Square44x44Logo.altform-unplated_targetsize-16", 415 | "folder": "windows11/", 416 | "padding": 0.0 417 | }, 418 | { 419 | "width": 20, 420 | "height": 20, 421 | "name": "Square44x44Logo.altform-unplated_targetsize-20", 422 | "desc": "Square44x44Logo.altform-unplated_targetsize-20", 423 | "folder": "windows11/", 424 | "padding": 0.0 425 | }, 426 | { 427 | "width": 24, 428 | "height": 24, 429 | "name": "Square44x44Logo.altform-unplated_targetsize-24", 430 | "desc": "Square44x44Logo.altform-unplated_targetsize-24", 431 | "folder": "windows11/", 432 | "padding": 0.0 433 | }, 434 | { 435 | "width": 30, 436 | "height": 30, 437 | "name": "Square44x44Logo.altform-unplated_targetsize-30", 438 | "desc": "Square44x44Logo.altform-unplated_targetsize-30", 439 | "folder": "windows11/", 440 | "padding": 0.0 441 | }, 442 | { 443 | "width": 32, 444 | "height": 32, 445 | "name": "Square44x44Logo.altform-unplated_targetsize-32", 446 | "desc": "Square44x44Logo.altform-unplated_targetsize-32", 447 | "folder": "windows11/", 448 | "padding": 0.0 449 | }, 450 | { 451 | "width": 36, 452 | "height": 36, 453 | "name": "Square44x44Logo.altform-unplated_targetsize-36", 454 | "desc": "Square44x44Logo.altform-unplated_targetsize-36", 455 | "folder": "windows11/", 456 | "padding": 0.0 457 | }, 458 | { 459 | "width": 40, 460 | "height": 40, 461 | "name": "Square44x44Logo.altform-unplated_targetsize-40", 462 | "desc": "Square44x44Logo.altform-unplated_targetsize-40", 463 | "folder": "windows11/", 464 | "padding": 0.0 465 | }, 466 | { 467 | "width": 44, 468 | "height": 44, 469 | "name": "Square44x44Logo.altform-unplated_targetsize-44", 470 | "desc": "Square44x44Logo.altform-unplated_targetsize-44", 471 | "folder": "windows11/", 472 | "padding": 0.0 473 | }, 474 | { 475 | "width": 48, 476 | "height": 48, 477 | "name": "Square44x44Logo.altform-unplated_targetsize-48", 478 | "desc": "Square44x44Logo.altform-unplated_targetsize-48", 479 | "folder": "windows11/", 480 | "padding": 0.0 481 | }, 482 | { 483 | "width": 60, 484 | "height": 60, 485 | "name": "Square44x44Logo.altform-unplated_targetsize-60", 486 | "desc": "Square44x44Logo.altform-unplated_targetsize-60", 487 | "folder": "windows11/", 488 | "padding": 0.0 489 | }, 490 | { 491 | "width": 64, 492 | "height": 64, 493 | "name": "Square44x44Logo.altform-unplated_targetsize-64", 494 | "desc": "Square44x44Logo.altform-unplated_targetsize-64", 495 | "folder": "windows11/", 496 | "padding": 0.0 497 | }, 498 | { 499 | "width": 72, 500 | "height": 72, 501 | "name": "Square44x44Logo.altform-unplated_targetsize-72", 502 | "desc": "Square44x44Logo.altform-unplated_targetsize-72", 503 | "folder": "windows11/", 504 | "padding": 0.0 505 | }, 506 | { 507 | "width": 80, 508 | "height": 80, 509 | "name": "Square44x44Logo.altform-unplated_targetsize-80", 510 | "desc": "Square44x44Logo.altform-unplated_targetsize-80", 511 | "folder": "windows11/", 512 | "padding": 0.0 513 | }, 514 | { 515 | "width": 96, 516 | "height": 96, 517 | "name": "Square44x44Logo.altform-unplated_targetsize-96", 518 | "desc": "Square44x44Logo.altform-unplated_targetsize-96", 519 | "folder": "windows11/", 520 | "padding": 0.0 521 | }, 522 | { 523 | "width": 256, 524 | "height": 256, 525 | "name": "Square44x44Logo.altform-unplated_targetsize-256", 526 | "desc": "Square44x44Logo.altform-unplated_targetsize-256", 527 | "folder": "windows11/", 528 | "padding": 0.0 529 | }, 530 | 531 | { 532 | "width": 16, 533 | "height": 16, 534 | "name": "Square44x44Logo.altform-lightunplated_targetsize-16", 535 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-16", 536 | "folder": "windows11/", 537 | "padding": 0.0 538 | }, 539 | { 540 | "width": 20, 541 | "height": 20, 542 | "name": "Square44x44Logo.altform-lightunplated_targetsize-20", 543 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-20", 544 | "folder": "windows11/", 545 | "padding": 0.0 546 | }, 547 | { 548 | "width": 24, 549 | "height": 24, 550 | "name": "Square44x44Logo.altform-lightunplated_targetsize-24", 551 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-24", 552 | "folder": "windows11/", 553 | "padding": 0.0 554 | }, 555 | { 556 | "width": 30, 557 | "height": 30, 558 | "name": "Square44x44Logo.altform-lightunplated_targetsize-30", 559 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-30", 560 | "folder": "windows11/", 561 | "padding": 0.0 562 | }, 563 | { 564 | "width": 32, 565 | "height": 32, 566 | "name": "Square44x44Logo.altform-lightunplated_targetsize-32", 567 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-32", 568 | "folder": "windows11/", 569 | "padding": 0.0 570 | }, 571 | { 572 | "width": 36, 573 | "height": 36, 574 | "name": "Square44x44Logo.altform-lightunplated_targetsize-36", 575 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-36", 576 | "folder": "windows11/", 577 | "padding": 0.0 578 | }, 579 | { 580 | "width": 40, 581 | "height": 40, 582 | "name": "Square44x44Logo.altform-lightunplated_targetsize-40", 583 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-40", 584 | "folder": "windows11/", 585 | "padding": 0.0 586 | }, 587 | { 588 | "width": 44, 589 | "height": 44, 590 | "name": "Square44x44Logo.altform-lightunplated_targetsize-44", 591 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-44", 592 | "folder": "windows11/", 593 | "padding": 0.0 594 | }, 595 | { 596 | "width": 48, 597 | "height": 48, 598 | "name": "Square44x44Logo.altform-lightunplated_targetsize-48", 599 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-48", 600 | "folder": "windows11/", 601 | "padding": 0.0 602 | }, 603 | { 604 | "width": 60, 605 | "height": 60, 606 | "name": "Square44x44Logo.altform-lightunplated_targetsize-60", 607 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-60", 608 | "folder": "windows11/", 609 | "padding": 0.0 610 | }, 611 | { 612 | "width": 64, 613 | "height": 64, 614 | "name": "Square44x44Logo.altform-lightunplated_targetsize-64", 615 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-64", 616 | "folder": "windows11/", 617 | "padding": 0.0 618 | }, 619 | { 620 | "width": 72, 621 | "height": 72, 622 | "name": "Square44x44Logo.altform-lightunplated_targetsize-72", 623 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-72", 624 | "folder": "windows11/", 625 | "padding": 0.0 626 | }, 627 | { 628 | "width": 80, 629 | "height": 80, 630 | "name": "Square44x44Logo.altform-lightunplated_targetsize-80", 631 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-80", 632 | "folder": "windows11/", 633 | "padding": 0.0 634 | }, 635 | { 636 | "width": 96, 637 | "height": 96, 638 | "name": "Square44x44Logo.altform-lightunplated_targetsize-96", 639 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-96", 640 | "folder": "windows11/", 641 | "padding": 0.0 642 | }, 643 | { 644 | "width": 256, 645 | "height": 256, 646 | "name": "Square44x44Logo.altform-lightunplated_targetsize-256", 647 | "desc": "Square44x44Logo.altform-lightunplated_targetsize-256", 648 | "folder": "windows11/", 649 | "padding": 0.0 650 | } 651 | ] 652 | -------------------------------------------------------------------------------- /App_Data/windowsImages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "width": 24, "height": 24, "name": "windows-smallsquare-24-24", "desc": "windows-smallsquare-24-24", "folder": "windows/" }, 3 | { "width": 30, "height": 30, "name": "windows-smallsquare-30-30", "desc": "windows-smallsquare-30-30", "folder": "windows/" }, 4 | { "width": 42, "height": 42, "name": "windows-smallsquare-42-42", "desc": "windows-smallsquare-42-42", "folder": "windows/" }, 5 | { "width": 54, "height": 54, "name": "windows-smallsquare-54-54", "desc": "windows-smallsquare-54-54", "folder": "windows/" }, 6 | 7 | { "width": 1116, "height": 540, "name": "windows-splashscreen-1116-540", "desc": "windows-splashscreen-1116-540", "folder": "windows/" }, 8 | { "width": 868, "height": 420, "name": "windows-splashscreen-868-420", "desc": "windows-splashscreen-868-420", "folder": "windows/" }, 9 | { "width": 620, "height": 300, "name": "windows-splashscreen-620-300", "desc": "windows-splashscreen-620-300", "folder": "windows/" }, 10 | 11 | { "width": 270, "height": 270, "name": "windows-squarelogo-270-270", "desc": "windows-squarelogo-270-270", "folder": "windows/" }, 12 | { "width": 210, "height": 210, "name": "windows-squarelogo-210-210", "desc": "windows-squarelogo-210-210", "folder": "windows/" }, 13 | { "width": 150, "height": 150, "name": "windows-squarelogo-150-150", "desc": "windows-squarelogo-150-150", "folder": "windows/" }, 14 | { "width": 120, "height": 120, "name": "windows-squarelogo-120-120", "desc": "windows-squarelogo-120-120", "folder": "windows/" }, 15 | 16 | { "width": 90, "height": 90, "name": "windows-storelogo-90-90", "desc": "windows-storelogo-90-90", "folder": "windows/" }, 17 | { "width": 70, "height": 70, "name": "windows-storelogo-70-70", "desc": "windows-storelogo-70-70", "folder": "windows/" }, 18 | { "width": 50, "height": 50, "name": "windows-storelogo-50-50", "desc": "windows-storelogo-50-50", "folder": "windows/" }, 19 | 20 | { "width": 106, "height": 106, "name": "windowsphone-appicon-106-106", "desc": "windowsphone-appicon-106-106", "folder": "windows/" }, 21 | { "width": 62, "height": 62, "name": "windowsphone-appicon-62-62", "desc": "windowsphone-appicon-62-62", "folder": "windows/" }, 22 | { "width": 44, "height": 44, "name": "windowsphone-appicon-44-44", "desc": "windowsphone-appicon-44-44", "folder": "windows/" }, 23 | 24 | { "width": 360, "height": 360, "name": "windowsphone-mediumtile-360-360", "desc": "windowsphone-mediumtile-360-360", "folder": "windows/" }, 25 | { "width": 210, "height": 210, "name": "windowsphone-mediumtile-210-210", "desc": "windowsphone-mediumtile-210-210", "folder": "windows/" }, 26 | { "width": 150, "height": 150, "name": "windowsphone-mediumtile-150-150", "desc": "windowsphone-mediumtile-150-150", "folder": "windows/" }, 27 | 28 | { "width": 170, "height": 170, "name": "windowsphone-smalltile-170-170", "desc": "windowsphone-mediumtile-170-170", "folder": "windows/" }, 29 | { "width": 99, "height": 99, "name": "windowsphone-smalltile-99-99", "desc": "windowsphone-mediumtile-99-99", "folder": "windows/" }, 30 | { "width": 71, "height": 71, "name": "windowsphone-smalltile-71-71", "desc": "windowsphone-mediumtile-71-71", "folder": "windows/" }, 31 | 32 | { "width": 120, "height": 120, "name": "windowsphone-storelogo-120-120", "desc": "windowsphone-storelogo-120-120", "folder": "windows/" }, 33 | { "width": 70, "height": 70, "name": "windowsphone-storelogo-70-70", "desc": "windowsphone-storelogo-70-70", "folder": "windows/" }, 34 | { "width": 50, "height": 50, "name": "windowsphone-storelogo-50-50", "desc": "windowsphone-storelogo-50-50", "folder": "windows/" }, 35 | ] 36 | -------------------------------------------------------------------------------- /Controllers/ImageController.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Compression; 2 | using System.Net; 3 | using System.Text.Json; 4 | 5 | using AppImageGenerator.Models; 6 | 7 | using SixLabors.ImageSharp.Formats; 8 | using SixLabors.ImageSharp.Formats.Jpeg; 9 | using SixLabors.ImageSharp.Formats.Png; 10 | using SixLabors.ImageSharp.Formats.Webp; 11 | using SixLabors.ImageSharp.Formats.Bmp; 12 | using SixLabors.ImageSharp.Formats.Tiff; 13 | 14 | using Microsoft.AspNetCore.Mvc; 15 | 16 | using System.ComponentModel; 17 | using System.ComponentModel.DataAnnotations; 18 | using System.Text.Json.Serialization; 19 | using Microsoft.AspNetCore.Mvc.ApplicationModels; 20 | using Microsoft.AspNetCore.Mvc.Infrastructure; 21 | 22 | namespace AppImageGenerator.Controllers; 23 | 24 | [ApiController] 25 | [Route("api/")] 26 | public class ImageController : ControllerBase 27 | { 28 | private readonly IWebHostEnvironment _webHostEnvironment; 29 | private readonly ILogger _logger; 30 | 31 | private const string FileDownloadName = "AppImages.zip"; 32 | private const string FileDownloadType = "application/octet-stream"; 33 | private const string FileIconsJsonName = "icons.json"; 34 | private const string PlatformNameCommonPart = "Images.json"; 35 | private const string AppDataFolderName = "App_Data"; 36 | private const string GetZipById = "getIconsZipById"; 37 | private const string GenerateIconsZip = "generateIconsZip"; 38 | 39 | public ImageController (IWebHostEnvironment webHostEnvironment, ILogger logger) 40 | { 41 | _logger = logger; 42 | _webHostEnvironment = webHostEnvironment; 43 | } 44 | 45 | [HttpGet(GetZipById, Name = GetZipById)] 46 | public async Task Get(string id) 47 | { 48 | try 49 | { 50 | // Create path from the id and return the file... 51 | var zipFilePath = CreateFilePathFromId(new Guid(id)); 52 | if (string.IsNullOrEmpty(zipFilePath)) 53 | { 54 | return new NotFoundResult(); 55 | } 56 | 57 | var archive = File(await System.IO.File.ReadAllBytesAsync(zipFilePath), FileDownloadType, fileDownloadName: FileDownloadName); 58 | 59 | System.IO.File.Delete(zipFilePath); 60 | 61 | return archive; 62 | } 63 | catch (Exception ex) 64 | { 65 | if (ex.Message.StartsWith("Could not find file")) 66 | { 67 | _logger.LogError(ex, string.Format("{{GetZipById}}: Couldn't find file", GetZipById)); 68 | return StatusCode((int)HttpStatusCode.NotFound, ex.ToString()); 69 | } 70 | else { 71 | _logger.LogError(ex, string.Format("{{GetZipById}}: Couldn't get generated zip due to exception", GetZipById)); 72 | return StatusCode((int)HttpStatusCode.InternalServerError, ex.ToString()); 73 | } 74 | 75 | } 76 | } 77 | 78 | /// 79 | /// Generates a list of images from the specified base image. 80 | /// Expected arguments: 81 | /// - baseImage: the image as a multipart form POST. This is the image from which all other images will be generated. Should generally be 512x512 or larger, ideally in PNG format. 82 | /// - platform: a list of values specifying the platform(s) for which the images are being generated, e.g. "windows10" 83 | /// - padding: a value between 0 and 1 specifying the padding for the generated images. 84 | /// - color: a hex color value to use as the background color of the generated images. If null, a best guess -- the color of pixel (0,0) -- will be used. 85 | /// 86 | /// 87 | [HttpPost(GenerateIconsZip)] 88 | public async Task Post([FromForm] ImageFormData Form) 89 | { 90 | var zipId = Guid.NewGuid(); 91 | 92 | try 93 | { 94 | using var args = ImageGenerationModel.FromFormData(HttpContext.Request.Form, HttpContext.Request.Form.Files); 95 | 96 | // Punt if we have invalid arguments. 97 | if (!string.IsNullOrEmpty(args.ErrorMessage)) 98 | { 99 | return new ObjectResult(args.ErrorMessage) { StatusCode = (int?)HttpStatusCode.BadRequest }; 100 | } 101 | 102 | var profiles = GetProfilesFromPlatforms(args.Platforms); 103 | if (profiles == null) 104 | throw new Exception(string.Format("No platforms found in config: {{PLATFORMS}}", args.Platforms != null? args.Platforms : "no param")); 105 | 106 | var imageStreams = new List(profiles.Count); 107 | 108 | using (var zip = ZipFile.Open(CreateFilePathFromId(zipId), ZipArchiveMode.Create)) 109 | { 110 | var iconObject = new IconRootObject(); 111 | foreach (var profile in profiles) 112 | { 113 | var stream = CreateImageStream(args, profile); 114 | if (stream != null) 115 | { 116 | imageStreams.Add(stream); 117 | var fmt = string.IsNullOrEmpty(profile.Format) ? "png" : profile.Format; 118 | var iconEntry = zip.CreateEntry(profile.Folder + profile.Name + "." + fmt, CompressionLevel.Fastest); 119 | var iconStream = iconEntry.Open(); 120 | await stream.CopyToAsync(iconStream); 121 | iconStream.Close(); 122 | stream.Close(); 123 | 124 | iconObject.Icons.Add(new IconObject(profile.Folder + profile.Name + "." + fmt, profile.Width + "x" + profile.Height)); 125 | } 126 | } 127 | 128 | var options = new JsonSerializerOptions { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; 129 | var iconStr = JsonSerializer.Serialize(iconObject, options); 130 | 131 | using (StreamWriter writer = new(zip.CreateEntry(FileIconsJsonName, CompressionLevel.Optimal).Open())) 132 | { 133 | writer.Write(iconStr); 134 | } 135 | 136 | var zipFilePath = CreateFilePathFromId(zipId); 137 | imageStreams.ForEach(s => s.Dispose()); 138 | } 139 | 140 | // Send back a route to download the zip file. 141 | var url = Url.RouteUrl(GetZipById, new { id = zipId.ToString() }); 142 | 143 | if (url == null) 144 | throw new Exception(string.Format("Couldn't generate RouteUrl for ID: {{ID}}", zipId.ToString())); 145 | 146 | _logger.LogInformation(string.Format("{{GenerateIconsZip}}: icons generated for platforms: {{PLATFORMS}}", GenerateIconsZip, 147 | args.Platforms != null ? args.Platforms : "no param")); 148 | 149 | return new RedirectResult(url); 150 | 151 | } 152 | //catch (OutOfMemoryException ex) 153 | //{ 154 | // _logger.LogError(ex, string.Format("{{GenerateIconsZip}}: Couldn't generate images due to exception" , GenerateIconsZip)); 155 | // return StatusCode((int)HttpStatusCode.UnsupportedMediaType, ex.ToString()); 156 | //} 157 | catch (Exception ex) 158 | { 159 | _logger.LogError(ex, string.Format("{{GenerateIconsZip}}: Couldn't generate images due to exception", GenerateIconsZip)); 160 | return StatusCode((int)HttpStatusCode.UnsupportedMediaType, ex.ToString()); 161 | } 162 | } 163 | 164 | // Legacy wrapper for new generateIconsZip method 165 | [Obsolete] 166 | [HttpPost("image")] 167 | public async Task LegacyGetGeneratedImagesZip([FromForm] MultipartFormDataContent Form) 168 | { 169 | var formData = new ImageFormData(); 170 | 171 | // Convert from MultipartFormDataContent to ImageFormData 172 | if (Request.Form.Files.Count > 0) 173 | { 174 | formData.FileName = Request.Form.Files[0]; 175 | } 176 | if (Request.Form.TryGetValue("Platform", out var platformValue)) 177 | { 178 | if (Enum.TryParse(platformValue, out var platform)) 179 | { 180 | formData.Platform = platform; 181 | } 182 | } 183 | if (Request.Form.TryGetValue("Padding", out var padding)) 184 | { 185 | formData.Padding = padding; 186 | } 187 | if (Request.Form.TryGetValue("Color", out var color)) 188 | { 189 | formData.Color = color; 190 | } 191 | 192 | if (formData.FileName == null) 193 | { 194 | _logger.LogError(null, "legacyGenerateIconsZip: Couldn't generate images due to bad FormData"); 195 | return StatusCode((int)HttpStatusCode.BadRequest); 196 | } 197 | 198 | var postResponse = await Post(formData); 199 | if (postResponse is RedirectResult redirectResult) 200 | { 201 | var responseMessage = new HttpResponseMessage(HttpStatusCode.Redirect); 202 | responseMessage.Content = new StringContent(JsonSerializer.Serialize(new ImageResponse { Uri = redirectResult.Url })); 203 | 204 | HttpContext.Request.Form.TryGetValue("platform", out var platforms); 205 | _logger.LogInformation(string.Format("legacyGenerateIconsZip: icons generated for platforms: {{PLATFORMS}}", platforms.ToString())); 206 | 207 | return new ObjectResult(new ImageResponse { Uri = redirectResult.Url }); 208 | } 209 | else 210 | { 211 | if (postResponse is ObjectResult redirectFail) 212 | { 213 | if (postResponse.GetType().GetProperty("Value") != null && postResponse.GetType().GetProperty("StatusCode") != null) 214 | { 215 | _logger.LogError(redirectFail.Value?.ToString(), "legacyGenerateIconsZip: Couldn't generate images due to exception"); 216 | return StatusCode((int)redirectFail.StatusCode!, redirectFail.Value!.ToString()); 217 | } 218 | 219 | } 220 | } 221 | _logger.LogError(null, "legacyGenerateIconsZip: Couldn't generate images due to exception"); 222 | return StatusCode((int)HttpStatusCode.BadRequest); 223 | } 224 | 225 | [HttpPost("generateBase64Icons")] 226 | public ActionResult Base64([FromForm] ImageFormData Form) 227 | { 228 | try 229 | { 230 | using var args = ImageGenerationModel.FromFormData(HttpContext.Request.Form, HttpContext.Request.Form.Files); 231 | if (!string.IsNullOrEmpty(args.ErrorMessage)) 232 | { 233 | return new ObjectResult(args.ErrorMessage) { StatusCode = (int?)HttpStatusCode.BadRequest }; 234 | } 235 | 236 | var profiles = GetProfilesFromPlatforms(args.Platforms); 237 | if (profiles == null) 238 | throw new Exception(string.Format("No platforms found in config: {{PLATFORMS}}", args.Platforms != null ? args.Platforms : "no param")); 239 | 240 | var imgs = profiles 241 | .Select(profile => new WebManifestIcon 242 | { 243 | Purpose = "any", 244 | Sizes = $"{profile.Width}x{profile.Height}", 245 | Src = CreateBase64Image(args, profile), 246 | Type = string.IsNullOrEmpty(profile.Format) ? "image/png" : profile.Format 247 | }); 248 | 249 | var options = new JsonSerializerOptions { WriteIndented = true }; 250 | var response = new ObjectResult(JsonSerializer.Serialize(imgs, options)); 251 | 252 | _logger.LogInformation(string.Format("generateBase64Icons: icons generated for platforms: {{PLATFORMS}}", 253 | args.Platforms != null ? args.Platforms : "no param")); 254 | 255 | return response; 256 | } 257 | catch (Exception ex) 258 | { 259 | _logger.LogError(ex, "generateBase64Icons: Couldn't generate images due to exception"); 260 | return StatusCode((int)HttpStatusCode.InternalServerError, ex.ToString()); 261 | } 262 | } 263 | 264 | private static string ReadStringFromConfigFile(string filePath) 265 | { 266 | using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) 267 | using (var sr = new StreamReader(fs)) 268 | { 269 | return sr.ReadToEnd(); 270 | } 271 | } 272 | 273 | private IEnumerable GetConfig(string platformId) 274 | { 275 | var config = new List(); 276 | var webRootPath = _webHostEnvironment.WebRootPath ?? _webHostEnvironment.ContentRootPath; 277 | var root = Path.Combine(webRootPath, AppDataFolderName); 278 | 279 | var filePath = Path.Combine(root, platformId + PlatformNameCommonPart); 280 | config.Add(ReadStringFromConfigFile(filePath)); 281 | return config; 282 | } 283 | 284 | private IReadOnlyList? GetProfilesFromPlatforms(IEnumerable? platforms) 285 | { 286 | List? profiles = null; 287 | if (platforms != null) 288 | { 289 | foreach (var platform in platforms) 290 | { 291 | // Get the platform and profiles 292 | var config = GetConfig(platform); 293 | if (config.Count() < 1) 294 | { 295 | throw new HttpRequestException(HttpStatusCode.BadRequest.ToString()); 296 | } 297 | 298 | foreach (var cfg in config) 299 | { 300 | if (cfg != null) 301 | { 302 | var profile = JsonSerializer.Deserialize>(cfg); 303 | if (profile != null) 304 | { 305 | //profiles == null ? profiles = profile : profiles.AddRange(profile); 306 | if (profiles == null) 307 | { 308 | profiles = profile; 309 | } 310 | else 311 | { 312 | profiles.AddRange(profile); 313 | } 314 | } 315 | } 316 | } 317 | } 318 | } 319 | 320 | return profiles; 321 | } 322 | 323 | private string CreateFilePathFromId(Guid id) 324 | { 325 | var webRootPath = _webHostEnvironment.WebRootPath ?? _webHostEnvironment.ContentRootPath; 326 | var root = Path.Combine(webRootPath, AppDataFolderName); 327 | var zipFilePath = Path.Combine(root, id + ".zip"); 328 | return zipFilePath; 329 | } 330 | 331 | private static IImageEncoder GetEncoderFromType(string? type) 332 | { 333 | if (!string.IsNullOrEmpty(type)) 334 | { 335 | string ImageType = type.ToLower(); 336 | 337 | if (ImageType.EndsWith("png")) 338 | return new PngEncoder(); 339 | if (ImageType.EndsWith("jpeg")|| ImageType.EndsWith("jpg")) 340 | return new JpegEncoder(); 341 | if (ImageType.EndsWith("webp")) 342 | return new WebpEncoder(); 343 | if (ImageType.EndsWith("bmp")) 344 | return new BmpEncoder(); 345 | if (ImageType.EndsWith("tiff")) 346 | return new TiffEncoder(); 347 | } 348 | 349 | return new PngEncoder(); 350 | } 351 | 352 | private static MemoryStream? CreateImageStream(ImageGenerationModel model, Profile profile) 353 | { 354 | // We the individual image has padding specified, used that. 355 | // Otherwise, use the general padding passed into the model. 356 | var padding = profile.Padding ?? model.Padding; 357 | 358 | var imageEncoder = GetEncoderFromType(profile.Format); 359 | 360 | if (model.SvgFormData != null) 361 | { 362 | return ImageGenerationModel.ProcessSvgToStream(model.SvgFormData, profile.Width, profile.Height, imageEncoder, padding, model.BackgroundColor); 363 | } 364 | else if (model.BaseImage != null) 365 | { 366 | return ImageGenerationModel.ProcessImageToStream(model.BaseImage, profile.Width, profile.Height, imageEncoder, padding, model.BackgroundColor); 367 | } 368 | 369 | return null; 370 | } 371 | 372 | private static string? CreateBase64Image(ImageGenerationModel model, Profile profile) 373 | { 374 | var formatOrPng = string.IsNullOrEmpty(profile.Format) ? "image/png" : profile.Format; 375 | using (var imgStream = CreateImageStream(model, profile)) 376 | { if (imgStream != null) 377 | { 378 | var base64 = Convert.ToBase64String(imgStream.ToArray()); 379 | return $"data:{formatOrPng};base64,{base64}"; 380 | } 381 | } 382 | return null; 383 | } 384 | } 385 | 386 | public class IconObject 387 | { 388 | public IconObject(string src, string size) 389 | { 390 | this.Src = src; 391 | this.Sizes = size; 392 | } 393 | 394 | public string Src { get; set; } 395 | 396 | public string Sizes { get; set; } 397 | } 398 | 399 | public class IconRootObject 400 | { 401 | public List Icons { get; set; } = new List(); 402 | } 403 | 404 | public class ImageFormData 405 | { 406 | [Required] 407 | public IFormFile? FileName { get; set; } 408 | 409 | [Required] 410 | [DefaultValue(Platform.android)] 411 | public Platform Platform { get; set; } /* "android" | "chrome "| "firefox" | "ios" | "msteams" | "windows10" | "windows11" */ 412 | public string? Padding { get; set; } 413 | 414 | public string? Color { get; set; } 415 | } 416 | 417 | public enum Platform { 418 | android, 419 | chrome, firefox, ios, msteams, windows10, windows11 } 420 | 421 | // Legacy 422 | public class ImageResponse 423 | { 424 | [JsonPropertyName("Uri")] 425 | public string? Uri { get; set; } 426 | } 427 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # docker build -t image-generator . 2 | # docker run -p 7120:80 image-generator 3 | 4 | # apt-get update && apt-get install -y libfontconfig1 5 | 6 | FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base 7 | WORKDIR /app 8 | EXPOSE 7120 9 | 10 | ENV ASPNETCORE_URLS=http://+:7120 11 | 12 | # Creates a non-root user with an explicit UID and adds permission to access the /app folder 13 | # For more info, please refer to https://aka.ms/vscode-docker-dotnet-configure-containers 14 | RUN adduser -u 5678 --disabled-password --gecos "" appuser && mkdir /app/App_Data && chown -R appuser /app 15 | USER appuser 16 | 17 | FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build 18 | WORKDIR /src 19 | COPY ["AppImageGenerator.csproj", "./"] 20 | RUN dotnet restore "AppImageGenerator.csproj" 21 | COPY . . 22 | WORKDIR "/src/." 23 | RUN dotnet build "AppImageGenerator.csproj" -c Release -o /app/build 24 | 25 | FROM build AS publish 26 | RUN dotnet publish "AppImageGenerator.csproj" -c Release -o /app/publish /p:UseAppHost=false 27 | 28 | FROM base AS final 29 | WORKDIR /app 30 | COPY --from=publish /app/publish . 31 | ENTRYPOINT ["dotnet", "AppImageGenerator.dll"] 32 | 33 | USER root 34 | RUN chown -R appuser /app/App_Data 35 | USER appuser 36 | -------------------------------------------------------------------------------- /Models/ImageGenerationModel.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | using SixLabors.ImageSharp.Formats; 4 | using SkiaSharp; 5 | using SKSvg = Svg.Skia.SKSvg; 6 | 7 | namespace AppImageGenerator.Models 8 | { 9 | public class ImageGenerationModel : IDisposable 10 | { 11 | private bool disposed = false; 12 | 13 | public double Padding { get; set; } 14 | public Color? BackgroundColor { get; set; } 15 | public string[]? Platforms { get; set; } 16 | public IFormFile? BaseImageData { get; set; } 17 | public Image? BaseImage { get; set; } 18 | public string? ErrorMessage { get; set; } 19 | public IFormFile? SvgFormData { get; set; } 20 | 21 | public static ImageGenerationModel FromFormData(IFormCollection form, IFormFileCollection files) 22 | { 23 | // Validate base image data. 24 | var baseImageData = files.FirstOrDefault(); 25 | if (baseImageData == null) 26 | { 27 | return new ImageGenerationModel 28 | { 29 | ErrorMessage = "No base image specified. Request must contain image." 30 | }; 31 | } 32 | 33 | // Get the image, or SVG file name if it's an SVG image. 34 | var svgFormData = default(IFormFile); 35 | var baseImage = default(Image); 36 | if (baseImageData.Headers.ContentType.ToString().Contains("svg") == true) 37 | { 38 | svgFormData = baseImageData; 39 | } 40 | else 41 | { 42 | using var rs = baseImageData.OpenReadStream(); 43 | baseImage = Image.Load(rs); 44 | } 45 | 46 | // Validate platforms. 47 | form.TryGetValue("platform", out var platforms); 48 | 49 | if (platforms.Count < 1) 50 | { 51 | return new ImageGenerationModel 52 | { 53 | ErrorMessage = "No platform has been specified." 54 | }; 55 | } 56 | 57 | // Validate the padding. 58 | form.TryGetValue("padding", out var paddings); 59 | var hasPadding = paddings.Count > 0 ? true : false; 60 | double padding = 0; 61 | 62 | if (hasPadding) 63 | { 64 | double.TryParse(paddings.First()?.Replace(',', '.'), NumberStyles.Number, CultureInfo.InvariantCulture, out padding); 65 | } 66 | 67 | if (!hasPadding || padding < 0 || padding > 1.0) 68 | { 69 | padding = 0; 70 | } 71 | 72 | // Validate the color. 73 | form.TryGetValue("color", out var colorStrings); 74 | 75 | var colorStr = colorStrings.First(); 76 | var color = Color.FromRgba(0, 0, 0, 0); 77 | 78 | if (!string.IsNullOrEmpty(colorStr)) 79 | { 80 | try 81 | { 82 | if (!Color.TryParse(colorStr, out color) && !Color.TryParseHex(colorStr, out color)) 83 | { 84 | throw new ArgumentException("Parsing color unsucessful"); 85 | } 86 | } 87 | catch 88 | { 89 | return new ImageGenerationModel 90 | { 91 | ErrorMessage = "Background Color value invalid. Please input a valid hex color." 92 | }; 93 | } 94 | } 95 | 96 | return new ImageGenerationModel 97 | { 98 | BaseImageData = baseImageData, 99 | BaseImage = baseImage, 100 | SvgFormData = svgFormData, 101 | BackgroundColor = color, 102 | Padding = padding, 103 | Platforms = platforms 104 | }; 105 | } 106 | 107 | public static MemoryStream? ProcessSvgToStream(IFormFile inputSvg, int newWidth, int newHeight, IImageEncoder imageEncoder, double? paddingProp, Color? backgroundColor = null) 108 | { 109 | using (var svg = new SKSvg()) 110 | { 111 | using var svgStream = inputSvg.OpenReadStream(); 112 | if (svg.Load(svgStream) != null && svg.Picture != null) 113 | { 114 | int adjustWidth; 115 | int adjustedHeight; 116 | int paddingW; 117 | int paddingH; 118 | 119 | if (paddingProp > 0) 120 | { 121 | paddingW = (int)(paddingProp * newWidth * 0.5); 122 | adjustWidth = newWidth - paddingW; 123 | paddingH = (int)(paddingProp * newHeight * 0.5); 124 | adjustedHeight = newHeight - paddingH; 125 | } 126 | else 127 | { 128 | paddingW = paddingH = 0; 129 | adjustWidth = newWidth; 130 | adjustedHeight = newHeight; 131 | } 132 | 133 | // Translate, scale and convert SVG to Image 134 | var svgWidth = svg.Picture.CullRect.Width; 135 | var svgHeight = svg.Picture.CullRect.Height; 136 | var svgMax = Math.Max(svgWidth, svgHeight); 137 | var imageMin = Math.Min(adjustWidth, adjustedHeight); 138 | var scale = imageMin / svgMax; 139 | var scaleMatrix = SKMatrix.CreateIdentity(); 140 | SKMatrix.Concat(ref scaleMatrix, 141 | SKMatrix.CreateTranslation((adjustWidth - svgWidth * scale) / 2 , (adjustedHeight - svgHeight * scale) / 2), 142 | 143 | SKMatrix.CreateScale(scale, scale)); 144 | 145 | var SkiaImage = SKImage.FromPicture(svg.Picture, new SKSizeI(adjustWidth, adjustedHeight), scaleMatrix); 146 | 147 | // Save the image to the stream in the specified format 148 | var outputImage = new MemoryStream(); 149 | using (SKData data = SkiaImage.Encode(SKEncodedImageFormat.Png, 100)) 150 | { 151 | data.SaveTo(outputImage); 152 | } 153 | outputImage.Position = 0; 154 | 155 | // Conver to ImageSharp and Resize with padding 156 | Image processedImage = Image.Load(outputImage); 157 | outputImage.Position = 0; 158 | 159 | if (backgroundColor != null) 160 | { 161 | processedImage.Mutate(x => x.BackgroundColor((Color)backgroundColor)); 162 | } 163 | if (paddingProp > 0) 164 | { 165 | processedImage.Mutate(x => x.Resize( 166 | new ResizeOptions 167 | { 168 | Size = new Size(newWidth, newHeight), 169 | Mode = ResizeMode.BoxPad, 170 | PadColor = backgroundColor ?? Color.Transparent 171 | })); 172 | } 173 | 174 | 175 | processedImage.Save(outputImage, imageEncoder); 176 | outputImage.Position = 0; 177 | 178 | return outputImage; 179 | 180 | } 181 | else 182 | { 183 | return null; 184 | } 185 | 186 | } 187 | } 188 | 189 | public static MemoryStream ProcessImageToStream(Image inputImage, int newWidth, int newHeight, IImageEncoder imageEncoder, double paddingProp = 0, Color? backgroundColor = null) 190 | { 191 | int adjustWidth; 192 | int adjustedHeight; 193 | var processedImage = inputImage.Clone(x => { }); 194 | 195 | if (paddingProp > 0) 196 | { 197 | adjustWidth = newWidth - (int)(paddingProp * newWidth * 0.5); 198 | adjustedHeight = newHeight - (int)(paddingProp * newHeight * 0.5); 199 | } 200 | else 201 | { 202 | adjustWidth = newWidth; 203 | adjustedHeight = newHeight; 204 | } 205 | 206 | processedImage.Mutate(x => x.Resize(new ResizeOptions 207 | { 208 | Size = new Size(adjustWidth, adjustedHeight), 209 | Mode = ResizeMode.Pad, 210 | Sampler = KnownResamplers.Lanczos3 211 | })); 212 | 213 | if (backgroundColor != null) 214 | { 215 | processedImage.Mutate(x => x.BackgroundColor((Color)backgroundColor)); 216 | } 217 | 218 | if (paddingProp > 0) 219 | { 220 | processedImage.Mutate(x => x.Resize( 221 | new ResizeOptions 222 | { 223 | Size = new Size(newWidth, newHeight), 224 | Mode = ResizeMode.BoxPad, 225 | PadColor = backgroundColor ?? Color.Transparent 226 | }) 227 | ); 228 | } 229 | 230 | var outputImage = new MemoryStream(); 231 | processedImage.Save(outputImage, imageEncoder); 232 | outputImage.Position = 0; 233 | 234 | 235 | return outputImage; 236 | } 237 | 238 | public void Dispose() 239 | { 240 | Dispose(true); 241 | GC.SuppressFinalize(this); 242 | } 243 | 244 | protected virtual void Dispose(bool disposing) 245 | { 246 | if (disposed) 247 | { 248 | return; 249 | } 250 | 251 | if (disposing) 252 | { 253 | if (BaseImage != null) 254 | { 255 | BaseImage.Dispose(); 256 | } 257 | 258 | if (!string.IsNullOrEmpty(BaseImageData?.FileName)) 259 | { 260 | File.Delete(BaseImageData.FileName); 261 | } 262 | } 263 | 264 | disposed = true; 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /Models/Profile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | using System.Text.Json.Serialization; 6 | 7 | namespace AppImageGenerator.Models 8 | { 9 | public class Profile 10 | { 11 | [JsonPropertyName("width")] 12 | public int Width { get; set; } 13 | 14 | [JsonPropertyName("height")] 15 | public int Height { get; set; } 16 | 17 | [JsonPropertyName("name")] 18 | public string? Name { get; set; } 19 | 20 | [JsonPropertyName("desc")] 21 | public string? Desc { get; set; } 22 | 23 | [JsonPropertyName("folder")] 24 | public string? Folder { get; set; } 25 | 26 | [JsonPropertyName("format")] 27 | public string? Format { get; set; } 28 | 29 | [JsonPropertyName("padding")] 30 | public double? Padding { get; set; } 31 | } 32 | } -------------------------------------------------------------------------------- /Models/WebManifestIcon.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace AppImageGenerator.Models 4 | { 5 | /// 6 | /// A web manifest icon. 7 | /// 8 | public class WebManifestIcon 9 | { 10 | [JsonPropertyName("src")] 11 | public string? Src { get; set; } 12 | [JsonPropertyName("type")] 13 | public string? Type { get; set; } 14 | [JsonPropertyName("sizes")] 15 | public string? Sizes { get; set; } 16 | [JsonPropertyName("purpose")] 17 | public string? Purpose { get; set; } // "any" | "maskable" | "monochrome"; 18 | 19 | public bool IsSquare() 20 | { 21 | if (Sizes == null) 22 | { 23 | return false; 24 | } 25 | 26 | return GetAllDimensions() 27 | .Any(d => d.width == d.height); 28 | } 29 | 30 | /// 31 | /// Gets the largest dimension for the image. 32 | /// 33 | /// 34 | public (int width, int height)? GetLargestDimension() 35 | { 36 | var largest = GetAllDimensions() 37 | .OrderByDescending(i => i.width + i.height) 38 | .FirstOrDefault(); 39 | if (largest.height == 0 && largest.width == 0) 40 | { 41 | return null; 42 | } 43 | 44 | return largest; 45 | } 46 | 47 | /// 48 | /// Finds the largest dimension from the property 49 | /// 50 | /// The largest dimension from the string. If no valid size could be found, null. 51 | public List<(int width, int height)> GetAllDimensions() 52 | { 53 | if (Sizes == null) 54 | { 55 | return new List<(int width, int height)>(0); 56 | } 57 | 58 | return Sizes.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries) 59 | .Select(size => size.Split(new[] { 'x' }, StringSplitOptions.RemoveEmptyEntries)) 60 | .Select(widthAndHeight => 61 | { 62 | if (int.TryParse(widthAndHeight.ElementAtOrDefault(0), out var width) && 63 | int.TryParse(widthAndHeight.ElementAtOrDefault(1), out var height)) 64 | { 65 | return (width, height); 66 | } 67 | return (width: 0, height: 0); 68 | }) 69 | .Where(d => d.width != 0 && d.height != 0) 70 | .ToList(); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | var builder = WebApplication.CreateBuilder(args); 4 | 5 | builder.Logging.ClearProviders(); 6 | builder.Logging.AddConsole(); 7 | 8 | // Add services to the container. 9 | 10 | builder.Services.AddControllers() 11 | .AddJsonOptions(options => 12 | { 13 | options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; 14 | }); 15 | builder.Services.ConfigureHttpJsonOptions(options => options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase); 16 | // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 17 | builder.Services.AddEndpointsApiExplorer(); 18 | 19 | // Configure swagger to use strings for enums 20 | builder.Services.AddSwaggerGen(config => { config.UseInlineDefinitionsForEnums(); }); 21 | builder.Services.Configure(options => 22 | { 23 | options.SerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); 24 | options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; 25 | }); 26 | builder.Services.Configure(options => 27 | { 28 | options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); 29 | options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; 30 | }); 31 | 32 | var app = builder.Build(); 33 | 34 | // Configure the HTTP request pipeline. 35 | // if (app.Environment.IsDevelopment()) 36 | // { 37 | app.UseSwagger(); 38 | app.UseSwaggerUI(); 39 | // } 40 | 41 | 42 | // app.UseHttpsRedirection(); 43 | 44 | app.UseAuthorization(); 45 | 46 | app.MapControllers(); 47 | 48 | app.Run(); 49 | -------------------------------------------------------------------------------- /Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "http": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "launchUrl": "swagger", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | }, 10 | "dotnetRunMessages": true, 11 | "applicationUrl": "http://localhost:5248" 12 | }, 13 | "https": { 14 | "commandName": "Project", 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | }, 20 | "dotnetRunMessages": true, 21 | "applicationUrl": "https://localhost:7120;http://localhost:5248" 22 | }, 23 | "IIS Express": { 24 | "commandName": "IISExpress", 25 | "launchBrowser": true, 26 | "launchUrl": "swagger", 27 | "environmentVariables": { 28 | "ASPNETCORE_ENVIRONMENT": "Development" 29 | } 30 | }, 31 | "Docker": { 32 | "commandName": "Docker", 33 | "launchBrowser": true, 34 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", 35 | "publishAllPorts": true, 36 | "useSSL": true 37 | } 38 | }, 39 | "$schema": "https://json.schemastore.org/launchsettings.json", 40 | "iisSettings": { 41 | "windowsAuthentication": false, 42 | "anonymousAuthentication": true, 43 | "iisExpress": { 44 | "applicationUrl": "http://localhost:38097", 45 | "sslPort": 44348 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Please use our [main repository for any issues/bugs/features suggestion](https://github.com/pwa-builder/PWABuilder/issues/new/choose). 2 | 3 | # App Image Generator 4 | A web tool that generates images for various app platforms. Used by PWABuilder.com. 5 | 6 | See a version of the website at [http://appimagegenerator-prod.azurewebsites.net](http://appimagegenerator-prod.azurewebsites.net/swagger/index.html). 7 | 8 | Written in C# and .NET 7.0.0 9 | 10 | Origionally written by [Peter Daukintis](https://github.com/peted70) 11 | 12 | ## Usage 13 | 14 | POST to **/api/generateImagesZip** or **/api/generateBase64Images** with the following Form Data: 15 | 16 | | Option | Type | Description | 17 | |--------------|-----------|------------| 18 | | fileName | bytes (Blob or File in JS) | The bytes of the file from which to generate all the image of the target platform(s) | 19 | | platform | "android" \| "chrome "\| "firefox" \| "ios" \| "msteams" \| "windows10" \| "windows11" | The platforms to generate the images for. To use multiple platforms, add this field multiple times to the form data | 20 | | padding | number | Optional. How much padding to add to generated images. Must be between 0 and 1, where 0 is no padding and 1 is maximum. 0.3 is a reasonable value. Some platforms, such as Windows 11, control padding per-image. In such cases, this value will be ignored. | 21 | | color | string | Optional. The background color to fill in the images' padding and transparent areas. The string can be a well-known color ("blue"), a hex color ("#ffeedd"), a hex color with alpha (#2828ab73). If omitted or null, the background color will be transparent. | 22 | 23 | 24 | ## Development 25 | 26 | dotnet build
27 | dotnet watch 28 | 29 | ## Docker 30 | 31 | docker build -t image-generator .
32 | docker run -p 7120:80 image-generator
33 | http://localhost/swagger/index.html 34 | -------------------------------------------------------------------------------- /appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /docker-compose.debug.yml: -------------------------------------------------------------------------------- 1 | # Please refer https://aka.ms/HTTPSinContainer on how to setup an https developer certificate for your ASP .NET Core service. 2 | 3 | version: '3.4' 4 | 5 | services: 6 | appimagegenerator: 7 | image: appimagegenerator 8 | build: 9 | context: . 10 | dockerfile: ./Dockerfile 11 | ports: 12 | - 80:7120 13 | environment: 14 | - ASPNETCORE_ENVIRONMENT=Development 15 | volumes: 16 | - ~/.vsdbg:/remote_debugger:rw 17 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Please refer https://aka.ms/HTTPSinContainer on how to setup an https developer certificate for your ASP .NET Core service. 2 | 3 | version: '3.4' 4 | 5 | services: 6 | appimagegenerator: 7 | image: appimagegenerator 8 | build: 9 | context: . 10 | dockerfile: ./Dockerfile 11 | ports: 12 | - 80:7120 13 | --------------------------------------------------------------------------------