Browse Source

Synchronize profiles on save.

pull/2751/head
James Jackson-South 2 years ago
parent
commit
8b5703c916
  1. 7
      src/ImageSharp/Formats/ImageEncoder.cs
  2. 6
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  3. 1
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  4. 2
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  5. 2
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  6. 22
      src/ImageSharp/Image.cs
  7. 5
      src/ImageSharp/Metadata/ImageFrameMetadata.cs
  8. 11
      src/ImageSharp/Metadata/ImageMetadata.cs
  9. 11
      src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
  10. 29
      tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs

7
src/ImageSharp/Formats/ImageEncoder.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
@ -42,6 +41,8 @@ public abstract class ImageEncoder : IImageEncoder
private void EncodeWithSeekableStream<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
image.SynchronizeMetadata();
Configuration configuration = image.Configuration;
if (stream.CanSeek)
{
@ -59,6 +60,8 @@ public abstract class ImageEncoder : IImageEncoder
private async Task EncodeWithSeekableStreamAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
image.SynchronizeMetadata();
Configuration configuration = image.Configuration;
if (stream.CanSeek)
{
@ -66,7 +69,7 @@ public abstract class ImageEncoder : IImageEncoder
}
else
{
using ChunkedMemoryStream ms = new(configuration.MemoryAllocator);
await using ChunkedMemoryStream ms = new(configuration.MemoryAllocator);
await DoEncodeAsync(ms);
ms.Position = 0;
await ms.CopyToAsync(stream, configuration.StreamProcessingBufferSize, cancellationToken)

6
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -539,17 +539,11 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
/// <param name="buffer">Temporary buffer.</param>
private void WriteProfiles(ImageMetadata metadata, Span<byte> buffer)
{
if (metadata is null)
{
return;
}
// For compatibility, place the profiles in the following order:
// - APP1 EXIF
// - APP1 XMP
// - APP2 ICC
// - APP13 IPTC
metadata.SyncProfiles();
this.WriteExifProfile(metadata.ExifProfile, buffer);
this.WriteXmpProfile(metadata.XmpProfile, buffer);
this.WriteIccProfile(metadata.IccProfile, buffer);

1
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -776,7 +776,6 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
return;
}
meta.SyncProfiles();
this.WriteChunk(stream, PngChunkType.Exif, meta.ExifProfile.ToByteArray());
}

2
src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs

@ -241,8 +241,6 @@ internal class Vp8LEncoder : IDisposable
{
// Write bytes from the bit-writer buffer to the stream.
ImageMetadata metadata = image.Metadata;
metadata.SyncProfiles();
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;

2
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs

@ -315,8 +315,6 @@ internal class Vp8Encoder : IDisposable
{
// Write bytes from the bitwriter buffer to the stream.
ImageMetadata metadata = image.Metadata;
metadata.SyncProfiles();
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;

22
src/ImageSharp/Image.cs

@ -157,6 +157,28 @@ public abstract partial class Image : IDisposable, IConfigurationProvider
public abstract Image<TPixel2> CloneAs<TPixel2>(Configuration configuration)
where TPixel2 : unmanaged, IPixel<TPixel2>;
/// <summary>
/// Synchronizes any embedded metadata profiles with the current image properties.
/// </summary>
public void SynchronizeMetadata()
{
this.Metadata.SynchronizeProfiles();
foreach (ImageFrame frame in this.Frames)
{
frame.Metadata.SynchronizeProfiles();
}
}
/// <summary>
/// Synchronizes any embedded metadata profiles with the current image properties.
/// </summary>
/// <param name="action">A synchronization action to run in addition to the default process.</param>
public void SynchronizeMetadata(Action<Image> action)
{
this.SynchronizeMetadata();
action(this);
}
/// <summary>
/// Update the size of the image after mutation.
/// </summary>

5
src/ImageSharp/Metadata/ImageFrameMetadata.cs

@ -131,4 +131,9 @@ public sealed class ImageFrameMetadata : IDeepCloneable<ImageFrameMetadata>
where TFormatMetadata : class
where TFormatFrameMetadata : class, IFormatFrameMetadata<TFormatFrameMetadata>
=> ((IDeepCloneable<TFormatFrameMetadata>)this.GetFormatMetadata(key)).DeepClone();
/// <summary>
/// Synchronizes the profiles with the current metadata.
/// </summary>
internal void SynchronizeProfiles() => this.ExifProfile?.Sync(this);
}

11
src/ImageSharp/Metadata/ImageMetadata.cs

@ -219,6 +219,11 @@ public sealed class ImageMetadata : IDeepCloneable<ImageMetadata>
/// <inheritdoc/>
public ImageMetadata DeepClone() => new(this);
/// <summary>
/// Synchronizes the profiles with the current metadata.
/// </summary>
internal void SynchronizeProfiles() => this.ExifProfile?.Sync(this);
internal PixelTypeInfo GetDecodedPixelTypeInfo()
{
// None found. Check if we have a decoded format to convert from.
@ -231,10 +236,4 @@ public sealed class ImageMetadata : IDeepCloneable<ImageMetadata>
// This should never happen.
return default;
}
/// TODO: This should be called on save.
/// <summary>
/// Synchronizes the profiles with the current metadata.
/// </summary>
internal void SyncProfiles() => this.ExifProfile?.Sync(this);
}

11
src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs

@ -298,6 +298,17 @@ public sealed class ExifProfile : IDeepCloneable<ExifProfile>
this.SyncResolution(ExifTag.YResolution, metadata.VerticalResolution);
}
/// <summary>
/// Synchronizes the profiles with the specified metadata.
/// </summary>
/// <param name="metadata">The metadata.</param>
#pragma warning disable CA1822, RCS1163, IDE0060
internal void Sync(ImageFrameMetadata metadata)
#pragma warning restore IDE0060, RCS1163, CA1822
{
// Nothing to do ....YET.
}
private void SyncResolution(ExifTag<Rational> tag, double resolution)
{
if (!this.TryGetValue(tag, out IExifValue<Rational>? value))

29
tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs

@ -16,9 +16,9 @@ public class ImageMetadataTests
[Fact]
public void ConstructorImageMetadata()
{
var metaData = new ImageMetadata();
ImageMetadata metaData = new();
var exifProfile = new ExifProfile();
ExifProfile exifProfile = new();
metaData.ExifProfile = exifProfile;
metaData.HorizontalResolution = 4;
@ -34,7 +34,7 @@ public class ImageMetadataTests
[Fact]
public void CloneIsDeep()
{
var metaData = new ImageMetadata
ImageMetadata metaData = new()
{
ExifProfile = new ExifProfile(),
HorizontalResolution = 4,
@ -53,7 +53,7 @@ public class ImageMetadataTests
[Fact]
public void HorizontalResolution()
{
var metaData = new ImageMetadata();
ImageMetadata metaData = new();
Assert.Equal(96, metaData.HorizontalResolution);
metaData.HorizontalResolution = 0;
@ -69,7 +69,7 @@ public class ImageMetadataTests
[Fact]
public void VerticalResolution()
{
var metaData = new ImageMetadata();
ImageMetadata metaData = new();
Assert.Equal(96, metaData.VerticalResolution);
metaData.VerticalResolution = 0;
@ -85,20 +85,19 @@ public class ImageMetadataTests
[Fact]
public void SyncProfiles()
{
var exifProfile = new ExifProfile();
ExifProfile exifProfile = new();
exifProfile.SetValue(ExifTag.XResolution, new Rational(200));
exifProfile.SetValue(ExifTag.YResolution, new Rational(300));
using (var image = new Image<Rgba32>(1, 1))
{
image.Metadata.ExifProfile = exifProfile;
image.Metadata.HorizontalResolution = 400;
image.Metadata.VerticalResolution = 500;
using Image<Rgba32> image = new(1, 1);
image.Metadata.ExifProfile = exifProfile;
image.Metadata.HorizontalResolution = 400;
image.Metadata.VerticalResolution = 500;
image.Metadata.SyncProfiles();
using MemoryStream memoryStream = new();
image.SaveAsBmp(memoryStream);
Assert.Equal(400, image.Metadata.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble());
Assert.Equal(500, image.Metadata.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble());
}
Assert.Equal(400, image.Metadata.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble());
Assert.Equal(500, image.Metadata.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble());
}
}

Loading…
Cancel
Save