mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-05-18 13:15:48 +02:00
[ImageResizer] Fix JPEG quality setting ignored after WinUI3 migration (#47134)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Restores honoring of the user-configured JPEG quality — via the Settings UI slider, the CLI `--quality` flag, or the persisted `imageresizer_jpegQualityLevel` — when resizing JPEG files. Since the WinUI3 migration (#45288) any Q value from 1 to 100 produced byte-identical output at WIC's internal default (~Q90) because the transcode encoder silently ignored the setting. Only `src/modules/imageresizer/ui/Models/ResizeOperation.cs` is changed. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #47135 <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed Built `ImageResizerCLI.csproj` in `Release|x64` with `/p:RuntimeIdentifier=win-x64`; no new warnings. Ran the resulting `x64\Release\WinUI3Apps\PowerToys.ImageResizerCLI.exe` against a synthetic test JPEG (`a.jpg` 2373×905, ~240 KB) and both in-tree EXIF assets: - `src/modules/imageresizer/tests/TestMetadataIssue1928.jpg` (42 EXIF properties) - `src/modules/imageresizer/tests/TestMetadataIssue2447.jpg` (44 EXIF properties) Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -102,11 +102,14 @@ namespace ImageResizer.Models
|
||||
using (var outputStream = _fileSystem.File.Open(path, FileMode.CreateNew, FileAccess.ReadWrite))
|
||||
{
|
||||
var winrtOutputStream = outputStream.AsRandomAccessStream();
|
||||
bool forceFresh = encoderGuid == BitmapEncoder.JpegEncoderId && !noTransformNeeded;
|
||||
|
||||
await EncodeToStreamAsync(
|
||||
decoder,
|
||||
winrtInputStream,
|
||||
winrtOutputStream,
|
||||
encoderGuid,
|
||||
forceFresh,
|
||||
async (encoder, isTranscode) =>
|
||||
{
|
||||
if (isTranscode)
|
||||
@@ -121,14 +124,6 @@ namespace ImageResizer.Models
|
||||
{
|
||||
encoder.BitmapTransform.Bounds = cropBounds.Value;
|
||||
}
|
||||
|
||||
// Apply codec-specific properties (e.g., JPEG quality).
|
||||
// Must be set after transforms since re-encoding will occur.
|
||||
var encoderProps = GetEncoderPropertySet(encoderGuid);
|
||||
if (encoderProps != null)
|
||||
{
|
||||
await encoder.BitmapProperties.SetPropertiesAsync(encoderProps);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -188,11 +183,15 @@ namespace ImageResizer.Models
|
||||
using (var outputStream = _fileSystem.File.Open(path, FileMode.CreateNew, FileAccess.ReadWrite))
|
||||
{
|
||||
var winrtOutputStream = outputStream.AsRandomAccessStream();
|
||||
|
||||
// SetSoftwareBitmap requires a fresh encoder; the transcode encoder only
|
||||
// accepts BitmapTransform. Also forces quality to apply for JPEG output.
|
||||
await EncodeToStreamAsync(
|
||||
decoder,
|
||||
winrtInputStream,
|
||||
winrtOutputStream,
|
||||
encoderGuid,
|
||||
forceFresh: true,
|
||||
(encoder, _) =>
|
||||
{
|
||||
encoder.SetSoftwareBitmap(aiResult);
|
||||
@@ -214,10 +213,12 @@ namespace ImageResizer.Models
|
||||
IRandomAccessStream inputStream,
|
||||
IRandomAccessStream outputStream,
|
||||
Guid encoderGuid,
|
||||
bool forceFresh,
|
||||
Func<BitmapEncoder, bool, Task> writeContent)
|
||||
{
|
||||
var decoderEncoderId = CodecHelper.GetEncoderIdForDecoder(decoder);
|
||||
bool canTranscode = !_settings.RemoveMetadata
|
||||
bool canTranscode = !forceFresh
|
||||
&& !_settings.RemoveMetadata
|
||||
&& decoderEncoderId.HasValue
|
||||
&& decoderEncoderId.Value == encoderGuid;
|
||||
|
||||
@@ -257,7 +258,8 @@ namespace ImageResizer.Models
|
||||
|
||||
/// <summary>
|
||||
/// Fresh encoder path: creates a blank encoder and manually writes pixel data.
|
||||
/// Used when metadata must be stripped (RemoveMetadata) or format doesn't match (ICO→PNG).
|
||||
/// Used when codec options (e.g. JPEG quality) must apply, when metadata must be
|
||||
/// stripped (RemoveMetadata), or when the format doesn't match (ICO→PNG).
|
||||
/// The <paramref name="writeContent"/> callback receives isTranscode=false and should
|
||||
/// call <see cref="EncodeFramesAsync"/> or <see cref="BitmapEncoder.SetSoftwareBitmap"/>.
|
||||
/// </summary>
|
||||
@@ -267,22 +269,18 @@ namespace ImageResizer.Models
|
||||
Guid encoderGuid,
|
||||
Func<BitmapEncoder, bool, Task> writeContent)
|
||||
{
|
||||
// Read rendering-critical metadata before encoding so we can restore it on
|
||||
// the blank encoder. Only needed for RemoveMetadata; format-mismatch files
|
||||
// (e.g. ICO) rarely carry meaningful EXIF data.
|
||||
BitmapPropertySet renderingMetadata = null;
|
||||
if (_settings.RemoveMetadata)
|
||||
{
|
||||
renderingMetadata = await ReadMetadataAsync(decoder, RenderingMetadataProperties);
|
||||
}
|
||||
// The blank encoder inherits nothing from the source, so we have to carry
|
||||
// metadata over ourselves. RemoveMetadata keeps only the rendering-critical
|
||||
// properties (orientation/colorspace); otherwise mirror the transcode path's
|
||||
// best-effort EXIF preservation.
|
||||
var propsToPreserve = _settings.RemoveMetadata
|
||||
? RenderingMetadataProperties
|
||||
: KnownMetadataProperties;
|
||||
var preservedMetadata = await ReadMetadataAsync(decoder, propsToPreserve);
|
||||
|
||||
var encoder = await CreateFreshEncoderAsync(encoderGuid, outputStream);
|
||||
await writeContent(encoder, false);
|
||||
|
||||
if (renderingMetadata != null)
|
||||
{
|
||||
await WriteMetadataAsync(encoder, renderingMetadata);
|
||||
}
|
||||
await WriteMetadataAsync(encoder, preservedMetadata);
|
||||
|
||||
await encoder.FlushAsync();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user