Skip to content
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

Add support for reading the Exif SubIFD #1518

Merged
merged 3 commits into from
Oct 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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