Browse Source

Fixing most of things of review

Also I fixed a little bug with PreviouslySeenPixels array

It's weird, but I don't see any problems with encoding and the tests are ok, I wrote the memory stream to files and they look the same, but the hashes and bytes aren't the same

This is very weird
qoi
LuisAlfredo92 3 years ago
parent
commit
6245666d61
No known key found for this signature in database GPG Key ID: 13A8436905993B8F
  1. 2
      src/ImageSharp/Formats/Qoi/QoiChunk.cs
  2. 16
      src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs
  3. 18
      src/ImageSharp/Formats/Qoi/QoiEncoder.cs
  4. 33
      src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs
  5. 12
      src/ImageSharp/Formats/Qoi/QoiMetadata.cs
  6. 5
      tests/ImageSharp.Tests/Formats/Qoi/QoiEncoderTests.cs

2
src/ImageSharp/Formats/Qoi/QoiChunkEnum.cs → src/ImageSharp/Formats/Qoi/QoiChunk.cs

@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Qoi;
/// Enum that contains the operations that encoder and decoder must process, written /// Enum that contains the operations that encoder and decoder must process, written
/// in binary to be easier to compare them in the reference /// in binary to be easier to compare them in the reference
/// </summary> /// </summary>
public enum QoiChunkEnum internal enum QoiChunk
{ {
/// <summary> /// <summary>
/// Indicates that the operation is QOI_OP_RGB where the RGB values are written /// Indicates that the operation is QOI_OP_RGB where the RGB values are written

16
src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs

@ -160,10 +160,10 @@ internal class QoiDecoderCore : IImageDecoderInternals
byte[] pixelBytes; byte[] pixelBytes;
Rgba32 readPixel; Rgba32 readPixel;
TPixel pixel = default; TPixel pixel = default;
switch ((QoiChunkEnum)operationByte) switch ((QoiChunk)operationByte)
{ {
// Reading one pixel with previous alpha intact // Reading one pixel with previous alpha intact
case QoiChunkEnum.QoiOpRgb: case QoiChunk.QoiOpRgb:
pixelBytes = new byte[3]; pixelBytes = new byte[3];
if (stream.Read(pixelBytes) < 3) if (stream.Read(pixelBytes) < 3)
{ {
@ -177,7 +177,7 @@ internal class QoiDecoderCore : IImageDecoderInternals
break; break;
// Reading one pixel with new alpha // Reading one pixel with new alpha
case QoiChunkEnum.QoiOpRgba: case QoiChunk.QoiOpRgba:
pixelBytes = new byte[4]; pixelBytes = new byte[4];
if (stream.Read(pixelBytes) < 4) if (stream.Read(pixelBytes) < 4)
{ {
@ -191,16 +191,16 @@ internal class QoiDecoderCore : IImageDecoderInternals
break; break;
default: default:
switch ((QoiChunkEnum)(operationByte & 0b11000000)) switch ((QoiChunk)(operationByte & 0b11000000))
{ {
// Getting one pixel from previously seen pixels // Getting one pixel from previously seen pixels
case QoiChunkEnum.QoiOpIndex: case QoiChunk.QoiOpIndex:
readPixel = previouslySeenPixels[operationByte]; readPixel = previouslySeenPixels[operationByte];
pixel.FromRgba32(readPixel); pixel.FromRgba32(readPixel);
break; break;
// Get one pixel from the difference (-2..1) of the previous pixel // Get one pixel from the difference (-2..1) of the previous pixel
case QoiChunkEnum.QoiOpDiff: case QoiChunk.QoiOpDiff:
byte redDifference = (byte)((operationByte & 0b00110000) >> 4), byte redDifference = (byte)((operationByte & 0b00110000) >> 4),
greenDifference = (byte)((operationByte & 0b00001100) >> 2), greenDifference = (byte)((operationByte & 0b00001100) >> 2),
blueDifference = (byte)(operationByte & 0b00000011); blueDifference = (byte)(operationByte & 0b00000011);
@ -217,7 +217,7 @@ internal class QoiDecoderCore : IImageDecoderInternals
// Get green difference in 6 bits and red and blue differences // Get green difference in 6 bits and red and blue differences
// depending on the green one // depending on the green one
case QoiChunkEnum.QoiOpLuma: case QoiChunk.QoiOpLuma:
byte diffGreen = (byte)(operationByte & 0b00111111), byte diffGreen = (byte)(operationByte & 0b00111111),
currentGreen = (byte)((previousPixel.G + (diffGreen - 32)) % 256), currentGreen = (byte)((previousPixel.G + (diffGreen - 32)) % 256),
nextByte = (byte)stream.ReadByte(), nextByte = (byte)stream.ReadByte(),
@ -232,7 +232,7 @@ internal class QoiDecoderCore : IImageDecoderInternals
break; break;
// Repeating the previous pixel 1..63 times // Repeating the previous pixel 1..63 times
case QoiChunkEnum.QoiOpRun: case QoiChunk.QoiOpRun:
byte repetitions = (byte)(operationByte & 0b00111111); byte repetitions = (byte)(operationByte & 0b00111111);
if (repetitions is 62 or 63) if (repetitions is 62 or 63)
{ {

18
src/ImageSharp/Formats/Qoi/QoiEncoder.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Text;
namespace SixLabors.ImageSharp.Formats.Qoi; namespace SixLabors.ImageSharp.Formats.Qoi;
/// <summary> /// <summary>
@ -8,10 +10,24 @@ namespace SixLabors.ImageSharp.Formats.Qoi;
/// </summary> /// </summary>
public class QoiEncoder : ImageEncoder public class QoiEncoder : ImageEncoder
{ {
/// <summary>
/// Gets the color channels on the image that can be
/// RGB or RGBA. This is purely informative. It doesn't
/// change the way data chunks are encoded.
/// </summary>
public QoiChannels? Channels { get; init; }
/// <summary>
/// Gets the color space of the image that can be sRGB with
/// linear alpha or all channels linear. This is purely
/// informative. It doesn't change the way data chunks are encoded.
/// </summary>
public QoiColorSpace? ColorSpace { get; init; }
/// <inheritdoc /> /// <inheritdoc />
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{ {
QoiEncoderCore encoder = new(); QoiEncoderCore encoder = new(this);
encoder.Encode(image, stream, cancellationToken); encoder.Encode(image, stream, cancellationToken);
} }
} }

33
src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs

@ -12,12 +12,11 @@ namespace SixLabors.ImageSharp.Formats.Qoi;
/// </summary> /// </summary>
public class QoiEncoderCore : IImageEncoderInternals public class QoiEncoderCore : IImageEncoderInternals
{ {
private readonly QoiEncoder encoder;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="QoiEncoderCore"/> class. /// Initializes a new instance of the <see cref="QoiEncoderCore"/> class.
/// </summary> /// </summary>
public QoiEncoderCore() public QoiEncoderCore(QoiEncoder encoder) => this.encoder = encoder;
{
}
/// <inheritdoc /> /// <inheritdoc />
public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
@ -26,23 +25,21 @@ public class QoiEncoderCore : IImageEncoderInternals
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
WriteHeader(image, stream); this.WriteHeader(image, stream);
WritePixels(image, stream); WritePixels(image, stream);
WriteEndOfStream(stream); WriteEndOfStream(stream);
stream.Flush(); stream.Flush();
} }
private static void WriteHeader(Image image, Stream stream) private void WriteHeader(Image image, Stream stream)
{ {
// Get metadata // Get metadata
Span<byte> width = stackalloc byte[4]; Span<byte> width = stackalloc byte[4];
Span<byte> height = stackalloc byte[4]; Span<byte> height = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(width, (uint)image.Width); BinaryPrimitives.WriteUInt32BigEndian(width, (uint)image.Width);
BinaryPrimitives.WriteUInt32BigEndian(height, (uint)image.Height); BinaryPrimitives.WriteUInt32BigEndian(height, (uint)image.Height);
QoiChannels qoiChannels = image.PixelType.BitsPerPixel == 24 ? QoiChannels.Rgb : QoiChannels.Rgba; QoiChannels qoiChannels = this.encoder.Channels ?? QoiChannels.Rgba;
QoiColorSpace qoiColorSpace = this.encoder.ColorSpace ?? QoiColorSpace.SrgbWithLinearAlpha;
// I need to check this, how do I check it with the pixel type or metadata of the original image?
const QoiColorSpace qoiColorSpace = QoiColorSpace.SrgbWithLinearAlpha;
// Write header to the stream // Write header to the stream
stream.Write(QoiConstants.Magic); stream.Write(QoiConstants.Magic);
@ -58,11 +55,9 @@ public class QoiEncoderCore : IImageEncoderInternals
// Start image encoding // Start image encoding
Rgba32[] previouslySeenPixels = new Rgba32[64]; Rgba32[] previouslySeenPixels = new Rgba32[64];
Rgba32 previousPixel = new(0, 0, 0, 255); Rgba32 previousPixel = new(0, 0, 0, 255);
int pixelArrayPosition = GetArrayPosition(previousPixel);
previouslySeenPixels[pixelArrayPosition] = previousPixel;
Buffer2D<TPixel> pixels = image.Frames[0].PixelBuffer;
Rgba32 currentRgba32 = default; Rgba32 currentRgba32 = default;
Buffer2D<TPixel> pixels = image.Frames[0].PixelBuffer;
for (int i = 0; i < pixels.Height; i++) for (int i = 0; i < pixels.Height; i++)
{ {
for (int j = 0; j < pixels.Width && i < pixels.Height; j++) for (int j = 0; j < pixels.Width && i < pixels.Height; j++)
@ -105,7 +100,7 @@ public class QoiEncoderCore : IImageEncoderInternals
while (currentRgba32.Equals(previousPixel) && repetitions < 62); while (currentRgba32.Equals(previousPixel) && repetitions < 62);
j--; j--;
stream.WriteByte((byte)((byte)QoiChunkEnum.QoiOpRun | (repetitions - 1))); stream.WriteByte((byte)((byte)QoiChunk.QoiOpRun | (repetitions - 1)));
/* If it's a QOI_OP_RUN, we don't overwrite the previous pixel since /* If it's a QOI_OP_RUN, we don't overwrite the previous pixel since
* it will be taken and compared on the next iteration * it will be taken and compared on the next iteration
@ -115,7 +110,7 @@ public class QoiEncoderCore : IImageEncoderInternals
// else, we check if it exists in the previously seen pixels // else, we check if it exists in the previously seen pixels
// If so, we do a QOI_OP_INDEX // If so, we do a QOI_OP_INDEX
pixelArrayPosition = GetArrayPosition(currentRgba32); int pixelArrayPosition = GetArrayPosition(currentRgba32);
if (previouslySeenPixels[pixelArrayPosition].Equals(currentPixel)) if (previouslySeenPixels[pixelArrayPosition].Equals(currentPixel))
{ {
stream.WriteByte((byte)pixelArrayPosition); stream.WriteByte((byte)pixelArrayPosition);
@ -140,7 +135,7 @@ public class QoiEncoderCore : IImageEncoderInternals
byte dr = (byte)(diffRed + 2), byte dr = (byte)(diffRed + 2),
dg = (byte)(diffGreen + 2), dg = (byte)(diffGreen + 2),
db = (byte)(diffBlue + 2), db = (byte)(diffBlue + 2),
valueToWrite = (byte)((byte)QoiChunkEnum.QoiOpDiff | (dr << 4) | (dg << 2) | db); valueToWrite = (byte)((byte)QoiChunk.QoiOpDiff | (dr << 4) | (dg << 2) | db);
stream.WriteByte(valueToWrite); stream.WriteByte(valueToWrite);
} }
else else
@ -156,7 +151,7 @@ public class QoiEncoderCore : IImageEncoderInternals
{ {
byte dr_dg = (byte)(diffRedGreen + 8), byte dr_dg = (byte)(diffRedGreen + 8),
db_dg = (byte)(diffBlueGreen + 8), db_dg = (byte)(diffBlueGreen + 8),
byteToWrite1 = (byte)((byte)QoiChunkEnum.QoiOpLuma | (diffGreen + 32)), byteToWrite1 = (byte)((byte)QoiChunk.QoiOpLuma | (diffGreen + 32)),
byteToWrite2 = (byte)((dr_dg << 4) | db_dg); byteToWrite2 = (byte)((dr_dg << 4) | db_dg);
stream.WriteByte(byteToWrite1); stream.WriteByte(byteToWrite1);
stream.WriteByte(byteToWrite2); stream.WriteByte(byteToWrite2);
@ -167,7 +162,7 @@ public class QoiEncoderCore : IImageEncoderInternals
// If so, we do a QOI_OP_RGB // If so, we do a QOI_OP_RGB
if (currentRgba32.A == previousPixel.A) if (currentRgba32.A == previousPixel.A)
{ {
stream.WriteByte((byte)QoiChunkEnum.QoiOpRgb); stream.WriteByte((byte)QoiChunk.QoiOpRgb);
stream.WriteByte(currentRgba32.R); stream.WriteByte(currentRgba32.R);
stream.WriteByte(currentRgba32.G); stream.WriteByte(currentRgba32.G);
stream.WriteByte(currentRgba32.B); stream.WriteByte(currentRgba32.B);
@ -175,7 +170,7 @@ public class QoiEncoderCore : IImageEncoderInternals
else else
{ {
// else, we do a QOI_OP_RGBA // else, we do a QOI_OP_RGBA
stream.WriteByte((byte)QoiChunkEnum.QoiOpRgba); stream.WriteByte((byte)QoiChunk.QoiOpRgba);
stream.WriteByte(currentRgba32.R); stream.WriteByte(currentRgba32.R);
stream.WriteByte(currentRgba32.G); stream.WriteByte(currentRgba32.G);
stream.WriteByte(currentRgba32.B); stream.WriteByte(currentRgba32.B);

12
src/ImageSharp/Formats/Qoi/QoiMetadata.cs

@ -21,22 +21,10 @@ public class QoiMetadata : IDeepCloneable
/// <param name="other">The metadata to create an instance from.</param> /// <param name="other">The metadata to create an instance from.</param>
public QoiMetadata(QoiMetadata other) public QoiMetadata(QoiMetadata other)
{ {
this.Width = other.Width;
this.Height = other.Height;
this.Channels = other.Channels; this.Channels = other.Channels;
this.ColorSpace = other.ColorSpace; this.ColorSpace = other.ColorSpace;
} }
/// <summary>
/// Gets or sets image width in pixels (BE)
/// </summary>
public uint Width { get; set; }
/// <summary>
/// Gets or sets image height in pixels (BE)
/// </summary>
public uint Height { get; set; }
/// <summary> /// <summary>
/// Gets or sets color channels of the image. 3 = RGB, 4 = RGBA. /// Gets or sets color channels of the image. 3 = RGB, 4 = RGBA.
/// </summary> /// </summary>

5
tests/ImageSharp.Tests/Formats/Qoi/QoiEncoderTests.cs

@ -4,6 +4,7 @@
using SixLabors.ImageSharp.Formats.Qoi; using SixLabors.ImageSharp.Formats.Qoi;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
namespace SixLabors.ImageSharp.Tests.Formats.Qoi; namespace SixLabors.ImageSharp.Tests.Formats.Qoi;
@ -24,12 +25,12 @@ public class QoiEncoderTests
private static void Encode<TPixel>(TestImageProvider<TPixel> provider) private static void Encode<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using Image<TPixel> image = provider.GetImage(); using Image<TPixel> image = provider.GetImage(new MagickReferenceDecoder());
using MemoryStream stream = new(); using MemoryStream stream = new();
QoiEncoder encoder = new(); QoiEncoder encoder = new();
image.Save(stream, encoder); image.Save(stream, encoder);
stream.Position = 0; stream.Position = 0;
using Image<TPixel> encodedImage = (Image<TPixel>)Image.Load(stream); using Image<TPixel> encodedImage = (Image<TPixel>)Image.Load(stream);
ImageComparingUtils.CompareWithReferenceDecoder(provider, encodedImage); ImageComparer.Exact.CompareImages(image, encodedImage);
} }
} }

Loading…
Cancel
Save