Browse Source

Add encoders that cannot be used.

pull/2579/head
舰队的偶像-岛风酱! 2 years ago
parent
commit
dbd178392f
No known key found for this signature in database GPG Key ID: 71F5B3A2B181950C
  1. 9
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  2. 81
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  3. 3
      src/ImageSharp/Formats/Cur/CurConfigurationModule.cs
  4. 17
      src/ImageSharp/Formats/Cur/CurEncoder.cs
  5. 19
      src/ImageSharp/Formats/Cur/CurEncoderCore.cs
  6. 3
      src/ImageSharp/Formats/Ico/IcoConfigurationModule.cs
  7. 17
      src/ImageSharp/Formats/Ico/IcoEncoder.cs
  8. 19
      src/ImageSharp/Formats/Ico/IcoEncoderCore.cs
  9. 14
      src/ImageSharp/Formats/Icon/IconAssert.cs
  10. 3
      src/ImageSharp/Formats/Icon/IconDir.cs
  11. 3
      src/ImageSharp/Formats/Icon/IconDirEntry.cs
  12. 69
      src/ImageSharp/Formats/Icon/IconEncoderCore.cs
  13. 1
      src/ImageSharp/Metadata/ImageMetadata.cs
  14. 32
      tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs
  15. 32
      tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs

9
src/ImageSharp/Formats/Bmp/BmpEncoder.cs

@ -29,6 +29,15 @@ public sealed class BmpEncoder : QuantizingImageEncoder
/// </summary>
public bool SupportTransparency { get; init; }
/// <inheritdoc cref="BmpDecoderOptions.ProcessedAlphaMask"/>
internal bool ProcessedAlphaMask { get; init; }
/// <inheritdoc cref="BmpDecoderOptions.SkipFileHeader"/>
internal bool SkipFileHeader { get; init; }
/// <inheritdoc cref="BmpDecoderOptions.UseDoubleHeight"/>
internal bool UseDoubleHeight { get; init; }
/// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{

81
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -3,6 +3,7 @@
using System.Buffers;
using System.Buffers.Binary;
using System.IO;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
@ -11,6 +12,7 @@ using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
using static System.Net.Mime.MediaTypeNames;
namespace SixLabors.ImageSharp.Formats.Bmp;
@ -92,6 +94,15 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// </summary>
private readonly IPixelSamplingStrategy pixelSamplingStrategy;
/// <inheritdoc cref="BmpDecoderOptions.ProcessedAlphaMask"/>
private readonly bool processedAlphaMask;
/// <inheritdoc cref="BmpDecoderOptions.SkipFileHeader"/>
private readonly bool skipFileHeader;
/// <inheritdoc cref="BmpDecoderOptions.UseDoubleHeight"/>
private readonly bool isDoubleHeight;
/// <summary>
/// Initializes a new instance of the <see cref="BmpEncoderCore"/> class.
/// </summary>
@ -104,6 +115,9 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
this.quantizer = encoder.Quantizer ?? KnownQuantizers.Octree;
this.pixelSamplingStrategy = encoder.PixelSamplingStrategy;
this.infoHeaderType = encoder.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3;
this.processedAlphaMask = encoder.ProcessedAlphaMask;
this.skipFileHeader = encoder.SkipFileHeader;
this.isDoubleHeight = encoder.UseDoubleHeight;
}
/// <summary>
@ -154,11 +168,23 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
_ => BmpInfoHeader.SizeV3
};
BmpInfoHeader infoHeader = this.CreateBmpInfoHeader(image.Width, image.Height, infoHeaderSize, bpp, bytesPerLine, metadata, iccProfileData);
// for ico/cur encoder.
int height = image.Height;
if (this.isDoubleHeight)
{
height <<= 1;
}
BmpInfoHeader infoHeader = this.CreateBmpInfoHeader(image.Width, height, infoHeaderSize, bpp, bytesPerLine, metadata, iccProfileData);
Span<byte> buffer = stackalloc byte[infoHeaderSize];
WriteBitmapFileHeader(stream, infoHeaderSize, colorPaletteSize, iccProfileSize, infoHeader, buffer);
// for ico/cur encoder.
if (!this.skipFileHeader)
{
WriteBitmapFileHeader(stream, infoHeaderSize, colorPaletteSize, iccProfileSize, infoHeader, buffer);
}
this.WriteBitmapInfoHeader(stream, infoHeader, buffer, infoHeaderSize);
this.WriteImage(configuration, stream, image);
WriteColorProfile(stream, iccProfileData, buffer);
@ -455,6 +481,11 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
{
this.Write8BitColor(configuration, stream, image, colorPalette);
}
if (this.processedAlphaMask)
{
ProcessedAlphaMask(stream, image);
}
}
/// <summary>
@ -572,6 +603,11 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
stream.WriteByte(0);
}
}
if (this.processedAlphaMask)
{
ProcessedAlphaMask(stream, image);
}
}
/// <summary>
@ -629,6 +665,11 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
stream.WriteByte(0);
}
}
if (this.processedAlphaMask)
{
ProcessedAlphaMask(stream, image);
}
}
/// <summary>
@ -679,6 +720,11 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
stream.WriteByte(0);
}
}
if (this.processedAlphaMask)
{
ProcessedAlphaMask(stream, image);
}
}
/// <summary>
@ -722,4 +768,35 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
stream.WriteByte(indices);
}
private static void ProcessedAlphaMask<TPixel>(Stream stream, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
Rgba32 rgba = default;
int arrayWidth = image.Width / 8;
int padding = arrayWidth % 4;
if (padding is not 0)
{
padding = 4 - padding;
}
Span<byte> mask = stackalloc byte[arrayWidth];
for (int y = image.Height - 1; y >= 0; y--)
{
mask.Clear();
for (int x = 0; x < image.Width; x++)
{
int bit = x % 8;
int i = x / 8;
TPixel pixel = image[x, y];
pixel.ToRgba32(ref rgba);
if (rgba.A is not 0)
{
mask[i] &= unchecked((byte)(0b10000000 >> bit));
}
}
stream.Write(mask);
}
}
}

3
src/ImageSharp/Formats/Cur/CurConfigurationModule.cs

@ -13,8 +13,7 @@ public sealed class CurConfigurationModule : IImageFormatConfigurationModule
/// <inheritdoc/>
public void Configure(Configuration configuration)
{
// TODO: CurEncoder
// configuration.ImageFormatsManager.SetEncoder(CurFormat.Instance, new CurEncoder());
configuration.ImageFormatsManager.SetEncoder(CurFormat.Instance, new CurEncoder());
configuration.ImageFormatsManager.SetDecoder(CurFormat.Instance, CurDecoder.Instance);
configuration.ImageFormatsManager.AddImageFormatDetector(new IconImageFormatDetector());
}

17
src/ImageSharp/Formats/Cur/CurEncoder.cs

@ -0,0 +1,17 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Cur;
/// <summary>
/// Image encoder for writing an image to a stream as a Windows Cursor.
/// </summary>
public sealed class CurEncoder : QuantizingImageEncoder
{
/// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
CurEncoderCore encoderCore = new();
encoderCore.Encode(image, stream, cancellationToken);
}
}

19
src/ImageSharp/Formats/Cur/CurEncoderCore.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Icon;
namespace SixLabors.ImageSharp.Formats.Cur;
internal sealed class CurEncoderCore : IconEncoderCore
{
protected override void GetHeader(in Image image)
{
this.FileHeader = new(IconFileType.ICO, (ushort)image.Frames.Count);
this.Entries = image.Frames.Select(i =>
{
CurFrameMetadata metadata = i.Metadata.GetCurMetadata();
return metadata.ToIconDirEntry();
}).ToArray();
}
}

3
src/ImageSharp/Formats/Ico/IcoConfigurationModule.cs

@ -13,8 +13,7 @@ public sealed class IcoConfigurationModule : IImageFormatConfigurationModule
/// <inheritdoc/>
public void Configure(Configuration configuration)
{
// TODO: IcoEncoder
// configuration.ImageFormatsManager.SetEncoder(IcoFormat.Instance, new IcoEncoder());
configuration.ImageFormatsManager.SetEncoder(IcoFormat.Instance, new IcoEncoder());
configuration.ImageFormatsManager.SetDecoder(IcoFormat.Instance, IcoDecoder.Instance);
configuration.ImageFormatsManager.AddImageFormatDetector(new IconImageFormatDetector());
}

17
src/ImageSharp/Formats/Ico/IcoEncoder.cs

@ -0,0 +1,17 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Ico;
/// <summary>
/// Image encoder for writing an image to a stream as a Windows Icon.
/// </summary>
public sealed class IcoEncoder : QuantizingImageEncoder
{
/// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
IcoEncoderCore encoderCore = new();
encoderCore.Encode(image, stream, cancellationToken);
}
}

19
src/ImageSharp/Formats/Ico/IcoEncoderCore.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Icon;
namespace SixLabors.ImageSharp.Formats.Ico;
internal sealed class IcoEncoderCore : IconEncoderCore
{
protected override void GetHeader(in Image image)
{
this.FileHeader = new(IconFileType.ICO, (ushort)image.Frames.Count);
this.Entries = image.Frames.Select(i =>
{
IcoFrameMetadata metadata = i.Metadata.GetIcoMetadata();
return metadata.ToIconDirEntry();
}).ToArray();
}
}

14
src/ImageSharp/Formats/Icon/IconAssert.cs

@ -32,4 +32,18 @@ internal class IconAssert
return v;
}
internal static byte Is1ByteSize(int i)
{
if (i is 256)
{
return 0;
}
else if (i > byte.MaxValue)
{
throw new FormatException("Image size Too Large.");
}
return (byte)i;
}
}

3
src/ImageSharp/Formats/Icon/IconDir.cs

@ -25,4 +25,7 @@ internal struct IconDir(ushort reserved, IconFileType type, ushort count)
public static IconDir Parse(in ReadOnlySpan<byte> data)
=> MemoryMarshal.Cast<byte, IconDir>(data)[0];
public unsafe void WriteTo(in Stream stream)
=> stream.Write(MemoryMarshal.Cast<IconDir, byte>([this]));
}

3
src/ImageSharp/Formats/Icon/IconDirEntry.cs

@ -28,4 +28,7 @@ internal struct IconDirEntry
public static IconDirEntry Parse(in ReadOnlySpan<byte> data)
=> MemoryMarshal.Cast<byte, IconDirEntry>(data)[0];
public unsafe void WriteTo(in Stream stream)
=> stream.Write(MemoryMarshal.Cast<IconDirEntry, byte>([this]));
}

69
src/ImageSharp/Formats/Icon/IconEncoderCore.cs

@ -0,0 +1,69 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Icon;
internal abstract class IconEncoderCore : IImageEncoderInternals
{
protected IconDir FileHeader { get; set; }
protected IconDirEntry[]? Entries { get; set; }
public void Encode<TPixel>(
Image<TPixel> image,
Stream stream,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
IconAssert.CanSeek(stream);
// Stream may not at 0.
long basePosition = stream.Position;
this.GetHeader(image);
int dataOffset = IconDir.Size + (IconDirEntry.Size * this.Entries.Length);
_ = stream.Seek(dataOffset, SeekOrigin.Current);
for (int i = 0; i < image.Frames.Count; i++)
{
ImageFrame<TPixel> frame = image.Frames[i];
this.Entries[i].ImageOffset = (uint)stream.Position;
Image<TPixel> img = new(Configuration.Default, frame.PixelBuffer, new());
// Note: this encoder are not supported PNG Data.
BmpEncoder encoder = new()
{
ProcessedAlphaMask = true,
UseDoubleHeight = true,
SkipFileHeader = true,
SupportTransparency = false,
BitsPerPixel = this.Entries[i].BitCount is 0
? BmpBitsPerPixel.Pixel8
: (BmpBitsPerPixel?)this.Entries[i].BitCount
};
encoder.Encode(img, stream);
this.Entries[i].BytesInRes = this.Entries[i].ImageOffset - (uint)stream.Position;
}
long endPosition = stream.Position;
_ = stream.Seek(basePosition, SeekOrigin.Begin);
this.FileHeader.WriteTo(stream);
foreach (IconDirEntry entry in this.Entries)
{
entry.WriteTo(stream);
}
_ = stream.Seek(endPosition, SeekOrigin.Begin);
}
[MemberNotNull(nameof(Entries))]
protected abstract void GetHeader(in Image image);
}

1
src/ImageSharp/Metadata/ImageMetadata.cs

@ -215,6 +215,7 @@ public sealed class ImageMetadata : IDeepCloneable<ImageMetadata>
metadata = default;
return false;
}
internal void SetFormatMetadata<TFormatMetadata>(IImageFormat<TFormatMetadata> key, TFormatMetadata value)
where TFormatMetadata : class, IDeepCloneable
=> this.formatMetadata[key] = value;

32
tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs

@ -0,0 +1,32 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Cur;
using SixLabors.ImageSharp.PixelFormats;
using static SixLabors.ImageSharp.Tests.TestImages.Cur;
namespace SixLabors.ImageSharp.Tests.Formats.Icon.Cur;
[Trait("Format", "Cur")]
public class CurEncoderTests
{
private static CurEncoder CurEncoder => new();
public static readonly TheoryData<string> Files = new()
{
{ WindowsMouse },
};
[Theory]
[MemberData(nameof(Files))]
public void Encode(string imagePath)
{
TestFile testFile = TestFile.Create(imagePath);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using MemoryStream memStream = new();
input.Save(memStream, CurEncoder);
memStream.Seek(0, SeekOrigin.Begin);
CurDecoder.Instance.Decode(new(), memStream);
}
}

32
tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs

@ -0,0 +1,32 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Ico;
using SixLabors.ImageSharp.PixelFormats;
using static SixLabors.ImageSharp.Tests.TestImages.Ico;
namespace SixLabors.ImageSharp.Tests.Formats.Icon.Ico;
[Trait("Format", "Icon")]
public class IcoEncoderTests
{
private static IcoEncoder CurEncoder => new();
public static readonly TheoryData<string> Files = new()
{
{ Flutter },
};
[Theory]
[MemberData(nameof(Files))]
public void Encode(string imagePath)
{
TestFile testFile = TestFile.Create(imagePath);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using MemoryStream memStream = new();
input.Save(memStream, CurEncoder);
memStream.Seek(0, SeekOrigin.Begin);
IcoDecoder.Instance.Decode(new(), memStream);
}
}
Loading…
Cancel
Save