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. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -42,6 +41,8 @@ public abstract class ImageEncoder : IImageEncoder
private void EncodeWithSeekableStream<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) private void EncodeWithSeekableStream<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
image.SynchronizeMetadata();
Configuration configuration = image.Configuration; Configuration configuration = image.Configuration;
if (stream.CanSeek) if (stream.CanSeek)
{ {
@ -59,6 +60,8 @@ public abstract class ImageEncoder : IImageEncoder
private async Task EncodeWithSeekableStreamAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) private async Task EncodeWithSeekableStreamAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
image.SynchronizeMetadata();
Configuration configuration = image.Configuration; Configuration configuration = image.Configuration;
if (stream.CanSeek) if (stream.CanSeek)
{ {
@ -66,7 +69,7 @@ public abstract class ImageEncoder : IImageEncoder
} }
else else
{ {
using ChunkedMemoryStream ms = new(configuration.MemoryAllocator); await using ChunkedMemoryStream ms = new(configuration.MemoryAllocator);
await DoEncodeAsync(ms); await DoEncodeAsync(ms);
ms.Position = 0; ms.Position = 0;
await ms.CopyToAsync(stream, configuration.StreamProcessingBufferSize, cancellationToken) 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> /// <param name="buffer">Temporary buffer.</param>
private void WriteProfiles(ImageMetadata metadata, Span<byte> buffer) private void WriteProfiles(ImageMetadata metadata, Span<byte> buffer)
{ {
if (metadata is null)
{
return;
}
// For compatibility, place the profiles in the following order: // For compatibility, place the profiles in the following order:
// - APP1 EXIF // - APP1 EXIF
// - APP1 XMP // - APP1 XMP
// - APP2 ICC // - APP2 ICC
// - APP13 IPTC // - APP13 IPTC
metadata.SyncProfiles();
this.WriteExifProfile(metadata.ExifProfile, buffer); this.WriteExifProfile(metadata.ExifProfile, buffer);
this.WriteXmpProfile(metadata.XmpProfile, buffer); this.WriteXmpProfile(metadata.XmpProfile, buffer);
this.WriteIccProfile(metadata.IccProfile, buffer); this.WriteIccProfile(metadata.IccProfile, buffer);

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

@ -776,7 +776,6 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
return; return;
} }
meta.SyncProfiles();
this.WriteChunk(stream, PngChunkType.Exif, meta.ExifProfile.ToByteArray()); 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. // Write bytes from the bit-writer buffer to the stream.
ImageMetadata metadata = image.Metadata; ImageMetadata metadata = image.Metadata;
metadata.SyncProfiles();
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile; ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile; 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. // Write bytes from the bitwriter buffer to the stream.
ImageMetadata metadata = image.Metadata; ImageMetadata metadata = image.Metadata;
metadata.SyncProfiles();
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile; ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile; 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) public abstract Image<TPixel2> CloneAs<TPixel2>(Configuration configuration)
where TPixel2 : unmanaged, IPixel<TPixel2>; 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> /// <summary>
/// Update the size of the image after mutation. /// Update the size of the image after mutation.
/// </summary> /// </summary>

5
src/ImageSharp/Metadata/ImageFrameMetadata.cs

@ -131,4 +131,9 @@ public sealed class ImageFrameMetadata : IDeepCloneable<ImageFrameMetadata>
where TFormatMetadata : class where TFormatMetadata : class
where TFormatFrameMetadata : class, IFormatFrameMetadata<TFormatFrameMetadata> where TFormatFrameMetadata : class, IFormatFrameMetadata<TFormatFrameMetadata>
=> ((IDeepCloneable<TFormatFrameMetadata>)this.GetFormatMetadata(key)).DeepClone(); => ((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/> /// <inheritdoc/>
public ImageMetadata DeepClone() => new(this); public ImageMetadata DeepClone() => new(this);
/// <summary>
/// Synchronizes the profiles with the current metadata.
/// </summary>
internal void SynchronizeProfiles() => this.ExifProfile?.Sync(this);
internal PixelTypeInfo GetDecodedPixelTypeInfo() internal PixelTypeInfo GetDecodedPixelTypeInfo()
{ {
// None found. Check if we have a decoded format to convert from. // 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. // This should never happen.
return default; 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); 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) private void SyncResolution(ExifTag<Rational> tag, double resolution)
{ {
if (!this.TryGetValue(tag, out IExifValue<Rational>? value)) if (!this.TryGetValue(tag, out IExifValue<Rational>? value))

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

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

Loading…
Cancel
Save