Skip to content

Commit 0701021

Browse files
authored
Merge pull request #1686 from IldarKhayrutdinov/encode-multiframe
Encode multi-frame Tiff format
2 parents 684a4dc + ffa8af7 commit 0701021

File tree

9 files changed

+471
-203
lines changed

9 files changed

+471
-203
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
namespace SixLabors.ImageSharp.Common.Helpers
5+
{
6+
internal readonly struct ExifResolutionValues
7+
{
8+
public ExifResolutionValues(ushort resolutionUnit, double? horizontalResolution, double? verticalResolution)
9+
{
10+
this.ResolutionUnit = resolutionUnit;
11+
this.HorizontalResolution = horizontalResolution;
12+
this.VerticalResolution = verticalResolution;
13+
}
14+
15+
public ushort ResolutionUnit { get; }
16+
17+
public double? HorizontalResolution { get; }
18+
19+
public double? VerticalResolution { get; }
20+
}
21+
}

src/ImageSharp/Common/Helpers/UnitConverter.cs

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,14 @@ public static PixelResolutionUnit ExifProfileToResolutionUnit(ExifProfile profil
9898
}
9999

100100
/// <summary>
101-
/// Sets the exif profile resolution values.
101+
/// Gets the exif profile resolution values.
102102
/// </summary>
103-
/// <param name="exifProfile">The exif profile.</param>
104103
/// <param name="unit">The resolution unit.</param>
105104
/// <param name="horizontal">The horizontal resolution value.</param>
106105
/// <param name="vertical">The vertical resolution value.</param>
106+
/// <returns><see cref="ExifResolutionValues"/></returns>
107107
[MethodImpl(InliningOptions.ShortMethod)]
108-
public static void SetResolutionValues(ExifProfile exifProfile, PixelResolutionUnit unit, double horizontal, double vertical)
108+
public static ExifResolutionValues GetExifResolutionValues(PixelResolutionUnit unit, double horizontal, double vertical)
109109
{
110110
switch (unit)
111111
{
@@ -115,9 +115,9 @@ public static void SetResolutionValues(ExifProfile exifProfile, PixelResolutionU
115115
break;
116116
case PixelResolutionUnit.PixelsPerMeter:
117117
{
118-
unit = PixelResolutionUnit.PixelsPerCentimeter;
119-
horizontal = UnitConverter.MeterToCm(horizontal);
120-
vertical = UnitConverter.MeterToCm(vertical);
118+
unit = PixelResolutionUnit.PixelsPerCentimeter;
119+
horizontal = MeterToCm(horizontal);
120+
vertical = MeterToCm(vertical);
121121
}
122122

123123
break;
@@ -126,18 +126,13 @@ public static void SetResolutionValues(ExifProfile exifProfile, PixelResolutionU
126126
break;
127127
}
128128

129-
exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)(unit + 1));
130-
129+
ushort exifUnit = (ushort)(unit + 1);
131130
if (unit == PixelResolutionUnit.AspectRatio)
132131
{
133-
exifProfile.RemoveValue(ExifTag.XResolution);
134-
exifProfile.RemoveValue(ExifTag.YResolution);
135-
}
136-
else
137-
{
138-
exifProfile.SetValue(ExifTag.XResolution, new Rational(horizontal));
139-
exifProfile.SetValue(ExifTag.YResolution, new Rational(vertical));
132+
return new ExifResolutionValues(exifUnit, null, null);
140133
}
134+
135+
return new ExifResolutionValues(exifUnit, horizontal, vertical);
141136
}
142137
}
143138
}

src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
7474
/// </summary>
7575
private const TiffPhotometricInterpretation DefaultPhotometricInterpretation = TiffPhotometricInterpretation.Rgb;
7676

77+
private readonly List<(long, uint)> frameMarkers = new List<(long, uint)>();
78+
7779
/// <summary>
7880
/// Initializes a new instance of the <see cref="TiffEncoderCore"/> class.
7981
/// </summary>
@@ -147,13 +149,25 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
147149
// Make sure, the Encoder options makes sense in combination with each other.
148150
this.SanitizeAndSetEncoderOptions(bitsPerPixel, image.PixelType.BitsPerPixel, photometricInterpretation, compression, predictor);
149151

150-
using (var writer = new TiffStreamWriter(stream))
152+
using var writer = new TiffStreamWriter(stream);
153+
long ifdMarker = this.WriteHeader(writer);
154+
155+
Image<TPixel> metadataImage = image;
156+
foreach (ImageFrame<TPixel> frame in image.Frames)
151157
{
152-
long firstIfdMarker = this.WriteHeader(writer);
158+
var subfileType = (TiffNewSubfileType)(frame.Metadata.ExifProfile?.GetValue(ExifTag.SubfileType)?.Value ?? (int)TiffNewSubfileType.FullImage);
153159

154-
// TODO: multiframing is not supported
155-
this.WriteImage(writer, image, firstIfdMarker);
160+
ifdMarker = this.WriteFrame(writer, frame, image.Metadata, metadataImage, ifdMarker);
161+
metadataImage = null;
162+
}
163+
164+
long currentOffset = writer.BaseStream.Position;
165+
foreach ((long, uint) marker in this.frameMarkers)
166+
{
167+
writer.WriteMarkerFast(marker.Item1, marker.Item2);
156168
}
169+
170+
writer.BaseStream.Seek(currentOffset, SeekOrigin.Begin);
157171
}
158172

159173
/// <summary>
@@ -174,41 +188,56 @@ public long WriteHeader(TiffStreamWriter writer)
174188
/// Writes all data required to define an image.
175189
/// </summary>
176190
/// <typeparam name="TPixel">The pixel format.</typeparam>
177-
/// <param name="writer">The <see cref="BinaryWriter"/> to write data to.</param>
178-
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
191+
/// <param name="writer">The <see cref="BinaryWriter" /> to write data to.</param>
192+
/// <param name="frame">The tiff frame.</param>
193+
/// <param name="imageMetadata">The image metadata (resolution values for each frame).</param>
194+
/// <param name="image">The image (common metadata for root frame).</param>
179195
/// <param name="ifdOffset">The marker to write this IFD offset.</param>
180-
private void WriteImage<TPixel>(TiffStreamWriter writer, Image<TPixel> image, long ifdOffset)
196+
/// <returns>
197+
/// The next IFD offset value.
198+
/// </returns>
199+
private long WriteFrame<TPixel>(
200+
TiffStreamWriter writer,
201+
ImageFrame<TPixel> frame,
202+
ImageMetadata imageMetadata,
203+
Image<TPixel> image,
204+
long ifdOffset)
181205
where TPixel : unmanaged, IPixel<TPixel>
182206
{
183-
var entriesCollector = new TiffEncoderEntriesCollector();
184-
185207
using TiffBaseCompressor compressor = TiffCompressorFactory.Create(
186208
this.CompressionType ?? TiffCompression.None,
187209
writer.BaseStream,
188210
this.memoryAllocator,
189-
image.Width,
211+
frame.Width,
190212
(int)this.BitsPerPixel,
191213
this.compressionLevel,
192214
this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor.Value : TiffPredictor.None);
193215

216+
var entriesCollector = new TiffEncoderEntriesCollector();
194217
using TiffBaseColorWriter<TPixel> colorWriter = TiffColorWriterFactory.Create(
195218
this.PhotometricInterpretation,
196-
image.Frames.RootFrame,
219+
frame,
197220
this.quantizer,
198221
this.memoryAllocator,
199222
this.configuration,
200223
entriesCollector,
201224
(int)this.BitsPerPixel);
202225

203-
int rowsPerStrip = this.CalcRowsPerStrip(image.Frames.RootFrame.Height, colorWriter.BytesPerRow);
226+
int rowsPerStrip = this.CalcRowsPerStrip(frame.Height, colorWriter.BytesPerRow);
204227

205228
colorWriter.Write(compressor, rowsPerStrip);
206229

230+
if (image != null)
231+
{
232+
entriesCollector.ProcessMetadata(image);
233+
}
234+
235+
entriesCollector.ProcessFrameInfo(frame, imageMetadata);
207236
entriesCollector.ProcessImageFormat(this);
208-
entriesCollector.ProcessGeneral(image);
209237

210-
writer.WriteMarker(ifdOffset, (uint)writer.Position);
211-
long nextIfdMarker = this.WriteIfd(writer, entriesCollector.Entries);
238+
this.frameMarkers.Add((ifdOffset, (uint)writer.Position));
239+
240+
return this.WriteIfd(writer, entriesCollector.Entries);
212241
}
213242

214243
/// <summary>
@@ -272,7 +301,7 @@ private long WriteIfd(TiffStreamWriter writer, List<IExifValue> entries)
272301
}
273302
else
274303
{
275-
var raw = new byte[length];
304+
byte[] raw = new byte[length];
276305
int sz = ExifWriter.WriteValue(entry, raw, 0);
277306
DebugGuard.IsTrue(sz == raw.Length, "Incorrect number of bytes written");
278307
largeDataBlocks.Add(raw);

0 commit comments

Comments
 (0)