Repository navigation
IPTC data from a cloned profile or image is written without the 1:90 UTF-8 character set record #3238
Description
Prerequisites
- I have written a descriptive issue title
- I have verified that I am running the latest version of ImageSharp
- I have verified if the problem exist in both
DEBUGandRELEASEmode - I have searched open and closed issues to ensure it has not already been reported
ImageSharp version
main @ a73c43d (also present in v4.1.3, same code)
Other ImageSharp packages and versions
None
Environment (Operating system, version and so on)
Windows 11 Pro 10.0.22631, x64
.NET Framework version
.NET 10 (net10.0)
Description
When an IPTC value uses UTF-8, IptcProfile.UpdateData() should start the data with the 1:90 Coded Character Set dataset (1C 01 5A 00 03 1B 25 47, i.e. ESC % G). Other applications use this record to decide how to read the text. Without it they read the bytes as Latin-1, so "ESPAÑA" shows as "ESPAÑA".
The record is not written when the profile is a deep clone. This happens with image.Clone() followed by a save. It is also not written when the value was set with the caller's own UTF8Encoding instance, such as new UTF8Encoding(false).
Cause: IptcProfile.HasValuesInUtf8() compares by reference (value.Encoding == Encoding.UTF8). IptcValue.DeepClone() clones the encoding ((Encoding)other.Encoding.Clone()). After that, the value is still UTF-8 but is no longer the Encoding.UTF8 instance, so the check fails.
ImageSharp itself reads the text back correctly, because its reader decodes as UTF-8 by default. The problem is visible only in other readers.
A fix is proposed in #3226.
Steps to Reproduce
using System.Text;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
using SixLabors.ImageSharp.PixelFormats;
// 1. Cloned profile.
IptcProfile profile = new();
profile.SetValue(IptcTag.City, "ESPAÑA");
IptcProfile clone = profile.DeepClone();
profile.UpdateData();
clone.UpdateData();
Console.WriteLine(Convert.ToHexString(profile.Data.AsSpan(0, 8))); // 1C015A00031B2547
Console.WriteLine(Convert.ToHexString(clone.Data.AsSpan(0, 8))); // 1C025A0007455350 (no 1:90 record)
// 2. Value set with a separate UTF8Encoding instance.
IptcProfile own = new();
own.SetValue(IptcTag.City, new UTF8Encoding(false), "ESPAÑA");
own.UpdateData();
Console.WriteLine(Convert.ToHexString(own.Data.AsSpan(0, 8))); // 1C025A0007455350 (no 1:90 record)
// 3. Cloned image saved as JPEG.
using Image<Rgba32> image = new(1, 1);
image.Metadata.IptcProfile = new IptcProfile();
image.Metadata.IptcProfile.SetValue(IptcTag.Caption, "ESPAÑA");
using Image<Rgba32> cloned = image.Clone();
using MemoryStream ms = new();
cloned.SaveAsJpeg(ms);
ms.Position = 0;
using Image reloaded = Image.Load(ms);
Console.WriteLine(Convert.ToHexString(reloaded.Metadata.IptcProfile!.Data));Actual result for case 3: the written IPTC data has only the caption dataset (2:120), with no 1:90 record before it:
1C0278000745535041C39141
Expected result: in all three cases the data starts with the 1:90 record, as it does for a profile that was not cloned:
1C015A00031B25471C0278000745535041C39141
Images
No images needed; the repro builds the input in memory.
Activity