-
-
Notifications
You must be signed in to change notification settings - Fork 892
Add JPEG COM marker support #2641
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
bb9ed65
b3a8452
d616590
e78db37
e259e35
5127202
9260be9
dc0484e
c10863f
d9169c5
d225128
6542476
8af9a80
13625ce
a78aab1
d8484da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| // Copyright (c) Six Labors. | ||
| // Licensed under the Six Labors Split License. | ||
|
|
||
| namespace SixLabors.ImageSharp.Formats.Jpeg; | ||
|
|
||
| /// <summary> | ||
| /// Contains JPEG comment | ||
| /// </summary> | ||
| public readonly struct JpegComData | ||
| { | ||
| /// <summary> | ||
| /// Converts string to <see cref="JpegComData"/> | ||
| /// </summary> | ||
| /// <param name="value">The comment string.</param> | ||
| /// <returns>The <see cref="JpegComData"/></returns> | ||
| public static JpegComData FromString(string value) => new(value.AsMemory()); | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="JpegComData"/> struct. | ||
| /// </summary> | ||
| /// <param name="value">The comment ReadOnlyMemory of chars.</param> | ||
| public JpegComData(ReadOnlyMemory<char> value) | ||
| => this.Value = value; | ||
|
|
||
| public ReadOnlyMemory<char> Value { get; } | ||
|
|
||
| /// <summary> | ||
| /// Converts Value to string | ||
| /// </summary> | ||
| /// <returns>The comment string.</returns> | ||
| public override string ToString() => this.Value.ToString(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
| #nullable disable | ||
|
|
||
| using System.Buffers.Binary; | ||
| using System.Text; | ||
| using SixLabors.ImageSharp.Common.Helpers; | ||
| using SixLabors.ImageSharp.Formats.Jpeg.Components; | ||
| using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; | ||
|
|
@@ -89,6 +90,9 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken | |
| // Write Exif, XMP, ICC and IPTC profiles | ||
| this.WriteProfiles(metadata, buffer); | ||
|
|
||
| // Write comments | ||
| this.WriteComment(jpegMetadata); | ||
|
|
||
| // Write the image dimensions. | ||
| this.WriteStartOfFrame(image.Width, image.Height, frameConfig, buffer); | ||
|
|
||
|
|
@@ -167,6 +171,54 @@ private void WriteJfifApplicationHeader(ImageMetadata meta, Span<byte> buffer) | |
| this.outputStream.Write(buffer, 0, 18); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Writes comment | ||
| /// </summary> | ||
| /// <param name="metadata">The image metadata.</param> | ||
| private void WriteComment(JpegMetadata metadata) | ||
| { | ||
| int maxCommentLength = 65533; | ||
|
|
||
| if (metadata.Comments.Count == 0) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| for (int i = 0; i < metadata.Comments.Count; i++) | ||
| { | ||
| string comment = metadata.Comments[i].ToString(); | ||
|
|
||
| if (comment.Length > maxCommentLength) | ||
| { | ||
| string splitComment = comment.Substring(maxCommentLength, comment.Length - maxCommentLength); | ||
| metadata.Comments.Insert(i + 1, JpegComData.FromString(splitComment)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should not be changing the metadata here. Encoding should leave the image unchanged. |
||
|
|
||
| // We don't want to keep the extra bytes | ||
| comment = comment.Substring(0, maxCommentLength); | ||
| } | ||
|
|
||
| int commentLength = comment.Length + 4; | ||
|
|
||
| Span<byte> commentSpan = new byte[commentLength]; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should be able to avoid the allocation here by using a single buffer of the maximum length plus markers outside of the loop and slicing. |
||
| Span<byte> markers = commentSpan.Slice(0, 2); | ||
| Span<byte> payloadSize = commentSpan.Slice(2, 2); | ||
| Span<byte> payload = commentSpan.Slice(4, comment.Length); | ||
|
|
||
| // Beginning of comment ff fe | ||
| markers[0] = JpegConstants.Markers.XFF; | ||
| markers[1] = JpegConstants.Markers.COM; | ||
|
|
||
| // Write payload size | ||
| int comWithoutMarker = commentLength - 2; | ||
| payloadSize[0] = (byte)((comWithoutMarker >> 8) & 0xFF); | ||
| payloadSize[1] = (byte)(comWithoutMarker & 0xFF); | ||
|
|
||
| Encoding.ASCII.GetBytes(comment, payload); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't know it's ASCII. I would cast and write. |
||
|
|
||
| this.outputStream.Write(commentSpan, 0, commentSpan.Length); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Writes the Define Huffman Table marker and tables. | ||
| /// </summary> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're assuming here that the input is always ASCII when we do not actually know that. I would create a
char[]cast it tobyte[], read the stream and pass it to theJpegComDataconstructor.