Skip to content

Commit 4a1befc

Browse files
committed
iOS packager uses new app image generation code
1 parent 9a2793b commit 4a1befc

File tree

10 files changed

+76
-123
lines changed

10 files changed

+76
-123
lines changed

apps/pwabuilder/Controllers/ImagesController.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,17 @@ public async Task<IActionResult> GetSafeImageForAnalysis([FromQuery] string anal
8080
[HttpPost("generateStoreImages")]
8181
[RequestFormLimits(MultipartBodyLengthLimit = 5 * 1024 * 1024)]
8282
[RequestSizeLimit(5 * 1024 * 1024)]
83-
public async Task<IActionResult> GenerateStoreImages([FromForm] StoreImageGenerationRequest request, [FromServices] StoreImageGenerationService imageGenerator, CancellationToken cancelToken)
83+
public async Task<IActionResult> GenerateStoreImages([FromForm] StoreImageCreationOptions request, [FromServices] StoreImageCreator imageGenerator, CancellationToken cancelToken)
8484
{
8585
try
8686
{
87-
var storeImagesZipStream = await imageGenerator.CreateStoreImagesZipAsync(request, cancelToken);
87+
using var baseImageStream = request.BaseImage.OpenReadStream();
88+
var storeImagesZipStream = await imageGenerator.CreateStoreImagesZipAsync(baseImageStream, request.BaseImage.ContentType, request.Padding, request.BackgroundColor, request.Platforms, cancelToken);
8889
return File(storeImagesZipStream, "application/zip", "appstore-images.zip");
8990
}
9091
catch (Exception imageGeneratorError)
9192
{
92-
logger.LogError(imageGeneratorError, "Error generating store images for image of size {size} for {platforms} with padding {padding} and color {color}.", request.BaseImage.Length, string.Join(", ", request.Platform), request.Padding, request.Color);
93+
logger.LogError(imageGeneratorError, "Error generating store images for image of size {size} for {platforms} with padding {padding} and color {color}.", request.BaseImage.Length, string.Join(", ", request.Platforms), request.Padding, request.BackgroundColor);
9394
return StatusCode(500, $"An error occurred while generating store images: {imageGeneratorError.Message}");
9495
}
9596
}

apps/pwabuilder/Frontend/src/script/pages/image-generator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,11 +337,11 @@ export class ImageGenerator extends LitElement {
337337

338338
form.append("baseImage", file as Blob);
339339
form.append("padding", String(this.padding));
340-
form.append("color", colorValue);
340+
form.append("backgroundColor", colorValue);
341341

342342
platformsData
343343
.filter((_, index) => this.platformSelected[index])
344-
.forEach(data => form.append("platform", data.value));
344+
.forEach(data => form.append("platforms", data.value));
345345

346346
const createStoreImagesRequest = await fetch("/api/images/generateStoreImages", {
347347
method: "POST",

apps/pwabuilder/Models/IOS/ImageGeneratorServiceZipFile.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ public ImageGeneratorServiceZipFile(ZipArchive zip)
1414

1515
public ZipArchiveEntry? GetTargetSize(ImageTargetSize size)
1616
{
17-
// Square44x44Logo isn't a typo here - the image generator service uses those files names, then appends the actual size to the file name.
1817
return zip.GetEntry($"ios/{size.ToFileName()}.png");
1918
}
2019

apps/pwabuilder/Models/StoreImageGenerationRequest.cs renamed to apps/pwabuilder/Models/StoreImageCreationOptions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace PWABuilder.Models;
55
/// <summary>
66
/// Represents a request to generate store images from a base image.
77
/// </summary>
8-
public sealed class StoreImageGenerationRequest
8+
public sealed class StoreImageCreationOptions
99
{
1010
/// <summary>
1111
/// The base image file uploaded by the client.
@@ -19,13 +19,13 @@ public sealed class StoreImageGenerationRequest
1919
public required double Padding { get; set; }
2020

2121
/// <summary>
22-
/// The background color to use. Can be a hex color, named color, or "transparent".
22+
/// The background color to use. Can be a hex color, named color, or "transparent". This color will be used when adding any specified padding or for the background when bringing the image into a non-square target.
2323
/// </summary>
24-
public required string Color { get; set; }
24+
public required string BackgroundColor { get; set; }
2525

2626
/// <summary>
2727
/// One or more target platform identifiers for which store images should be generated.
2828
/// </summary>
2929
[MinLength(1)]
30-
public required List<string> Platform { get; set; }
30+
public required List<string> Platforms { get; set; }
3131
}

apps/pwabuilder/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
builder.Services.AddSingleton<ViteEntryPointProvider>();
3838
builder.Services.AddSingleton<IPuppeteerService, PuppeteerService>();
3939
builder.Services.AddTransient<TempDirectory>();
40-
builder.Services.AddTransient<ImageGenerator>();
40+
builder.Services.AddTransient<IOSImageWriter>();
4141
builder.Services.AddTransient<IOSPackageCreator>();
4242
if (builder.Environment.IsDevelopment())
4343
{
@@ -73,7 +73,7 @@
7373
builder.Services.AddSingleton<ManifestAnalyzer>();
7474
builder.Services.AddSingleton<IServiceWorkerAnalyzer, ServiceWorkerAnalyzer>();
7575
builder.Services.AddSingleton<IImageValidationService, ImageValidationService>();
76-
builder.Services.AddSingleton<StoreImageGenerationService>();
76+
builder.Services.AddSingleton<StoreImageCreator>();
7777
builder.Services.AddSingleton(services =>
7878
{
7979
// Create a single, reusable Puppeteer browser instance. This can be used across different requests so that we're not spinning up multiple browsers for each request.

apps/pwabuilder/Services/IOS/ImageGenerator.cs renamed to apps/pwabuilder/Services/IOS/IOSImageWriter.cs

Lines changed: 26 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,27 @@
55
using PWABuilder.IOS.Common;
66
using PWABuilder.IOS.Models;
77
using PWABuilder.Models;
8+
using PWABuilder.Services;
89

910
namespace PWABuilder.IOS.Services
1011
{
1112
/// <summary>
12-
/// Creates the recommended Windows package images using images from the <see cref="WindowsAppPackageOptions"/>, images from the PWA's web app manifest, and images generated on behalf of the user from the PWABuilder app image generator service.
13+
/// Creates the recommended iOS package images using images from the <see cref="IOSAppPackageOptions"/>, images from the PWA's web app manifest, and images generated on behalf of the user from the PWABuilder image generator service.
1314
/// </summary>
14-
/// <remarks>
15-
/// PWABuilder app image generator service is at https://www.pwabuilder.com/imageGenerator
16-
/// </remarks>
17-
public class ImageGenerator
15+
public class IOSImageWriter
1816
{
19-
private readonly ILogger<ImageGenerator> logger;
17+
private readonly StoreImageCreator storeImageCreator;
2018
private readonly HttpClient http;
21-
private readonly Uri imageGeneratorServiceUrl;
19+
private readonly ILogger<IOSImageWriter> logger;
2220

23-
public ImageGenerator(
21+
public IOSImageWriter(
22+
StoreImageCreator storeImageCreator,
2423
IHttpClientFactory httpClientFactory,
25-
IOptions<AppSettings> appSettings,
26-
ILogger<ImageGenerator> logger
24+
ILogger<IOSImageWriter> logger
2725
)
2826
{
29-
http = httpClientFactory.CreateClient(Constants.PwaBuilderAgentHttpClient);
30-
imageGeneratorServiceUrl = new Uri(appSettings.Value.ImageGeneratorApiUrl);
27+
this.storeImageCreator = storeImageCreator;
28+
this.http = httpClientFactory.CreateClient();
3129
this.logger = logger;
3230
}
3331

@@ -39,15 +37,15 @@ ILogger<ImageGenerator> logger
3937
/// <param name="manifest">The web app manifest containing one or more images.</param>
4038
/// <param name="outputDirectory">The directory to write the images to.</param>
4139
/// <returns>An object containing the paths to the generated images.</returns>
42-
public async Task<ImageGeneratorResult> Generate(
40+
public async Task<ImageGeneratorResult> WriteImages(
4341
IOSAppPackageOptions.Validated options,
4442
WebAppManifestContext manifest,
4543
string outputDirectory
4644
)
4745
{
4846
// 1. Generate images using PWABuilder's image generation service.
4947
// These will be used as a backup if the options and manifest are missing images.
50-
var generatedImagesZip = await InvokePwabuilderImageGeneratorService(options, manifest);
48+
using var generatedImagesZip = await GenerateStoreImagesZip(options, manifest);
5149

5250
// 2. Assemble the image sources from web manifest and generated images zip.
5351
var imageSources = GetImageSourcesForTargetSizes(manifest, generatedImagesZip);
@@ -57,30 +55,32 @@ string outputDirectory
5755
return new ImageGeneratorResult(imagePaths);
5856
}
5957

60-
private async Task<ImageGeneratorServiceZipFile> InvokePwabuilderImageGeneratorService(
58+
private async Task<ImageGeneratorServiceZipFile> GenerateStoreImagesZip(
6159
IOSAppPackageOptions.Validated options,
6260
WebAppManifestContext webManifest
6361
)
6462
{
6563
var baseImageBytes = await GetBaseImage(options, webManifest);
66-
var imagesZipUrl = await CreateIOSImagesZip(baseImageBytes, 0, "transparent");
67-
return await DownloadIOSImagesZip(imagesZipUrl);
64+
return await CreateIOSImagesZip(baseImageBytes, 0, "white");
6865
}
6966

7067
private async Task<byte[]> GetBaseImage(
7168
IOSAppPackageOptions.Validated options,
7269
WebAppManifestContext webManifest
7370
)
7471
{
75-
// Find a base image from which to generate all Windows package images.
72+
// Find a base image from which to generate all iOS package images.
7673
// Best: the user supplied an image.
7774
// Second best: the largest square image from the manifest
7875
// Better than nothing: the largest image from the manifest
7976

8077
// Go through each source and see if we can get the bytes for it.
8178
var baseImageSources = new[]
8279
{
83-
(source: options.ImageUri, description: "the base image from package options"),
80+
(
81+
source: options.ImageUri,
82+
description: "the base image from package options"
83+
),
8484
(
8585
source: webManifest.GetIconSuitableForIOSApps(1024),
8686
description: "the largest square PNG icon 1024x1024 or larger from web manifest"
@@ -100,7 +100,7 @@ WebAppManifestContext webManifest
100100
(
101101
source: webManifest.GetIconSuitableForIOSApps(0),
102102
description: "the largest square PNG icon from web manifest"
103-
),
103+
)
104104
};
105105

106106
if (baseImageSources.All(s => s.source == null))
@@ -127,72 +127,16 @@ WebAppManifestContext webManifest
127127
}
128128

129129
throw new InvalidOperationException(
130-
$"Couldn't find a suitable base image from which to generate all Windows package images. Please ensure your web app manifest has a square PNG image 512x512 or larger. Base image sources: {string.Join(", ", baseImageSources)}"
131-
);
132-
}
133-
134-
private async Task<Uri> CreateIOSImagesZip(
135-
byte[] image,
136-
double padding,
137-
string backgroundColor
138-
)
139-
{
140-
// The image generation API takes the following parameters, documented here: https://github.com/pwa-builder/pwabuilder-Image-Generator
141-
// - fileName: bytes
142-
// - padding: double (0 = no padding, 1 = max padding)
143-
// - color: hex color, named color, or "transparent"
144-
// - platform: ios
145-
// - colorChanged: if colorOption = "choose", this should be 1. Otherwise, omit.
146-
var fileContent = new StreamContent(new MemoryStream(image));
147-
fileContent.Headers.ContentDisposition =
148-
new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data")
149-
{
150-
Name = "file",
151-
FileName = "image.png",
152-
};
153-
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(
154-
"application/octet-stream"
130+
$"Couldn't find a suitable base image from which to generate all iOS package images. Please ensure your web app manifest has a square PNG image 512x512 or larger. Base image sources: {string.Join(", ", baseImageSources)}"
155131
);
156-
var imageGeneratorArgs = new MultipartFormDataContent
157-
{
158-
{ fileContent, "fileName" },
159-
{ new StringContent(padding.ToString()), "padding" },
160-
{ new StringContent(backgroundColor), "color" },
161-
{ new StringContent("ios"), "platform" },
162-
};
163-
164-
var imagesResponse = await http.PostAsync(imageGeneratorServiceUrl, imageGeneratorArgs);
165-
if (!imagesResponse.IsSuccessStatusCode)
166-
{
167-
throw new HttpRequestException(
168-
$"Image generator service call failed with status {(int)imagesResponse.StatusCode} ({imagesResponse.StatusCode}): {imagesResponse.ReasonPhrase}"
169-
);
170-
}
171-
172-
var imagesResponseString = await imagesResponse.Content.ReadAsStringAsync(); // it should be a JSON string containing a ImageGeneratorServiceResult
173-
var imagesResult = JsonSerializer.Deserialize<ImageGeneratorServiceResult>(
174-
imagesResponseString,
175-
new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
176-
);
177-
if (imagesResult == null)
178-
{
179-
throw new InvalidOperationException("Unable to deserialize image generator result");
180-
}
181-
if (!Uri.TryCreate(imageGeneratorServiceUrl, imagesResult.Uri, out var imagesZipUri))
182-
{
183-
throw new Exception(
184-
$"Unable to generate images for Windows package. The image URI returned from the image generator service, '{imagesResult.Uri}', is an invalid URI. Raw response: {imagesResponseString}"
185-
);
186-
}
187-
188-
return imagesZipUri;
189132
}
190133

191-
private async Task<ImageGeneratorServiceZipFile> DownloadIOSImagesZip(Uri url)
134+
private async Task<ImageGeneratorServiceZipFile> CreateIOSImagesZip(byte[] imageBytes, double padding, string backgroundColor)
192135
{
193-
var zipStream = await http.GetStreamAsync(url);
194-
var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Read, false);
195-
return new ImageGeneratorServiceZipFile(zipArchive);
136+
using var baseImageStream = new MemoryStream(imageBytes);
137+
var zipStream = await storeImageCreator.CreateStoreImagesZipAsync(baseImageStream, null, padding, backgroundColor, ["ios"], CancellationToken.None);
138+
var zipFile = new ZipArchive(zipStream, ZipArchiveMode.Read, false);
139+
return new ImageGeneratorServiceZipFile(zipFile);
196140
}
197141

198142
private IEnumerable<ImageSource> GetImageSourcesForTargetSizes(

apps/pwabuilder/Services/IOS/IOSPackageCreator.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,21 @@ namespace PWABuilder.IOS.Services
88
{
99
public class IOSPackageCreator
1010
{
11-
private readonly ImageGenerator imageGenerator;
11+
private readonly IOSImageWriter iosImageWriter;
1212
private readonly TempDirectory temp;
1313
private readonly AppSettings appSettings;
1414
private readonly ILogger<IOSPackageCreator> logger;
1515
private readonly IWebHostEnvironment env;
1616

1717
public IOSPackageCreator(
18-
ImageGenerator imageGenerator,
18+
IOSImageWriter iosImageWriter,
1919
IOptions<AppSettings> appSettings,
2020
TempDirectory temp,
2121
ILogger<IOSPackageCreator> logger,
2222
IWebHostEnvironment env
2323
)
2424
{
25-
this.imageGenerator = imageGenerator;
25+
this.iosImageWriter = iosImageWriter;
2626
this.appSettings = appSettings.Value;
2727
this.temp = temp;
2828
this.logger = logger;
@@ -52,7 +52,7 @@ public async Task<byte[]> Create(IOSAppPackageOptions.Validated options)
5252

5353
// Create any missing images for the iOS template.
5454
// This should be done before project.ApplyChanges(). Otherwise, it'll attempt to write the images to the "pwa-shell" directory, which no longer exists after ApplyChanges().
55-
await imageGenerator.Generate(
55+
await iosImageWriter.WriteImages(
5656
options,
5757
WebAppManifestContext.From(options.Manifest, options.ManifestUri),
5858
outputDir

0 commit comments

Comments
 (0)