Skip to content

Commit

Permalink
Add support for reading the Exif SubIFD (#1518)
Browse files Browse the repository at this point in the history
Fixes #1517
  • Loading branch information
mattleibow authored Oct 12, 2020
1 parent 753bd44 commit ceff370
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 104 deletions.
Binary file added tests/Content/images/P8211052.JPG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/Content/images/PA010741.JPG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
234 changes: 131 additions & 103 deletions tests/Tests/SKCodecTest.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Xunit;

Expand All @@ -12,13 +9,13 @@ namespace SkiaSharp.Tests
public class SKCodecTest : SKTest
{
[SkippableFact]
public void MinBufferedBytesNeededHasAValue ()
public void MinBufferedBytesNeededHasAValue()
{
Assert.True (SKCodec.MinBufferedBytesNeeded > 0);
Assert.True(SKCodec.MinBufferedBytesNeeded > 0);
}

[SkippableFact]
public unsafe void StreamLosesOwnershipTddoCodecButIsNotForgotten()
public unsafe void ImageCanBeDecodedManyTimes()
{
var codec = SKCodec.Create(Path.Combine(PathToImages, "color-wheel.png"));

Expand All @@ -28,6 +25,16 @@ public unsafe void StreamLosesOwnershipTddoCodecButIsNotForgotten()
}
}

[SkippableTheory]
[InlineData("P8211052.JPG", SKEncodedOrigin.LeftBottom)]
[InlineData("PA010741.JPG", SKEncodedOrigin.LeftBottom)]
public void CodecCanLoadCorrectOrigin(string image, SKEncodedOrigin origin)
{
var codec = SKCodec.Create(Path.Combine(PathToImages, image));

Assert.Equal(origin, codec.EncodedOrigin);
}

[SkippableFact]
public unsafe void ReleaseDataWasInvokedOnlyAfterTheCodecWasFinished()
{
Expand Down Expand Up @@ -188,49 +195,55 @@ public void CanCreateStreamCodecWithResult()
}

[SkippableFact]
public void GetGifFrames ()
public void GetGifFrames()
{
const int FrameCount = 16;

var stream = new SKFileStream (Path.Combine (PathToImages, "animated-heart.gif"));
using (var codec = SKCodec.Create (stream)) {
Assert.Equal (-1, codec.RepetitionCount);
var stream = new SKFileStream(Path.Combine(PathToImages, "animated-heart.gif"));
using (var codec = SKCodec.Create(stream))
{
Assert.Equal(-1, codec.RepetitionCount);

var frameInfos = codec.FrameInfo;
Assert.Equal (FrameCount, frameInfos.Length);
Assert.Equal(FrameCount, frameInfos.Length);

Assert.Equal (-1, frameInfos [0].RequiredFrame);
Assert.Equal(-1, frameInfos[0].RequiredFrame);

var cachedFrames = new SKBitmap [FrameCount];
var info = new SKImageInfo (codec.Info.Width, codec.Info.Height);
var cachedFrames = new SKBitmap[FrameCount];
var info = new SKImageInfo(codec.Info.Width, codec.Info.Height);

var decode = new Action<SKBitmap, int, int> ((bm, cachedIndex, index) => {
var decode = new Action<SKBitmap, int, int>((bm, cachedIndex, index) =>
{
var decodeInfo = info;
if (index > 0) {
decodeInfo = info.WithAlphaType (frameInfos [index].AlphaType);
if (index > 0)
{
decodeInfo = info.WithAlphaType(frameInfos[index].AlphaType);
}
Assert.True (bm.TryAllocPixels (decodeInfo));
if (cachedIndex != -1) {
Assert.True (cachedFrames [cachedIndex].CopyTo (bm));
Assert.True(bm.TryAllocPixels(decodeInfo));
if (cachedIndex != -1)
{
Assert.True(cachedFrames[cachedIndex].CopyTo(bm));
}
var opts = new SKCodecOptions (index, cachedIndex);
var result = codec.GetPixels (decodeInfo, bm.GetPixels (), opts);
if (cachedIndex != -1 && frameInfos [cachedIndex].DisposalMethod == SKCodecAnimationDisposalMethod.RestorePrevious) {
Assert.Equal (SKCodecResult.InvalidParameters, result);
var opts = new SKCodecOptions(index, cachedIndex);
var result = codec.GetPixels(decodeInfo, bm.GetPixels(), opts);
if (cachedIndex != -1 && frameInfos[cachedIndex].DisposalMethod == SKCodecAnimationDisposalMethod.RestorePrevious)
{
Assert.Equal(SKCodecResult.InvalidParameters, result);
}
Assert.Equal (SKCodecResult.Success, result);
Assert.Equal(SKCodecResult.Success, result);
});

for (var i = 0; i < FrameCount; i++) {
var cachedFrame = cachedFrames [i] = new SKBitmap ();
decode (cachedFrame, -1, i);
for (var i = 0; i < FrameCount; i++)
{
var cachedFrame = cachedFrames[i] = new SKBitmap();
decode(cachedFrame, -1, i);

var uncachedFrame = new SKBitmap ();
decode (uncachedFrame, frameInfos [i].RequiredFrame, i);
var uncachedFrame = new SKBitmap();
decode(uncachedFrame, frameInfos[i].RequiredFrame, i);

var cachedBytes = cachedFrame.Bytes;
var uncachedBytes = uncachedFrame.Bytes;
Assert.Equal (cachedBytes, uncachedBytes);
Assert.Equal(cachedBytes, uncachedBytes);
}
}
}
Expand All @@ -251,158 +264,173 @@ public void GetSingleGifFrame()
}

[SkippableFact]
public void GetEncodedInfo ()
public void GetEncodedInfo()
{
var stream = new SKFileStream (Path.Combine (PathToImages, "color-wheel.png"));
using (var codec = SKCodec.Create (stream)) {
Assert.Equal (SKImageInfo.PlatformColorType, codec.Info.ColorType);
Assert.Equal (SKAlphaType.Unpremul, codec.Info.AlphaType);
Assert.Equal (32, codec.Info.BitsPerPixel);
var stream = new SKFileStream(Path.Combine(PathToImages, "color-wheel.png"));
using (var codec = SKCodec.Create(stream))
{
Assert.Equal(SKImageInfo.PlatformColorType, codec.Info.ColorType);
Assert.Equal(SKAlphaType.Unpremul, codec.Info.AlphaType);
Assert.Equal(32, codec.Info.BitsPerPixel);
}
}

[SkippableFact]
public void CanGetPixels ()
public void CanGetPixels()
{
var stream = new SKFileStream (Path.Combine (PathToImages, "baboon.png"));
using (var codec = SKCodec.Create (stream)) {
var stream = new SKFileStream(Path.Combine(PathToImages, "baboon.png"));
using (var codec = SKCodec.Create(stream))
{
var pixels = codec.Pixels;
Assert.Equal (codec.Info.BytesSize, pixels.Length);
Assert.Equal(codec.Info.BytesSize, pixels.Length);
}
}

[SkippableFact]
public void DecodeImageScanlines ()
public void DecodeImageScanlines()
{
var path = Path.Combine (PathToImages, "CMYK.jpg");
var path = Path.Combine(PathToImages, "CMYK.jpg");
var imageHeight = 516;

var fileData = File.ReadAllBytes (path);
var correctBitmap = SKBitmap.Decode (path);
var fileData = File.ReadAllBytes(path);
var correctBitmap = SKBitmap.Decode(path);

var stream = new SKFileStream (path);
using (var codec = SKCodec.Create (stream)) {
var info = new SKImageInfo (codec.Info.Width, codec.Info.Height);
using (var scanlineBitmap = new SKBitmap (info)) {
scanlineBitmap.Erase (SKColors.Fuchsia);
var stream = new SKFileStream(path);
using (var codec = SKCodec.Create(stream))
{
var info = new SKImageInfo(codec.Info.Width, codec.Info.Height);
using (var scanlineBitmap = new SKBitmap(info))
{
scanlineBitmap.Erase(SKColors.Fuchsia);

var result = codec.StartScanlineDecode (info);
Assert.Equal (SKCodecResult.Success, result);
var result = codec.StartScanlineDecode(info);
Assert.Equal(SKCodecResult.Success, result);

Assert.Equal (SKCodecScanlineOrder.TopDown, codec.ScanlineOrder);
Assert.Equal (0, codec.NextScanline);
Assert.Equal(SKCodecScanlineOrder.TopDown, codec.ScanlineOrder);
Assert.Equal(0, codec.NextScanline);

// only decode every second line
for (int y = 0; y < info.Height; y += 2) {
Assert.Equal (1, codec.GetScanlines (scanlineBitmap.GetAddress (0, y), 1, info.RowBytes));
Assert.Equal (y + 1, codec.NextScanline);
if (codec.SkipScanlines (1))
Assert.Equal (y + 2, codec.NextScanline);
for (int y = 0; y < info.Height; y += 2)
{
Assert.Equal(1, codec.GetScanlines(scanlineBitmap.GetAddress(0, y), 1, info.RowBytes));
Assert.Equal(y + 1, codec.NextScanline);
if (codec.SkipScanlines(1))
Assert.Equal(y + 2, codec.NextScanline);
else
Assert.Equal (imageHeight, codec.NextScanline); // reached the end
Assert.Equal(imageHeight, codec.NextScanline); // reached the end
}

Assert.False (codec.SkipScanlines (1));
Assert.Equal (imageHeight, codec.NextScanline);
Assert.False(codec.SkipScanlines(1));
Assert.Equal(imageHeight, codec.NextScanline);

for (var x = 0; x < info.Width; x++) {
for (var y = 0; y < info.Height; y++) {
for (var x = 0; x < info.Width; x++)
{
for (var y = 0; y < info.Height; y++)
{
if (y % 2 == 0)
Assert.Equal (correctBitmap.GetPixel (x, y), scanlineBitmap.GetPixel (x, y));
Assert.Equal(correctBitmap.GetPixel(x, y), scanlineBitmap.GetPixel(x, y));
else
Assert.Equal (SKColors.Fuchsia, scanlineBitmap.GetPixel (x, y));
Assert.Equal(SKColors.Fuchsia, scanlineBitmap.GetPixel(x, y));
}
}
}
}
}

[SkippableFact]
public void DecodePartialImage ()
public void DecodePartialImage()
{
// read the data here, so we can fake a throttle/download
var path = Path.Combine (PathToImages, "baboon.png");
var fileData = File.ReadAllBytes (path);
var path = Path.Combine(PathToImages, "baboon.png");
var fileData = File.ReadAllBytes(path);
SKColor[] correctBytes;
using (var bitmap = SKBitmap.Decode (path)) {
using (var bitmap = SKBitmap.Decode(path))
{
correctBytes = bitmap.Pixels;
}

int offset = 0;
int maxCount = 1024 * 4;

// the "download" stream needs some data
var downloadStream = new MemoryStream ();
downloadStream.Write (fileData, offset, maxCount);
var downloadStream = new MemoryStream();
downloadStream.Write(fileData, offset, maxCount);
downloadStream.Position -= maxCount;
offset += maxCount;

using (var codec = SKCodec.Create (new SKManagedStream (downloadStream))) {
var info = new SKImageInfo (codec.Info.Width, codec.Info.Height);
using (var codec = SKCodec.Create(new SKManagedStream(downloadStream)))
{
var info = new SKImageInfo(codec.Info.Width, codec.Info.Height);

// the bitmap to be decoded
using (var incremental = new SKBitmap (info)) {
using (var incremental = new SKBitmap(info))
{

// start decoding
IntPtr length;
var pixels = incremental.GetPixels (out length);
var result = codec.StartIncrementalDecode (info, pixels, info.RowBytes);
var pixels = incremental.GetPixels(out length);
var result = codec.StartIncrementalDecode(info, pixels, info.RowBytes);

// make sure the start was successful
Assert.Equal (SKCodecResult.Success, result);
Assert.Equal(SKCodecResult.Success, result);
result = SKCodecResult.IncompleteInput;

while (result == SKCodecResult.IncompleteInput) {
while (result == SKCodecResult.IncompleteInput)
{
// decode the rest
int rowsDecoded = 0;
result = codec.IncrementalDecode (out rowsDecoded);
result = codec.IncrementalDecode(out rowsDecoded);

// write some more data to the stream
maxCount = Math.Min (maxCount, fileData.Length - offset);
downloadStream.Write (fileData, offset, maxCount);
maxCount = Math.Min(maxCount, fileData.Length - offset);
downloadStream.Write(fileData, offset, maxCount);
downloadStream.Position -= maxCount;
offset += maxCount;
}

// compare to original
Assert.Equal (correctBytes, incremental.Pixels);
Assert.Equal(correctBytes, incremental.Pixels);
}
}
}

[SkippableFact]
public void BitmapDecodesCorrectly ()
public void BitmapDecodesCorrectly()
{
byte[] codecPixels;
byte[] bitmapPixels;

using (var codec = SKCodec.Create (new SKFileStream (Path.Combine (PathToImages, "baboon.png")))) {
using (var codec = SKCodec.Create(new SKFileStream(Path.Combine(PathToImages, "baboon.png"))))
{
codecPixels = codec.Pixels;
}

using (var bitmap = SKBitmap.Decode (Path.Combine (PathToImages, "baboon.png"))) {
using (var bitmap = SKBitmap.Decode(Path.Combine(PathToImages, "baboon.png")))
{
bitmapPixels = bitmap.Bytes;
}

Assert.Equal (codecPixels, bitmapPixels);
Assert.Equal(codecPixels, bitmapPixels);
}

[SkippableFact]
public void BitmapDecodesCorrectlyWithManagedStream ()
public void BitmapDecodesCorrectlyWithManagedStream()
{
byte[] codecPixels;
byte[] bitmapPixels;

var stream = File.OpenRead (Path.Combine(PathToImages, "baboon.png"));
using (var codec = SKCodec.Create (new SKManagedStream (stream))) {
var stream = File.OpenRead(Path.Combine(PathToImages, "baboon.png"));
using (var codec = SKCodec.Create(new SKManagedStream(stream)))
{
codecPixels = codec.Pixels;
}

using (var bitmap = SKBitmap.Decode (Path.Combine (PathToImages, "baboon.png"))) {
using (var bitmap = SKBitmap.Decode(Path.Combine(PathToImages, "baboon.png")))
{
bitmapPixels = bitmap.Bytes;
}

Assert.Equal (codecPixels, bitmapPixels);
Assert.Equal(codecPixels, bitmapPixels);
}

[SkippableFact]
Expand All @@ -413,22 +441,22 @@ public void CanReadManagedStream()
Assert.NotNull(codec);
}

[SkippableFact (Skip = "This keeps breaking CI for some reason.")]
public async Task DownloadedStream ()
[SkippableFact(Skip = "This keeps breaking CI for some reason.")]
public async Task DownloadedStream()
{
var httpClient = new HttpClient ();
using (var stream = await httpClient.GetStreamAsync (new Uri ("http://www.gstatic.com/webp/gallery/2.webp")))
using (var bitmap = SKBitmap.Decode (stream))
Assert.NotNull (bitmap);
var httpClient = new HttpClient();
using (var stream = await httpClient.GetStreamAsync(new Uri("http://www.gstatic.com/webp/gallery/2.webp")))
using (var bitmap = SKBitmap.Decode(stream))
Assert.NotNull(bitmap);
}

[SkippableFact]
public void ReadOnlyStream ()
public void ReadOnlyStream()
{
using (var stream = File.OpenRead (Path.Combine (PathToImages, "baboon.png")))
using (var nonSeekable = new NonSeekableReadOnlyStream (stream))
using (var bitmap = SKBitmap.Decode (nonSeekable))
Assert.NotNull (bitmap);
using (var stream = File.OpenRead(Path.Combine(PathToImages, "baboon.png")))
using (var nonSeekable = new NonSeekableReadOnlyStream(stream))
using (var bitmap = SKBitmap.Decode(nonSeekable))
Assert.NotNull(bitmap);
}

[SkippableTheory]
Expand Down

0 comments on commit ceff370

Please sign in to comment.