mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Conflicts:
src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
tests/ImageSharp.Tests/TestImages.cs
pull/1760/head
401 changed files with 13433 additions and 4338 deletions
@ -1,8 +1,5 @@ |
|||
blank_issues_enabled: false |
|||
contact_links: |
|||
- name: Ask a Question |
|||
url: https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AQ%26A |
|||
about: Ask a question about this project. |
|||
- name: Feature Request |
|||
url: https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AIdeas |
|||
about: Share ideas for new features for this project. |
|||
|
|||
@ -1 +1 @@ |
|||
Subproject commit a042aba176cdb840d800c6ed4cfe41a54fb7b1e3 |
|||
Subproject commit 59ce17f5a4e1f956811133f41add7638e74c2836 |
|||
@ -1,82 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Tuples |
|||
{ |
|||
/// <summary>
|
|||
/// Its faster to process multiple Vector4-s together, so let's pair them!
|
|||
/// On AVX2 this pair should be convertible to <see cref="Vector{T}"/> of <see cref="float"/>!
|
|||
/// TODO: Investigate defining this as union with an Octet.OfSingle type.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal struct Vector4Pair |
|||
{ |
|||
public Vector4 A; |
|||
|
|||
public Vector4 B; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void MultiplyInplace(float value) |
|||
{ |
|||
this.A *= value; |
|||
this.B *= value; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void AddInplace(Vector4 value) |
|||
{ |
|||
this.A += value; |
|||
this.B += value; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void AddInplace(ref Vector4Pair other) |
|||
{ |
|||
this.A += other.A; |
|||
this.B += other.B; |
|||
} |
|||
|
|||
/// <summary>.
|
|||
/// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4! /// TODO: Move it somewhere else.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal void RoundAndDownscalePreVector8(float downscaleFactor) |
|||
{ |
|||
ref Vector<float> a = ref Unsafe.As<Vector4, Vector<float>>(ref this.A); |
|||
a = a.FastRound(); |
|||
|
|||
ref Vector<float> b = ref Unsafe.As<Vector4, Vector<float>>(ref this.B); |
|||
b = b.FastRound(); |
|||
|
|||
// Downscale by 1/factor
|
|||
var scale = new Vector4(1 / downscaleFactor); |
|||
this.A *= scale; |
|||
this.B *= scale; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// AVX2-only Downscale method, specific to Jpeg color conversion.
|
|||
/// TODO: Move it somewhere else.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal void RoundAndDownscaleVector8(float downscaleFactor) |
|||
{ |
|||
ref Vector<float> self = ref Unsafe.As<Vector4Pair, Vector<float>>(ref this); |
|||
Vector<float> v = self; |
|||
v = v.FastRound(); |
|||
|
|||
// Downscale by 1/factor
|
|||
v *= new Vector<float>(1 / downscaleFactor); |
|||
self = v; |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return $"{nameof(Vector4Pair)}({this.A}, {this.B})"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
#if SUPPORTS_RUNTIME_INTRINSICS
|
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components |
|||
{ |
|||
internal unsafe partial struct Block8x8 |
|||
{ |
|||
[FieldOffset(0)] |
|||
public Vector128<short> V0; |
|||
[FieldOffset(16)] |
|||
public Vector128<short> V1; |
|||
[FieldOffset(32)] |
|||
public Vector128<short> V2; |
|||
[FieldOffset(48)] |
|||
public Vector128<short> V3; |
|||
[FieldOffset(64)] |
|||
public Vector128<short> V4; |
|||
[FieldOffset(80)] |
|||
public Vector128<short> V5; |
|||
[FieldOffset(96)] |
|||
public Vector128<short> V6; |
|||
[FieldOffset(112)] |
|||
public Vector128<short> V7; |
|||
|
|||
[FieldOffset(0)] |
|||
public Vector256<short> V01; |
|||
[FieldOffset(32)] |
|||
public Vector256<short> V23; |
|||
[FieldOffset(64)] |
|||
public Vector256<short> V45; |
|||
[FieldOffset(96)] |
|||
public Vector256<short> V67; |
|||
} |
|||
} |
|||
#endif
|
|||
@ -1,18 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverter |
|||
{ |
|||
internal abstract class Avx2JpegColorConverter : VectorizedJpegColorConverter |
|||
{ |
|||
protected Avx2JpegColorConverter(JpegColorSpace colorSpace, int precision) |
|||
: base(colorSpace, precision, 8) |
|||
{ |
|||
} |
|||
|
|||
protected sealed override bool IsAvailable => SimdUtils.HasAvx2; |
|||
} |
|||
} |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverter |
|||
{ |
|||
internal abstract class BasicJpegColorConverter : JpegColorConverter |
|||
{ |
|||
protected BasicJpegColorConverter(JpegColorSpace colorSpace, int precision) |
|||
: base(colorSpace, precision) |
|||
{ |
|||
} |
|||
|
|||
protected override bool IsAvailable => true; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
#if SUPPORTS_RUNTIME_INTRINSICS
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using System.Runtime.Intrinsics.X86; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverterBase |
|||
{ |
|||
internal sealed class FromCmykAvx : JpegColorConverterAvx |
|||
{ |
|||
public FromCmykAvx(int precision) |
|||
: base(JpegColorSpace.Cmyk, precision) |
|||
{ |
|||
} |
|||
|
|||
public override void ConvertToRgbInplace(in ComponentValues values) |
|||
{ |
|||
ref Vector256<float> c0Base = |
|||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|||
ref Vector256<float> c1Base = |
|||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|||
ref Vector256<float> c2Base = |
|||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|||
ref Vector256<float> c3Base = |
|||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|||
|
|||
// Used for the color conversion
|
|||
var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); |
|||
|
|||
nint n = values.Component0.Length / Vector256<float>.Count; |
|||
for (nint i = 0; i < n; i++) |
|||
{ |
|||
ref Vector256<float> c = ref Unsafe.Add(ref c0Base, i); |
|||
ref Vector256<float> m = ref Unsafe.Add(ref c1Base, i); |
|||
ref Vector256<float> y = ref Unsafe.Add(ref c2Base, i); |
|||
Vector256<float> k = Unsafe.Add(ref c3Base, i); |
|||
|
|||
k = Avx.Multiply(k, scale); |
|||
c = Avx.Multiply(c, k); |
|||
m = Avx.Multiply(m, k); |
|||
y = Avx.Multiply(y, k); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
#endif
|
|||
@ -1,60 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
#if SUPPORTS_RUNTIME_INTRINSICS
|
|||
using System.Runtime.Intrinsics; |
|||
using System.Runtime.Intrinsics.X86; |
|||
using static SixLabors.ImageSharp.SimdUtils; |
|||
#endif
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverter |
|||
{ |
|||
internal sealed class FromCmykAvx2 : Avx2JpegColorConverter |
|||
{ |
|||
public FromCmykAvx2(int precision) |
|||
: base(JpegColorSpace.Cmyk, precision) |
|||
{ |
|||
} |
|||
|
|||
protected override void ConvertCoreVectorizedInplace(in ComponentValues values) |
|||
{ |
|||
#if SUPPORTS_RUNTIME_INTRINSICS
|
|||
ref Vector256<float> c0Base = |
|||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|||
ref Vector256<float> c1Base = |
|||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|||
ref Vector256<float> c2Base = |
|||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|||
ref Vector256<float> c3Base = |
|||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|||
|
|||
// Used for the color conversion
|
|||
var scale = Vector256.Create(1 / this.MaximumValue); |
|||
|
|||
nint n = values.Component0.Length / 8; |
|||
for (nint i = 0; i < n; i++) |
|||
{ |
|||
ref Vector256<float> c = ref Unsafe.Add(ref c0Base, i); |
|||
ref Vector256<float> m = ref Unsafe.Add(ref c1Base, i); |
|||
ref Vector256<float> y = ref Unsafe.Add(ref c2Base, i); |
|||
Vector256<float> k = Unsafe.Add(ref c3Base, i); |
|||
|
|||
k = Avx.Multiply(k, scale); |
|||
c = Avx.Multiply(Avx.Multiply(c, k), scale); |
|||
m = Avx.Multiply(Avx.Multiply(m, k), scale); |
|||
y = Avx.Multiply(Avx.Multiply(y, k), scale); |
|||
} |
|||
#endif
|
|||
} |
|||
|
|||
protected override void ConvertCoreInplace(in ComponentValues values) => |
|||
FromCmykBasic.ConvertCoreInplace(values, this.MaximumValue); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverterBase |
|||
{ |
|||
internal sealed class FromCmykVector : JpegColorConverterVector |
|||
{ |
|||
public FromCmykVector(int precision) |
|||
: base(JpegColorSpace.Cmyk, precision) |
|||
{ |
|||
} |
|||
|
|||
protected override void ConvertCoreVectorizedInplace(in ComponentValues values) |
|||
{ |
|||
ref Vector<float> cBase = |
|||
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|||
ref Vector<float> mBase = |
|||
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|||
ref Vector<float> yBase = |
|||
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|||
ref Vector<float> kBase = |
|||
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|||
|
|||
var scale = new Vector<float>(1 / (this.MaximumValue * this.MaximumValue)); |
|||
|
|||
nint n = values.Component0.Length / Vector<float>.Count; |
|||
for (nint i = 0; i < n; i++) |
|||
{ |
|||
ref Vector<float> c = ref Unsafe.Add(ref cBase, i); |
|||
ref Vector<float> m = ref Unsafe.Add(ref mBase, i); |
|||
ref Vector<float> y = ref Unsafe.Add(ref yBase, i); |
|||
Vector<float> k = Unsafe.Add(ref kBase, i); |
|||
|
|||
k *= scale; |
|||
c *= k; |
|||
m *= k; |
|||
y *= k; |
|||
} |
|||
} |
|||
|
|||
protected override void ConvertCoreInplace(in ComponentValues values) => |
|||
FromCmykScalar.ConvertCoreInplace(values, this.MaximumValue); |
|||
} |
|||
} |
|||
} |
|||
@ -1,53 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp.Tuples; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverter |
|||
{ |
|||
internal sealed class FromCmykVector8 : Vector8JpegColorConverter |
|||
{ |
|||
public FromCmykVector8(int precision) |
|||
: base(JpegColorSpace.Cmyk, precision) |
|||
{ |
|||
} |
|||
|
|||
protected override void ConvertCoreVectorizedInplace(in ComponentValues values) |
|||
{ |
|||
ref Vector<float> cBase = |
|||
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|||
ref Vector<float> mBase = |
|||
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|||
ref Vector<float> yBase = |
|||
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|||
ref Vector<float> kBase = |
|||
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|||
|
|||
var scale = new Vector<float>(1 / this.MaximumValue); |
|||
|
|||
// Walking 8 elements at one step:
|
|||
nint n = values.Component0.Length / 8; |
|||
for (nint i = 0; i < n; i++) |
|||
{ |
|||
ref Vector<float> c = ref Unsafe.Add(ref cBase, i); |
|||
ref Vector<float> m = ref Unsafe.Add(ref mBase, i); |
|||
ref Vector<float> y = ref Unsafe.Add(ref yBase, i); |
|||
Vector<float> k = Unsafe.Add(ref kBase, i) * scale; |
|||
|
|||
c = (c * k) * scale; |
|||
m = (m * k) * scale; |
|||
y = (y * k) * scale; |
|||
} |
|||
} |
|||
|
|||
protected override void ConvertCoreInplace(in ComponentValues values) => |
|||
FromCmykBasic.ConvertCoreInplace(values, this.MaximumValue); |
|||
} |
|||
} |
|||
} |
|||
@ -1,47 +1,39 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
#if SUPPORTS_RUNTIME_INTRINSICS
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
#if SUPPORTS_RUNTIME_INTRINSICS
|
|||
using System.Runtime.Intrinsics; |
|||
using System.Runtime.Intrinsics.X86; |
|||
using static SixLabors.ImageSharp.SimdUtils; |
|||
#endif
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverter |
|||
internal abstract partial class JpegColorConverterBase |
|||
{ |
|||
internal sealed class FromGrayscaleAvx2 : Avx2JpegColorConverter |
|||
internal sealed class FromGrayscaleAvx : JpegColorConverterAvx |
|||
{ |
|||
public FromGrayscaleAvx2(int precision) |
|||
public FromGrayscaleAvx(int precision) |
|||
: base(JpegColorSpace.Grayscale, precision) |
|||
{ |
|||
} |
|||
|
|||
protected override void ConvertCoreVectorizedInplace(in ComponentValues values) |
|||
public override void ConvertToRgbInplace(in ComponentValues values) |
|||
{ |
|||
#if SUPPORTS_RUNTIME_INTRINSICS
|
|||
ref Vector256<float> c0Base = |
|||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|||
|
|||
// Used for the color conversion
|
|||
var scale = Vector256.Create(1 / this.MaximumValue); |
|||
|
|||
nint n = values.Component0.Length / 8; |
|||
nint n = values.Component0.Length / Vector256<float>.Count; |
|||
for (nint i = 0; i < n; i++) |
|||
{ |
|||
ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i); |
|||
c0 = Avx.Multiply(c0, scale); |
|||
} |
|||
#endif
|
|||
} |
|||
|
|||
protected override void ConvertCoreInplace(in ComponentValues values) => |
|||
FromGrayscaleBasic.ScaleValues(values.Component0, this.MaximumValue); |
|||
} |
|||
} |
|||
} |
|||
#endif
|
|||
@ -1,53 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverter |
|||
{ |
|||
internal sealed class FromGrayscaleBasic : BasicJpegColorConverter |
|||
{ |
|||
public FromGrayscaleBasic(int precision) |
|||
: base(JpegColorSpace.Grayscale, precision) |
|||
{ |
|||
} |
|||
|
|||
public override void ConvertToRgbInplace(in ComponentValues values) => |
|||
ScaleValues(values.Component0, this.MaximumValue); |
|||
|
|||
internal static void ScaleValues(Span<float> values, float maxValue) |
|||
{ |
|||
Span<Vector4> vecValues = MemoryMarshal.Cast<float, Vector4>(values); |
|||
|
|||
var scaleVector = new Vector4(1 / maxValue); |
|||
|
|||
for (int i = 0; i < vecValues.Length; i++) |
|||
{ |
|||
vecValues[i] *= scaleVector; |
|||
} |
|||
|
|||
values = values.Slice(vecValues.Length * 4); |
|||
if (!values.IsEmpty) |
|||
{ |
|||
float scaleValue = 1f / maxValue; |
|||
values[0] *= scaleValue; |
|||
|
|||
if ((uint)values.Length > 1) |
|||
{ |
|||
values[1] *= scaleValue; |
|||
|
|||
if ((uint)values.Length > 2) |
|||
{ |
|||
values[2] *= scaleValue; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverterBase |
|||
{ |
|||
internal sealed class FromGrayscaleScalar : JpegColorConverterScalar |
|||
{ |
|||
public FromGrayscaleScalar(int precision) |
|||
: base(JpegColorSpace.Grayscale, precision) |
|||
{ |
|||
} |
|||
|
|||
public override void ConvertToRgbInplace(in ComponentValues values) => |
|||
ConvertCoreInplace(values.Component0, this.MaximumValue); |
|||
|
|||
internal static void ConvertCoreInplace(Span<float> values, float maxValue) |
|||
{ |
|||
ref float valuesRef = ref MemoryMarshal.GetReference(values); |
|||
float scale = 1 / maxValue; |
|||
|
|||
for (nint i = 0; i < values.Length; i++) |
|||
{ |
|||
Unsafe.Add(ref valuesRef, i) *= scale; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverterBase |
|||
{ |
|||
internal sealed class FromGrayScaleVector : JpegColorConverterVector |
|||
{ |
|||
public FromGrayScaleVector(int precision) |
|||
: base(JpegColorSpace.Grayscale, precision) |
|||
{ |
|||
} |
|||
|
|||
protected override void ConvertCoreVectorizedInplace(in ComponentValues values) |
|||
{ |
|||
ref Vector<float> cBase = |
|||
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|||
|
|||
var scale = new Vector<float>(1 / this.MaximumValue); |
|||
|
|||
nint n = values.Component0.Length / Vector<float>.Count; |
|||
for (nint i = 0; i < n; i++) |
|||
{ |
|||
ref Vector<float> c0 = ref Unsafe.Add(ref cBase, i); |
|||
c0 *= scale; |
|||
} |
|||
} |
|||
|
|||
protected override void ConvertCoreInplace(in ComponentValues values) => |
|||
FromGrayscaleScalar.ConvertCoreInplace(values.Component0, this.MaximumValue); |
|||
} |
|||
} |
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverter |
|||
{ |
|||
internal sealed class FromRgbBasic : BasicJpegColorConverter |
|||
{ |
|||
public FromRgbBasic(int precision) |
|||
: base(JpegColorSpace.RGB, precision) |
|||
{ |
|||
} |
|||
|
|||
public override void ConvertToRgbInplace(in ComponentValues values) |
|||
{ |
|||
ConvertCoreInplace(values, this.MaximumValue); |
|||
} |
|||
|
|||
internal static void ConvertCoreInplace(ComponentValues values, float maxValue) |
|||
{ |
|||
FromGrayscaleBasic.ScaleValues(values.Component0, maxValue); |
|||
FromGrayscaleBasic.ScaleValues(values.Component1, maxValue); |
|||
FromGrayscaleBasic.ScaleValues(values.Component2, maxValue); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverterBase |
|||
{ |
|||
internal sealed class FromRgbScalar : JpegColorConverterScalar |
|||
{ |
|||
public FromRgbScalar(int precision) |
|||
: base(JpegColorSpace.RGB, precision) |
|||
{ |
|||
} |
|||
|
|||
public override void ConvertToRgbInplace(in ComponentValues values) => |
|||
ConvertCoreInplace(values, this.MaximumValue); |
|||
|
|||
internal static void ConvertCoreInplace(ComponentValues values, float maxValue) |
|||
{ |
|||
FromGrayscaleScalar.ConvertCoreInplace(values.Component0, maxValue); |
|||
FromGrayscaleScalar.ConvertCoreInplace(values.Component1, maxValue); |
|||
FromGrayscaleScalar.ConvertCoreInplace(values.Component2, maxValue); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,42 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverter |
|||
{ |
|||
internal sealed class FromYCbCrBasic : BasicJpegColorConverter |
|||
{ |
|||
public FromYCbCrBasic(int precision) |
|||
: base(JpegColorSpace.YCbCr, precision) |
|||
{ |
|||
} |
|||
|
|||
public override void ConvertToRgbInplace(in ComponentValues values) |
|||
=> ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); |
|||
|
|||
internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue) |
|||
{ |
|||
Span<float> c0 = values.Component0; |
|||
Span<float> c1 = values.Component1; |
|||
Span<float> c2 = values.Component2; |
|||
|
|||
var scale = 1 / maxValue; |
|||
|
|||
for (int i = 0; i < c0.Length; i++) |
|||
{ |
|||
float y = c0[i]; |
|||
float cb = c1[i] - halfValue; |
|||
float cr = c2[i] - halfValue; |
|||
|
|||
c0[i] = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero) * scale; |
|||
c1[i] = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero) * scale; |
|||
c2[i] = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero) * scale; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverterBase |
|||
{ |
|||
internal sealed class FromYCbCrScalar : JpegColorConverterScalar |
|||
{ |
|||
// TODO: comments, derived from ITU-T Rec. T.871
|
|||
internal const float RCrMult = 1.402f; |
|||
internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); |
|||
internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); |
|||
internal const float BCbMult = 1.772f; |
|||
|
|||
public FromYCbCrScalar(int precision) |
|||
: base(JpegColorSpace.YCbCr, precision) |
|||
{ |
|||
} |
|||
|
|||
public override void ConvertToRgbInplace(in ComponentValues values) |
|||
=> ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); |
|||
|
|||
internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue) |
|||
{ |
|||
Span<float> c0 = values.Component0; |
|||
Span<float> c1 = values.Component1; |
|||
Span<float> c2 = values.Component2; |
|||
|
|||
float scale = 1 / maxValue; |
|||
|
|||
for (int i = 0; i < c0.Length; i++) |
|||
{ |
|||
float y = c0[i]; |
|||
float cb = c1[i] - halfValue; |
|||
float cr = c2[i] - halfValue; |
|||
|
|||
// r = y + (1.402F * cr);
|
|||
// g = y - (0.344136F * cb) - (0.714136F * cr);
|
|||
// b = y + (1.772F * cb);
|
|||
c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale; |
|||
c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale; |
|||
c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,88 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp.Tuples; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverter |
|||
{ |
|||
internal sealed class FromYCbCrVector4 : VectorizedJpegColorConverter |
|||
{ |
|||
public FromYCbCrVector4(int precision) |
|||
: base(JpegColorSpace.YCbCr, precision, 8) |
|||
{ |
|||
} |
|||
|
|||
protected override bool IsAvailable => SimdUtils.HasVector4; |
|||
|
|||
protected override void ConvertCoreVectorizedInplace(in ComponentValues values) |
|||
{ |
|||
DebugGuard.IsTrue(values.Component0.Length % 8 == 0, nameof(values), "Length should be divisible by 8!"); |
|||
|
|||
ref Vector4Pair c0Base = |
|||
ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component0)); |
|||
ref Vector4Pair c1Base = |
|||
ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component1)); |
|||
ref Vector4Pair c2Base = |
|||
ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component2)); |
|||
|
|||
var chromaOffset = new Vector4(-this.HalfValue); |
|||
var maxValue = this.MaximumValue; |
|||
|
|||
// Walking 8 elements at one step:
|
|||
nint n = values.Component0.Length / 8; |
|||
|
|||
for (nint i = 0; i < n; i++) |
|||
{ |
|||
// y = yVals[i];
|
|||
ref Vector4Pair c0 = ref Unsafe.Add(ref c0Base, i); |
|||
|
|||
// cb = cbVals[i] - halfValue);
|
|||
ref Vector4Pair c1 = ref Unsafe.Add(ref c1Base, i); |
|||
c1.AddInplace(chromaOffset); |
|||
|
|||
// cr = crVals[i] - halfValue;
|
|||
ref Vector4Pair c2 = ref Unsafe.Add(ref c2Base, i); |
|||
c2.AddInplace(chromaOffset); |
|||
|
|||
// r = y + (1.402F * cr);
|
|||
Vector4Pair r = c0; |
|||
Vector4Pair tmp = c2; |
|||
tmp.MultiplyInplace(1.402F); |
|||
r.AddInplace(ref tmp); |
|||
|
|||
// g = y - (0.344136F * cb) - (0.714136F * cr);
|
|||
Vector4Pair g = c0; |
|||
tmp = c1; |
|||
tmp.MultiplyInplace(-0.344136F); |
|||
g.AddInplace(ref tmp); |
|||
tmp = c2; |
|||
tmp.MultiplyInplace(-0.714136F); |
|||
g.AddInplace(ref tmp); |
|||
|
|||
// b = y + (1.772F * cb);
|
|||
Vector4Pair b = c0; |
|||
tmp = c1; |
|||
tmp.MultiplyInplace(1.772F); |
|||
b.AddInplace(ref tmp); |
|||
|
|||
r.RoundAndDownscalePreVector8(maxValue); |
|||
g.RoundAndDownscalePreVector8(maxValue); |
|||
b.RoundAndDownscalePreVector8(maxValue); |
|||
|
|||
c0 = r; |
|||
c1 = g; |
|||
c2 = b; |
|||
} |
|||
} |
|||
|
|||
protected override void ConvertCoreInplace(in ComponentValues values) |
|||
=> FromYCbCrBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); |
|||
} |
|||
} |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverter |
|||
{ |
|||
internal abstract class Vector8JpegColorConverter : VectorizedJpegColorConverter |
|||
{ |
|||
protected Vector8JpegColorConverter(JpegColorSpace colorSpace, int precision) |
|||
: base(colorSpace, precision, 8) |
|||
{ |
|||
} |
|||
|
|||
protected sealed override bool IsAvailable => SimdUtils.HasVector8; |
|||
} |
|||
} |
|||
} |
|||
@ -1,47 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverter |
|||
{ |
|||
internal abstract class VectorizedJpegColorConverter : JpegColorConverter |
|||
{ |
|||
private readonly int vectorSize; |
|||
|
|||
protected VectorizedJpegColorConverter(JpegColorSpace colorSpace, int precision, int vectorSize) |
|||
: base(colorSpace, precision) |
|||
{ |
|||
this.vectorSize = vectorSize; |
|||
} |
|||
|
|||
public override void ConvertToRgbInplace(in ComponentValues values) |
|||
{ |
|||
int length = values.Component0.Length; |
|||
int remainder = values.Component0.Length % this.vectorSize; |
|||
int simdCount = length - remainder; |
|||
if (simdCount > 0) |
|||
{ |
|||
// This implementation is actually AVX specific.
|
|||
// An AVX register is capable of storing 8 float-s.
|
|||
if (!this.IsAvailable) |
|||
{ |
|||
throw new InvalidOperationException( |
|||
"This converter can be used only on architecture having 256 byte floating point SIMD registers!"); |
|||
} |
|||
|
|||
this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount)); |
|||
} |
|||
|
|||
this.ConvertCoreInplace(values.Slice(simdCount, remainder)); |
|||
} |
|||
|
|||
protected virtual void ConvertCoreVectorizedInplace(in ComponentValues values) => throw new NotImplementedException(); |
|||
|
|||
protected virtual void ConvertCoreInplace(in ComponentValues values) => throw new NotImplementedException(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
#if SUPPORTS_RUNTIME_INTRINSICS
|
|||
using System.Runtime.Intrinsics.X86; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
|
|||
/// based on <see cref="Avx"/> instructions.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Converters of this family would expect input buffers lengths to be
|
|||
/// divisible by 8 without a remainder.
|
|||
/// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks.
|
|||
/// DO NOT pass test data of invalid size to these converters as they
|
|||
/// potentially won't do a bound check and return a false positive result.
|
|||
/// </remarks>
|
|||
internal abstract class JpegColorConverterAvx : JpegColorConverterBase |
|||
{ |
|||
protected JpegColorConverterAvx(JpegColorSpace colorSpace, int precision) |
|||
: base(colorSpace, precision) |
|||
{ |
|||
} |
|||
|
|||
public override bool IsAvailable => Avx.IsSupported; |
|||
} |
|||
} |
|||
} |
|||
#endif
|
|||
@ -0,0 +1,22 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
|
|||
/// based on scalar instructions.
|
|||
/// </summary>
|
|||
internal abstract class JpegColorConverterScalar : JpegColorConverterBase |
|||
{ |
|||
protected JpegColorConverterScalar(JpegColorSpace colorSpace, int precision) |
|||
: base(colorSpace, precision) |
|||
{ |
|||
} |
|||
|
|||
public override bool IsAvailable => true; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|||
{ |
|||
internal abstract partial class JpegColorConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
|
|||
/// based on <see cref="Vector"/> API.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Converters of this family can work with data of any size.
|
|||
/// Even though real life data is guaranteed to be of size
|
|||
/// divisible by 8 newer SIMD instructions like AVX512 won't work with
|
|||
/// such data out of the box. These converters have fallback code
|
|||
/// for 'remainder' data.
|
|||
/// </remarks>
|
|||
internal abstract class JpegColorConverterVector : JpegColorConverterBase |
|||
{ |
|||
protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision) |
|||
: base(colorSpace, precision) |
|||
{ |
|||
} |
|||
|
|||
public sealed override bool IsAvailable => Vector.IsHardwareAccelerated && Vector<float>.Count % 4 == 0; |
|||
|
|||
public override void ConvertToRgbInplace(in ComponentValues values) |
|||
{ |
|||
DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); |
|||
|
|||
int length = values.Component0.Length; |
|||
int remainder = (int)((uint)length % (uint)Vector<float>.Count); |
|||
|
|||
// Jpeg images are guaranteed to have pixel strides at least 8 pixels wide
|
|||
// Thus there's no need to check whether simdCount is greater than zero
|
|||
int simdCount = length - remainder; |
|||
this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount)); |
|||
|
|||
// Jpeg images width is always divisible by 8 without a remainder
|
|||
// so it's safe to say SSE/AVX implementations would never have
|
|||
// 'remainder' pixels
|
|||
// But some exotic simd implementations e.g. AVX-512 can have
|
|||
// remainder pixels
|
|||
if (remainder > 0) |
|||
{ |
|||
this.ConvertCoreInplace(values.Slice(simdCount, remainder)); |
|||
} |
|||
} |
|||
|
|||
protected virtual void ConvertCoreVectorizedInplace(in ComponentValues values) => throw new NotImplementedException(); |
|||
|
|||
protected virtual void ConvertCoreInplace(in ComponentValues values) => throw new NotImplementedException(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,82 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder |
|||
{ |
|||
/// <summary>
|
|||
/// Encapsulates the implementation of processing "raw" jpeg buffers into Jpeg image channels.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal struct JpegBlockPostProcessor |
|||
{ |
|||
/// <summary>
|
|||
/// Source block
|
|||
/// </summary>
|
|||
public Block8x8F SourceBlock; |
|||
|
|||
/// <summary>
|
|||
/// The quantization table as <see cref="Block8x8F"/>.
|
|||
/// </summary>
|
|||
public Block8x8F DequantiazationTable; |
|||
|
|||
/// <summary>
|
|||
/// Defines the horizontal and vertical scale we need to apply to the 8x8 sized block.
|
|||
/// </summary>
|
|||
private Size subSamplingDivisors; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="JpegBlockPostProcessor"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="decoder">The raw jpeg data.</param>
|
|||
/// <param name="component">The raw component.</param>
|
|||
public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component) |
|||
{ |
|||
int qtIndex = component.QuantizationTableIndex; |
|||
this.DequantiazationTable = decoder.QuantizationTables[qtIndex]; |
|||
this.subSamplingDivisors = component.SubSamplingDivisors; |
|||
|
|||
this.SourceBlock = default; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes 'sourceBlock' producing Jpeg color channel values from spectral values:
|
|||
/// - Dequantize
|
|||
/// - Applying IDCT
|
|||
/// - Level shift by +maximumValue/2, clip to [0, maximumValue]
|
|||
/// - Copy the resulting color values into 'destArea' scaling up the block by amount defined in <see cref="subSamplingDivisors"/>.
|
|||
/// </summary>
|
|||
/// <param name="sourceBlock">The source block.</param>
|
|||
/// <param name="destAreaOrigin">Reference to the origin of the destination pixel area.</param>
|
|||
/// <param name="destAreaStride">The width of the destination pixel buffer.</param>
|
|||
/// <param name="maximumValue">The maximum value derived from the bitdepth.</param>
|
|||
public void ProcessBlockColorsInto( |
|||
ref Block8x8 sourceBlock, |
|||
ref float destAreaOrigin, |
|||
int destAreaStride, |
|||
float maximumValue) |
|||
{ |
|||
ref Block8x8F block = ref this.SourceBlock; |
|||
block.LoadFrom(ref sourceBlock); |
|||
|
|||
// Dequantize:
|
|||
block.MultiplyInPlace(ref this.DequantiazationTable); |
|||
|
|||
FastFloatingPointDCT.TransformIDCT(ref block); |
|||
|
|||
// To conform better to libjpeg we actually NEED TO loose precision here.
|
|||
// This is because they store blocks as Int16 between all the operations.
|
|||
// To be "more accurate", we need to emulate this by rounding!
|
|||
block.NormalizeColorsAndRoundInPlace(maximumValue); |
|||
|
|||
block.ScaledCopyTo( |
|||
ref destAreaOrigin, |
|||
destAreaStride, |
|||
this.subSamplingDivisors.Width, |
|||
this.subSamplingDivisors.Height); |
|||
} |
|||
} |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// <auto-generated />
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components |
|||
{ |
|||
internal unsafe partial struct GenericBlock8x8<T> |
|||
{ |
|||
#pragma warning disable 169
|
|||
|
|||
// It's not allowed use fix-sized buffers with generics, need to place all the fields manually:
|
|||
private T _y0_x0, _y0_x1, _y0_x2, _y0_x3, _y0_x4, _y0_x5, _y0_x6, _y0_x7; |
|||
private T _y1_x0, _y1_x1, _y1_x2, _y1_x3, _y1_x4, _y1_x5, _y1_x6, _y1_x7; |
|||
private T _y2_x0, _y2_x1, _y2_x2, _y2_x3, _y2_x4, _y2_x5, _y2_x6, _y2_x7; |
|||
private T _y3_x0, _y3_x1, _y3_x2, _y3_x3, _y3_x4, _y3_x5, _y3_x6, _y3_x7; |
|||
private T _y4_x0, _y4_x1, _y4_x2, _y4_x3, _y4_x4, _y4_x5, _y4_x6, _y4_x7; |
|||
private T _y5_x0, _y5_x1, _y5_x2, _y5_x3, _y5_x4, _y5_x5, _y5_x6, _y5_x7; |
|||
private T _y6_x0, _y6_x1, _y6_x2, _y6_x3, _y6_x4, _y6_x5, _y6_x6, _y6_x7; |
|||
private T _y7_x0, _y7_x1, _y7_x2, _y7_x3, _y7_x4, _y7_x5, _y7_x6, _y7_x7; |
|||
|
|||
#pragma warning restore 169
|
|||
} |
|||
} |
|||
@ -1,122 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components |
|||
{ |
|||
/// <summary>
|
|||
/// A generic 8x8 block implementation, useful for manipulating custom 8x8 pixel data.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal unsafe partial struct GenericBlock8x8<T> |
|||
where T : unmanaged |
|||
{ |
|||
public const int Size = 64; |
|||
|
|||
/// <summary>
|
|||
/// FOR TESTING ONLY!
|
|||
/// Gets or sets a <see cref="Rgb24"/> value at the given index
|
|||
/// </summary>
|
|||
/// <param name="idx">The index</param>
|
|||
/// <returns>The value</returns>
|
|||
public T this[int idx] |
|||
{ |
|||
get |
|||
{ |
|||
ref T selfRef = ref Unsafe.As<GenericBlock8x8<T>, T>(ref this); |
|||
return Unsafe.Add(ref selfRef, idx); |
|||
} |
|||
|
|||
set |
|||
{ |
|||
ref T selfRef = ref Unsafe.As<GenericBlock8x8<T>, T>(ref this); |
|||
Unsafe.Add(ref selfRef, idx) = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// FOR TESTING ONLY!
|
|||
/// Gets or sets a value in a row+column of the 8x8 block
|
|||
/// </summary>
|
|||
/// <param name="x">The x position index in the row</param>
|
|||
/// <param name="y">The column index</param>
|
|||
/// <returns>The value</returns>
|
|||
public T this[int x, int y] |
|||
{ |
|||
get => this[(y * 8) + x]; |
|||
set => this[(y * 8) + x] = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Load a 8x8 region of an image into the block.
|
|||
/// The "outlying" area of the block will be stretched out with pixels on the right and bottom edge of the image.
|
|||
/// </summary>
|
|||
public void LoadAndStretchEdges(Buffer2D<T> source, int sourceX, int sourceY, ref RowOctet<T> currentRows) |
|||
{ |
|||
int width = Math.Min(8, source.Width - sourceX); |
|||
int height = Math.Min(8, source.Height - sourceY); |
|||
|
|||
if (width <= 0 || height <= 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
uint byteWidth = (uint)width * (uint)Unsafe.SizeOf<T>(); |
|||
int remainderXCount = 8 - width; |
|||
|
|||
ref byte blockStart = ref Unsafe.As<GenericBlock8x8<T>, byte>(ref this); |
|||
int blockRowSizeInBytes = 8 * Unsafe.SizeOf<T>(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<T> row = currentRows[y]; |
|||
|
|||
ref byte s = ref Unsafe.As<T, byte>(ref row[sourceX]); |
|||
ref byte d = ref Unsafe.Add(ref blockStart, y * blockRowSizeInBytes); |
|||
|
|||
Unsafe.CopyBlock(ref d, ref s, byteWidth); |
|||
|
|||
ref T last = ref Unsafe.Add(ref Unsafe.As<byte, T>(ref d), width - 1); |
|||
|
|||
for (int x = 1; x <= remainderXCount; x++) |
|||
{ |
|||
Unsafe.Add(ref last, x) = last; |
|||
} |
|||
} |
|||
|
|||
int remainderYCount = 8 - height; |
|||
|
|||
if (remainderYCount == 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * blockRowSizeInBytes); |
|||
|
|||
for (int y = 1; y <= remainderYCount; y++) |
|||
{ |
|||
ref byte remStart = ref Unsafe.Add(ref lastRowStart, blockRowSizeInBytes * y); |
|||
Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)blockRowSizeInBytes); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Only for on-stack instances!
|
|||
/// </summary>
|
|||
public Span<T> AsSpanUnsafe() |
|||
{ |
|||
#if SUPPORTS_CREATESPAN
|
|||
Span<GenericBlock8x8<T>> s = MemoryMarshal.CreateSpan(ref this, 1); |
|||
return MemoryMarshal.Cast<GenericBlock8x8<T>, T>(s); |
|||
#else
|
|||
return new Span<T>(Unsafe.AsPointer(ref this), Size); |
|||
#endif
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,194 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Pixel decoding methods for the PBM binary encoding.
|
|||
/// </summary>
|
|||
internal class BinaryDecoder |
|||
{ |
|||
private static L8 white = new(255); |
|||
private static L8 black = new(0); |
|||
|
|||
/// <summary>
|
|||
/// Decode the specified pixels.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of pixel to encode to.</typeparam>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="pixels">The pixel array to encode into.</param>
|
|||
/// <param name="stream">The stream to read the data from.</param>
|
|||
/// <param name="colorType">The ColorType to decode.</param>
|
|||
/// <param name="componentType">Data type of the pixles components.</param>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// Thrown if an invalid combination of setting is requested.
|
|||
/// </exception>
|
|||
public static void Process<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
if (colorType == PbmColorType.Grayscale) |
|||
{ |
|||
if (componentType == PbmComponentType.Byte) |
|||
{ |
|||
ProcessGrayscale(configuration, pixels, stream); |
|||
} |
|||
else |
|||
{ |
|||
ProcessWideGrayscale(configuration, pixels, stream); |
|||
} |
|||
} |
|||
else if (colorType == PbmColorType.Rgb) |
|||
{ |
|||
if (componentType == PbmComponentType.Byte) |
|||
{ |
|||
ProcessRgb(configuration, pixels, stream); |
|||
} |
|||
else |
|||
{ |
|||
ProcessWideRgb(configuration, pixels, stream); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
ProcessBlackAndWhite(configuration, pixels, stream); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
const int bytesPerPixel = 1; |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
|||
Span<byte> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
stream.Read(rowSpan); |
|||
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromL8Bytes( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan, |
|||
width); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessWideGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
const int bytesPerPixel = 2; |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
|||
Span<byte> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
stream.Read(rowSpan); |
|||
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromL16Bytes( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan, |
|||
width); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
const int bytesPerPixel = 3; |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
|||
Span<byte> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
stream.Read(rowSpan); |
|||
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromRgb24Bytes( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan, |
|||
width); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessWideRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
const int bytesPerPixel = 6; |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
|||
Span<byte> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
stream.Read(rowSpan); |
|||
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromRgb48Bytes( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan, |
|||
width); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessBlackAndWhite<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
int startBit = 0; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
|||
Span<L8> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
for (int x = 0; x < width;) |
|||
{ |
|||
int raw = stream.ReadByte(); |
|||
int bit = startBit; |
|||
startBit = 0; |
|||
for (; bit < 8; bit++) |
|||
{ |
|||
bool bitValue = (raw & (0x80 >> bit)) != 0; |
|||
rowSpan[x] = bitValue ? black : white; |
|||
x++; |
|||
if (x == width) |
|||
{ |
|||
startBit = (bit + 1) & 7; // Round off to below 8.
|
|||
if (startBit != 0) |
|||
{ |
|||
stream.Seek(-1, System.IO.SeekOrigin.Current); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromL8( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,208 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Pixel encoding methods for the PBM binary encoding.
|
|||
/// </summary>
|
|||
internal class BinaryEncoder |
|||
{ |
|||
/// <summary>
|
|||
/// Decode pixels into the PBM binary encoding.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of input pixel.</typeparam>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="stream">The bytestream to write to.</param>
|
|||
/// <param name="image">The input image.</param>
|
|||
/// <param name="colorType">The ColorType to use.</param>
|
|||
/// <param name="componentType">Data type of the pixles components.</param>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// Thrown if an invalid combination of setting is requested.
|
|||
/// </exception>
|
|||
public static void WritePixels<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image, PbmColorType colorType, PbmComponentType componentType) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
if (colorType == PbmColorType.Grayscale) |
|||
{ |
|||
if (componentType == PbmComponentType.Byte) |
|||
{ |
|||
WriteGrayscale(configuration, stream, image); |
|||
} |
|||
else |
|||
{ |
|||
WriteWideGrayscale(configuration, stream, image); |
|||
} |
|||
} |
|||
else if (colorType == PbmColorType.Rgb) |
|||
{ |
|||
if (componentType == PbmComponentType.Byte) |
|||
{ |
|||
WriteRgb(configuration, stream, image); |
|||
} |
|||
else |
|||
{ |
|||
WriteWideRgb(configuration, stream, image); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
WriteBlackAndWhite(configuration, stream, image); |
|||
} |
|||
} |
|||
|
|||
private static void WriteGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Width; |
|||
int height = image.Height; |
|||
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width); |
|||
Span<byte> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
|||
|
|||
PixelOperations<TPixel>.Instance.ToL8Bytes( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan, |
|||
width); |
|||
|
|||
stream.Write(rowSpan); |
|||
} |
|||
} |
|||
|
|||
private static void WriteWideGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
const int bytesPerPixel = 2; |
|||
int width = image.Width; |
|||
int height = image.Height; |
|||
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
|||
Span<byte> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
|||
|
|||
PixelOperations<TPixel>.Instance.ToL16Bytes( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan, |
|||
width); |
|||
|
|||
stream.Write(rowSpan); |
|||
} |
|||
} |
|||
|
|||
private static void WriteRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
const int bytesPerPixel = 3; |
|||
int width = image.Width; |
|||
int height = image.Height; |
|||
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
|||
Span<byte> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
|||
|
|||
PixelOperations<TPixel>.Instance.ToRgb24Bytes( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan, |
|||
width); |
|||
|
|||
stream.Write(rowSpan); |
|||
} |
|||
} |
|||
|
|||
private static void WriteWideRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
const int bytesPerPixel = 6; |
|||
int width = image.Width; |
|||
int height = image.Height; |
|||
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
|||
Span<byte> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
|||
|
|||
PixelOperations<TPixel>.Instance.ToRgb48Bytes( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan, |
|||
width); |
|||
|
|||
stream.Write(rowSpan); |
|||
} |
|||
} |
|||
|
|||
private static void WriteBlackAndWhite<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Width; |
|||
int height = image.Height; |
|||
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
|||
Span<L8> rowSpan = row.GetSpan(); |
|||
|
|||
int previousValue = 0; |
|||
int startBit = 0; |
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
|||
|
|||
PixelOperations<TPixel>.Instance.ToL8( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan); |
|||
|
|||
for (int x = 0; x < width;) |
|||
{ |
|||
int value = previousValue; |
|||
for (int i = startBit; i < 8; i++) |
|||
{ |
|||
if (rowSpan[x].PackedValue < 128) |
|||
{ |
|||
value |= 0x80 >> i; |
|||
} |
|||
|
|||
x++; |
|||
if (x == width) |
|||
{ |
|||
previousValue = value; |
|||
startBit = (i + 1) & 7; // Round off to below 8.
|
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (startBit == 0) |
|||
{ |
|||
stream.WriteByte((byte)value); |
|||
previousValue = 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Extensions methods for <see cref="BufferedReadStream"/>.
|
|||
/// </summary>
|
|||
internal static class BufferedReadStreamExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Skip over any whitespace or any comments.
|
|||
/// </summary>
|
|||
public static void SkipWhitespaceAndComments(this BufferedReadStream stream) |
|||
{ |
|||
bool isWhitespace; |
|||
do |
|||
{ |
|||
int val = stream.ReadByte(); |
|||
|
|||
// Comments start with '#' and end at the next new-line.
|
|||
if (val == 0x23) |
|||
{ |
|||
int innerValue; |
|||
do |
|||
{ |
|||
innerValue = stream.ReadByte(); |
|||
} |
|||
while (innerValue != 0x0a); |
|||
|
|||
// Continue searching for whitespace.
|
|||
val = innerValue; |
|||
} |
|||
|
|||
isWhitespace = val is 0x09 or 0x0a or 0x0d or 0x20; |
|||
} |
|||
while (isWhitespace); |
|||
stream.Seek(-1, SeekOrigin.Current); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Read a decimal text value.
|
|||
/// </summary>
|
|||
/// <returns>The integer value of the decimal.</returns>
|
|||
public static int ReadDecimal(this BufferedReadStream stream) |
|||
{ |
|||
int value = 0; |
|||
while (true) |
|||
{ |
|||
int current = stream.ReadByte() - 0x30; |
|||
if ((uint)current > 9) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
value = (value * 10) + current; |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Configuration options for use during PBM encoding.
|
|||
/// </summary>
|
|||
internal interface IPbmEncoderOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the encoding of the pixels.
|
|||
/// </summary>
|
|||
PbmEncoding? Encoding { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the Color type of the resulting image.
|
|||
/// </summary>
|
|||
PbmColorType? ColorType { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the Data Type of the pixel components.
|
|||
/// </summary>
|
|||
PbmComponentType? ComponentType { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Pbm; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
|
|||
namespace SixLabors.ImageSharp |
|||
{ |
|||
/// <summary>
|
|||
/// Extension methods for the <see cref="ImageMetadata"/> type.
|
|||
/// </summary>
|
|||
public static partial class MetadataExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the pbm format specific metadata for the image.
|
|||
/// </summary>
|
|||
/// <param name="metadata">The metadata this method extends.</param>
|
|||
/// <returns>The <see cref="PbmMetadata"/>.</returns>
|
|||
public static PbmMetadata GetPbmMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(PbmFormat.Instance); |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Provides enumeration of available PBM color types.
|
|||
/// </summary>
|
|||
public enum PbmColorType : byte |
|||
{ |
|||
/// <summary>
|
|||
/// PBM
|
|||
/// </summary>
|
|||
BlackAndWhite = 0, |
|||
|
|||
/// <summary>
|
|||
/// PGM - Greyscale. Single component.
|
|||
/// </summary>
|
|||
Grayscale = 1, |
|||
|
|||
/// <summary>
|
|||
/// PPM - RGB Color. 3 components.
|
|||
/// </summary>
|
|||
Rgb = 2, |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// The data type of the components of the pixels.
|
|||
/// </summary>
|
|||
public enum PbmComponentType : byte |
|||
{ |
|||
/// <summary>
|
|||
/// Single bit per pixel, exclusively for <see cref="PbmColorType.BlackAndWhite"/>.
|
|||
/// </summary>
|
|||
Bit = 0, |
|||
|
|||
/// <summary>
|
|||
/// 8 bits unsigned integer per component.
|
|||
/// </summary>
|
|||
Byte = 1, |
|||
|
|||
/// <summary>
|
|||
/// 16 bits unsigned integer per component.
|
|||
/// </summary>
|
|||
Short = 2 |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Registers the image encoders, decoders and mime type detectors for the Pbm format.
|
|||
/// </summary>
|
|||
public sealed class PbmConfigurationModule : IConfigurationModule |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public void Configure(Configuration configuration) |
|||
{ |
|||
configuration.ImageFormatsManager.SetEncoder(PbmFormat.Instance, new PbmEncoder()); |
|||
configuration.ImageFormatsManager.SetDecoder(PbmFormat.Instance, new PbmDecoder()); |
|||
configuration.ImageFormatsManager.AddImageFormatDetector(new PbmImageFormatDetector()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Contains PBM constant values defined in the specification.
|
|||
/// </summary>
|
|||
internal static class PbmConstants |
|||
{ |
|||
/// <summary>
|
|||
/// The maximum allowable pixel value of a ppm image.
|
|||
/// </summary>
|
|||
public const ushort MaxLength = 65535; |
|||
|
|||
/// <summary>
|
|||
/// The list of mimetypes that equate to a ppm.
|
|||
/// </summary>
|
|||
public static readonly IEnumerable<string> MimeTypes = new[] { "image/x-portable-pixmap", "image/x-portable-anymap" }; |
|||
|
|||
/// <summary>
|
|||
/// The list of file extensions that equate to a ppm.
|
|||
/// </summary>
|
|||
public static readonly IEnumerable<string> FileExtensions = new[] { "ppm", "pbm", "pgm" }; |
|||
} |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Image decoder for reading PGM, PBM or PPM bitmaps from a stream. These images are from
|
|||
/// the family of PNM images.
|
|||
/// <list type="bullet">
|
|||
/// <item>
|
|||
/// <term>PBM</term>
|
|||
/// <description>Black and white images.</description>
|
|||
/// </item>
|
|||
/// <item>
|
|||
/// <term>PGM</term>
|
|||
/// <description>Grayscale images.</description>
|
|||
/// </item>
|
|||
/// <item>
|
|||
/// <term>PPM</term>
|
|||
/// <description>Color images, with RGB pixels.</description>
|
|||
/// </item>
|
|||
/// </list>
|
|||
/// The specification of these images is found at <seealso href="http://netpbm.sourceforge.net/doc/pnm.html"/>.
|
|||
/// </summary>
|
|||
public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
var decoder = new PbmDecoderCore(configuration); |
|||
return decoder.Decode<TPixel>(configuration, stream); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public Image Decode(Configuration configuration, Stream stream) |
|||
=> this.Decode<Rgb24>(configuration, stream); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
var decoder = new PbmDecoderCore(configuration); |
|||
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) |
|||
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken) |
|||
.ConfigureAwait(false); |
|||
|
|||
/// <inheritdoc/>
|
|||
public IImageInfo Identify(Configuration configuration, Stream stream) |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
var decoder = new PbmDecoderCore(configuration); |
|||
return decoder.Identify(configuration, stream); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
var decoder = new PbmDecoderCore(configuration); |
|||
return decoder.IdentifyAsync(configuration, stream, cancellationToken); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,195 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Threading; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Performs the PBM decoding operation.
|
|||
/// </summary>
|
|||
internal sealed class PbmDecoderCore : IImageDecoderInternals |
|||
{ |
|||
private int maxPixelValue; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PbmDecoderCore" /> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
public PbmDecoderCore(Configuration configuration) => this.Configuration = configuration ?? Configuration.Default; |
|||
|
|||
/// <inheritdoc />
|
|||
public Configuration Configuration { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the colortype to use
|
|||
/// </summary>
|
|||
public PbmColorType ColorType { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the size of the pixel array
|
|||
/// </summary>
|
|||
public Size PixelSize { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the component data type
|
|||
/// </summary>
|
|||
public PbmComponentType ComponentType { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the Encoding of pixels
|
|||
/// </summary>
|
|||
public PbmEncoding Encoding { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance.
|
|||
/// </summary>
|
|||
public ImageMetadata Metadata { get; private set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
Size IImageDecoderInternals.Dimensions => this.PixelSize; |
|||
|
|||
private bool NeedsUpscaling => this.ColorType != PbmColorType.BlackAndWhite && this.maxPixelValue is not 255 and not 65535; |
|||
|
|||
/// <inheritdoc/>
|
|||
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
this.ProcessHeader(stream); |
|||
|
|||
var image = new Image<TPixel>(this.Configuration, this.PixelSize.Width, this.PixelSize.Height, this.Metadata); |
|||
|
|||
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer(); |
|||
|
|||
this.ProcessPixels(stream, pixels); |
|||
if (this.NeedsUpscaling) |
|||
{ |
|||
this.ProcessUpscaling(image); |
|||
} |
|||
|
|||
return image; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) |
|||
{ |
|||
this.ProcessHeader(stream); |
|||
|
|||
// BlackAndWhite pixels are encoded into a byte.
|
|||
int bitsPerPixel = this.ComponentType == PbmComponentType.Short ? 16 : 8; |
|||
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.PixelSize.Width, this.PixelSize.Height, this.Metadata); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the ppm header.
|
|||
/// </summary>
|
|||
/// <param name="stream">The input stream.</param>
|
|||
private void ProcessHeader(BufferedReadStream stream) |
|||
{ |
|||
Span<byte> buffer = stackalloc byte[2]; |
|||
|
|||
int bytesRead = stream.Read(buffer); |
|||
if (bytesRead != 2 || buffer[0] != 'P') |
|||
{ |
|||
throw new InvalidImageContentException("Empty or not an PPM image."); |
|||
} |
|||
|
|||
switch ((char)buffer[1]) |
|||
{ |
|||
case '1': |
|||
// Plain PBM format: 1 component per pixel, boolean value ('0' or '1').
|
|||
this.ColorType = PbmColorType.BlackAndWhite; |
|||
this.Encoding = PbmEncoding.Plain; |
|||
break; |
|||
case '2': |
|||
// Plain PGM format: 1 component per pixel, in decimal text.
|
|||
this.ColorType = PbmColorType.Grayscale; |
|||
this.Encoding = PbmEncoding.Plain; |
|||
break; |
|||
case '3': |
|||
// Plain PPM format: 3 components per pixel, in decimal text.
|
|||
this.ColorType = PbmColorType.Rgb; |
|||
this.Encoding = PbmEncoding.Plain; |
|||
break; |
|||
case '4': |
|||
// Binary PBM format: 1 component per pixel, 8 pixels per byte.
|
|||
this.ColorType = PbmColorType.BlackAndWhite; |
|||
this.Encoding = PbmEncoding.Binary; |
|||
break; |
|||
case '5': |
|||
// Binary PGM format: 1 components per pixel, in binary integers.
|
|||
this.ColorType = PbmColorType.Grayscale; |
|||
this.Encoding = PbmEncoding.Binary; |
|||
break; |
|||
case '6': |
|||
// Binary PPM format: 3 components per pixel, in binary integers.
|
|||
this.ColorType = PbmColorType.Rgb; |
|||
this.Encoding = PbmEncoding.Binary; |
|||
break; |
|||
case '7': |
|||
// PAM image: sequence of images.
|
|||
// Not implemented yet
|
|||
default: |
|||
throw new InvalidImageContentException("Unknown of not implemented image type encountered."); |
|||
} |
|||
|
|||
stream.SkipWhitespaceAndComments(); |
|||
int width = stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
int height = stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
if (this.ColorType != PbmColorType.BlackAndWhite) |
|||
{ |
|||
this.maxPixelValue = stream.ReadDecimal(); |
|||
if (this.maxPixelValue > 255) |
|||
{ |
|||
this.ComponentType = PbmComponentType.Short; |
|||
} |
|||
else |
|||
{ |
|||
this.ComponentType = PbmComponentType.Byte; |
|||
} |
|||
|
|||
stream.SkipWhitespaceAndComments(); |
|||
} |
|||
else |
|||
{ |
|||
this.ComponentType = PbmComponentType.Bit; |
|||
} |
|||
|
|||
this.PixelSize = new Size(width, height); |
|||
this.Metadata = new ImageMetadata(); |
|||
PbmMetadata meta = this.Metadata.GetPbmMetadata(); |
|||
meta.Encoding = this.Encoding; |
|||
meta.ColorType = this.ColorType; |
|||
meta.ComponentType = this.ComponentType; |
|||
} |
|||
|
|||
private void ProcessPixels<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
if (this.Encoding == PbmEncoding.Binary) |
|||
{ |
|||
BinaryDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.ComponentType); |
|||
} |
|||
else |
|||
{ |
|||
PlainDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.ComponentType); |
|||
} |
|||
} |
|||
|
|||
private void ProcessUpscaling<TPixel>(Image<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int maxAllocationValue = this.ComponentType == PbmComponentType.Short ? 65535 : 255; |
|||
float factor = maxAllocationValue / this.maxPixelValue; |
|||
image.Mutate(x => x.Brightness(factor)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Image encoder for writing an image to a stream as PGM, PBM or PPM bitmap. These images are from
|
|||
/// the family of PNM images.
|
|||
/// <para>
|
|||
/// The PNM formats are a fairly simple image format. They share a plain text header, consisting of:
|
|||
/// signature, width, height and max_pixel_value only. The pixels follow thereafter and can be in
|
|||
/// plain text decimals separated by spaces, or binary encoded.
|
|||
/// <list type="bullet">
|
|||
/// <item>
|
|||
/// <term>PBM</term>
|
|||
/// <description>Black and white images, with 1 representing black and 0 representing white.</description>
|
|||
/// </item>
|
|||
/// <item>
|
|||
/// <term>PGM</term>
|
|||
/// <description>Grayscale images, scaling from 0 to max_pixel_value, 0 representing black and max_pixel_value representing white.</description>
|
|||
/// </item>
|
|||
/// <item>
|
|||
/// <term>PPM</term>
|
|||
/// <description>Color images, with RGB pixels (in that order), with 0 representing black and 2 representing full color.</description>
|
|||
/// </item>
|
|||
/// </list>
|
|||
/// </para>
|
|||
/// The specification of these images is found at <seealso href="http://netpbm.sourceforge.net/doc/pnm.html"/>.
|
|||
/// </summary>
|
|||
public sealed class PbmEncoder : IImageEncoder, IPbmEncoderOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the Encoding of the pixels.
|
|||
/// </summary>
|
|||
public PbmEncoding? Encoding { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the Color type of the resulting image.
|
|||
/// </summary>
|
|||
public PbmColorType? ColorType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the data type of the pixels components.
|
|||
/// </summary>
|
|||
public PbmComponentType? ComponentType { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Encode<TPixel>(Image<TPixel> image, Stream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
var encoder = new PbmEncoderCore(image.GetConfiguration(), this); |
|||
encoder.Encode(image, stream); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
var encoder = new PbmEncoderCore(image.GetConfiguration(), this); |
|||
return encoder.EncodeAsync(image, stream, cancellationToken); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,187 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers.Text; |
|||
using System.IO; |
|||
using System.Threading; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Image encoder for writing an image to a stream as a PGM, PBM, PPM or PAM bitmap.
|
|||
/// </summary>
|
|||
internal sealed class PbmEncoderCore : IImageEncoderInternals |
|||
{ |
|||
private const byte NewLine = (byte)'\n'; |
|||
private const byte Space = (byte)' '; |
|||
private const byte P = (byte)'P'; |
|||
|
|||
/// <summary>
|
|||
/// The global configuration.
|
|||
/// </summary>
|
|||
private Configuration configuration; |
|||
|
|||
/// <summary>
|
|||
/// The encoder options.
|
|||
/// </summary>
|
|||
private readonly IPbmEncoderOptions options; |
|||
|
|||
/// <summary>
|
|||
/// The encoding for the pixels.
|
|||
/// </summary>
|
|||
private PbmEncoding encoding; |
|||
|
|||
/// <summary>
|
|||
/// Gets the Color type of the resulting image.
|
|||
/// </summary>
|
|||
private PbmColorType colorType; |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum pixel value, per component.
|
|||
/// </summary>
|
|||
private PbmComponentType componentType; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PbmEncoderCore"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="options">The encoder options.</param>
|
|||
public PbmEncoderCore(Configuration configuration, IPbmEncoderOptions options) |
|||
{ |
|||
this.configuration = configuration; |
|||
this.options = options; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
|
|||
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
|
|||
/// <param name="cancellationToken">The token to request cancellation.</param>
|
|||
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)); |
|||
|
|||
this.DeduceOptions(image); |
|||
|
|||
byte signature = this.DeduceSignature(); |
|||
this.WriteHeader(stream, signature, image.Size()); |
|||
|
|||
this.WritePixels(stream, image.Frames.RootFrame); |
|||
|
|||
stream.Flush(); |
|||
} |
|||
|
|||
private void DeduceOptions<TPixel>(Image<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
this.configuration = image.GetConfiguration(); |
|||
PbmMetadata metadata = image.Metadata.GetPbmMetadata(); |
|||
this.encoding = this.options.Encoding ?? metadata.Encoding; |
|||
this.colorType = this.options.ColorType ?? metadata.ColorType; |
|||
if (this.colorType != PbmColorType.BlackAndWhite) |
|||
{ |
|||
this.componentType = this.options.ComponentType ?? metadata.ComponentType; |
|||
} |
|||
else |
|||
{ |
|||
this.componentType = PbmComponentType.Bit; |
|||
} |
|||
} |
|||
|
|||
private byte DeduceSignature() |
|||
{ |
|||
byte signature; |
|||
if (this.colorType == PbmColorType.BlackAndWhite) |
|||
{ |
|||
if (this.encoding == PbmEncoding.Plain) |
|||
{ |
|||
signature = (byte)'1'; |
|||
} |
|||
else |
|||
{ |
|||
signature = (byte)'4'; |
|||
} |
|||
} |
|||
else if (this.colorType == PbmColorType.Grayscale) |
|||
{ |
|||
if (this.encoding == PbmEncoding.Plain) |
|||
{ |
|||
signature = (byte)'2'; |
|||
} |
|||
else |
|||
{ |
|||
signature = (byte)'5'; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// RGB ColorType
|
|||
if (this.encoding == PbmEncoding.Plain) |
|||
{ |
|||
signature = (byte)'3'; |
|||
} |
|||
else |
|||
{ |
|||
signature = (byte)'6'; |
|||
} |
|||
} |
|||
|
|||
return signature; |
|||
} |
|||
|
|||
private void WriteHeader(Stream stream, byte signature, Size pixelSize) |
|||
{ |
|||
Span<byte> buffer = stackalloc byte[128]; |
|||
|
|||
int written = 3; |
|||
buffer[0] = P; |
|||
buffer[1] = signature; |
|||
buffer[2] = NewLine; |
|||
|
|||
Utf8Formatter.TryFormat(pixelSize.Width, buffer.Slice(written), out int bytesWritten); |
|||
written += bytesWritten; |
|||
buffer[written++] = Space; |
|||
Utf8Formatter.TryFormat(pixelSize.Height, buffer.Slice(written), out bytesWritten); |
|||
written += bytesWritten; |
|||
buffer[written++] = NewLine; |
|||
|
|||
if (this.colorType != PbmColorType.BlackAndWhite) |
|||
{ |
|||
int maxPixelValue = this.componentType == PbmComponentType.Short ? 65535 : 255; |
|||
Utf8Formatter.TryFormat(maxPixelValue, buffer.Slice(written), out bytesWritten); |
|||
written += bytesWritten; |
|||
buffer[written++] = NewLine; |
|||
} |
|||
|
|||
stream.Write(buffer, 0, written); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the pixel data to the binary stream.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
|
|||
/// <param name="image">
|
|||
/// The <see cref="ImageFrame{TPixel}"/> containing pixel data.
|
|||
/// </param>
|
|||
private void WritePixels<TPixel>(Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
if (this.encoding == PbmEncoding.Plain) |
|||
{ |
|||
PlainEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.componentType); |
|||
} |
|||
else |
|||
{ |
|||
BinaryEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.componentType); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Provides enumeration of available PBM encodings.
|
|||
/// </summary>
|
|||
public enum PbmEncoding : byte |
|||
{ |
|||
/// <summary>
|
|||
/// Plain text decimal encoding.
|
|||
/// </summary>
|
|||
Plain = 0, |
|||
|
|||
/// <summary>
|
|||
/// Binary integer encoding.
|
|||
/// </summary>
|
|||
Binary = 1, |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Registers the image encoders, decoders and mime type detectors for the PBM format.
|
|||
/// </summary>
|
|||
public sealed class PbmFormat : IImageFormat<PbmMetadata> |
|||
{ |
|||
private PbmFormat() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the current instance.
|
|||
/// </summary>
|
|||
public static PbmFormat Instance { get; } = new(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public string Name => "PBM"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public string DefaultMimeType => "image/x-portable-pixmap"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> MimeTypes => PbmConstants.MimeTypes; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> FileExtensions => PbmConstants.FileExtensions; |
|||
|
|||
/// <inheritdoc/>
|
|||
public PbmMetadata CreateDefaultFormatMetadata() => new(); |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Detects Pbm file headers.
|
|||
/// </summary>
|
|||
public sealed class PbmImageFormatDetector : IImageFormatDetector |
|||
{ |
|||
private const byte P = (byte)'P'; |
|||
private const byte Zero = (byte)'0'; |
|||
private const byte Seven = (byte)'7'; |
|||
|
|||
/// <inheritdoc/>
|
|||
public int HeaderSize => 2; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) => this.IsSupportedFileFormat(header) ? PbmFormat.Instance : null; |
|||
|
|||
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header) |
|||
{ |
|||
#pragma warning disable SA1131 // Use readable conditions
|
|||
if (1 < (uint)header.Length) |
|||
#pragma warning restore SA1131 // Use readable conditions
|
|||
{ |
|||
// Signature should be between P1 and P6.
|
|||
return header[0] == P && (uint)(header[1] - Zero - 1) < (Seven - Zero - 1); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Provides PBM specific metadata information for the image.
|
|||
/// </summary>
|
|||
public class PbmMetadata : IDeepCloneable |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PbmMetadata"/> class.
|
|||
/// </summary>
|
|||
public PbmMetadata() => |
|||
this.ComponentType = this.ColorType == PbmColorType.BlackAndWhite ? PbmComponentType.Bit : PbmComponentType.Byte; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PbmMetadata"/> class.
|
|||
/// </summary>
|
|||
/// <param name="other">The metadata to create an instance from.</param>
|
|||
private PbmMetadata(PbmMetadata other) |
|||
{ |
|||
this.Encoding = other.Encoding; |
|||
this.ColorType = other.ColorType; |
|||
this.ComponentType = other.ComponentType; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the encoding of the pixels.
|
|||
/// </summary>
|
|||
public PbmEncoding Encoding { get; set; } = PbmEncoding.Plain; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the color type.
|
|||
/// </summary>
|
|||
public PbmColorType ColorType { get; set; } = PbmColorType.Grayscale; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the data type of the pixel components.
|
|||
/// </summary>
|
|||
public PbmComponentType ComponentType { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public IDeepCloneable DeepClone() => new PbmMetadata(this); |
|||
} |
|||
} |
|||
@ -0,0 +1,198 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Pixel decoding methods for the PBM plain encoding.
|
|||
/// </summary>
|
|||
internal class PlainDecoder |
|||
{ |
|||
private static readonly L8 White = new(255); |
|||
private static readonly L8 Black = new(0); |
|||
|
|||
/// <summary>
|
|||
/// Decode the specified pixels.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of pixel to encode to.</typeparam>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="pixels">The pixel array to encode into.</param>
|
|||
/// <param name="stream">The stream to read the data from.</param>
|
|||
/// <param name="colorType">The ColorType to decode.</param>
|
|||
/// <param name="componentType">Data type of the pixles components.</param>
|
|||
public static void Process<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
if (colorType == PbmColorType.Grayscale) |
|||
{ |
|||
if (componentType == PbmComponentType.Byte) |
|||
{ |
|||
ProcessGrayscale(configuration, pixels, stream); |
|||
} |
|||
else |
|||
{ |
|||
ProcessWideGrayscale(configuration, pixels, stream); |
|||
} |
|||
} |
|||
else if (colorType == PbmColorType.Rgb) |
|||
{ |
|||
if (componentType == PbmComponentType.Byte) |
|||
{ |
|||
ProcessRgb(configuration, pixels, stream); |
|||
} |
|||
else |
|||
{ |
|||
ProcessWideRgb(configuration, pixels, stream); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
ProcessBlackAndWhite(configuration, pixels, stream); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
|||
Span<L8> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
byte value = (byte)stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
rowSpan[x] = new L8(value); |
|||
} |
|||
|
|||
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromL8( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessWideGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<L16> row = allocator.Allocate<L16>(width); |
|||
Span<L16> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
ushort value = (ushort)stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
rowSpan[x] = new L16(value); |
|||
} |
|||
|
|||
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromL16( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<Rgb24> row = allocator.Allocate<Rgb24>(width); |
|||
Span<Rgb24> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
byte red = (byte)stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
byte green = (byte)stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
byte blue = (byte)stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
rowSpan[x] = new Rgb24(red, green, blue); |
|||
} |
|||
|
|||
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromRgb24( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessWideRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<Rgb48> row = allocator.Allocate<Rgb48>(width); |
|||
Span<Rgb48> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
ushort red = (ushort)stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
ushort green = (ushort)stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
ushort blue = (ushort)stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
rowSpan[x] = new Rgb48(red, green, blue); |
|||
} |
|||
|
|||
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromRgb48( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessBlackAndWhite<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
|||
Span<L8> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
int value = stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
rowSpan[x] = value == 0 ? White : Black; |
|||
} |
|||
|
|||
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromL8( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,251 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Buffers.Text; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Pixel encoding methods for the PBM plain encoding.
|
|||
/// </summary>
|
|||
internal class PlainEncoder |
|||
{ |
|||
private const byte NewLine = 0x0a; |
|||
private const byte Space = 0x20; |
|||
private const byte Zero = 0x30; |
|||
private const byte One = 0x31; |
|||
|
|||
private const int MaxCharsPerPixelBlackAndWhite = 2; |
|||
private const int MaxCharsPerPixelGrayscale = 4; |
|||
private const int MaxCharsPerPixelGrayscaleWide = 6; |
|||
private const int MaxCharsPerPixelRgb = 4 * 3; |
|||
private const int MaxCharsPerPixelRgbWide = 6 * 3; |
|||
|
|||
private static readonly StandardFormat DecimalFormat = StandardFormat.Parse("D"); |
|||
|
|||
/// <summary>
|
|||
/// Decode pixels into the PBM plain encoding.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of input pixel.</typeparam>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="stream">The bytestream to write to.</param>
|
|||
/// <param name="image">The input image.</param>
|
|||
/// <param name="colorType">The ColorType to use.</param>
|
|||
/// <param name="componentType">Data type of the pixles components.</param>
|
|||
public static void WritePixels<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image, PbmColorType colorType, PbmComponentType componentType) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
if (colorType == PbmColorType.Grayscale) |
|||
{ |
|||
if (componentType == PbmComponentType.Byte) |
|||
{ |
|||
WriteGrayscale(configuration, stream, image); |
|||
} |
|||
else |
|||
{ |
|||
WriteWideGrayscale(configuration, stream, image); |
|||
} |
|||
} |
|||
else if (colorType == PbmColorType.Rgb) |
|||
{ |
|||
if (componentType == PbmComponentType.Byte) |
|||
{ |
|||
WriteRgb(configuration, stream, image); |
|||
} |
|||
else |
|||
{ |
|||
WriteWideRgb(configuration, stream, image); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
WriteBlackAndWhite(configuration, stream, image); |
|||
} |
|||
|
|||
// Write EOF indicator, as some encoders expect it.
|
|||
stream.WriteByte(Space); |
|||
} |
|||
|
|||
private static void WriteGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Width; |
|||
int height = image.Height; |
|||
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
|||
Span<L8> rowSpan = row.GetSpan(); |
|||
using IMemoryOwner<byte> plainMemory = allocator.Allocate<byte>(width * MaxCharsPerPixelGrayscale); |
|||
Span<byte> plainSpan = plainMemory.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.ToL8( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan); |
|||
|
|||
int written = 0; |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
Utf8Formatter.TryFormat(rowSpan[x].PackedValue, plainSpan.Slice(written), out int bytesWritten, DecimalFormat); |
|||
written += bytesWritten; |
|||
plainSpan[written++] = Space; |
|||
} |
|||
|
|||
plainSpan[written - 1] = NewLine; |
|||
stream.Write(plainSpan, 0, written); |
|||
} |
|||
} |
|||
|
|||
private static void WriteWideGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Width; |
|||
int height = image.Height; |
|||
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<L16> row = allocator.Allocate<L16>(width); |
|||
Span<L16> rowSpan = row.GetSpan(); |
|||
using IMemoryOwner<byte> plainMemory = allocator.Allocate<byte>(width * MaxCharsPerPixelGrayscaleWide); |
|||
Span<byte> plainSpan = plainMemory.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.ToL16( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan); |
|||
|
|||
int written = 0; |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
Utf8Formatter.TryFormat(rowSpan[x].PackedValue, plainSpan.Slice(written), out int bytesWritten, DecimalFormat); |
|||
written += bytesWritten; |
|||
plainSpan[written++] = Space; |
|||
} |
|||
|
|||
plainSpan[written - 1] = NewLine; |
|||
stream.Write(plainSpan, 0, written); |
|||
} |
|||
} |
|||
|
|||
private static void WriteRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Width; |
|||
int height = image.Height; |
|||
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<Rgb24> row = allocator.Allocate<Rgb24>(width); |
|||
Span<Rgb24> rowSpan = row.GetSpan(); |
|||
using IMemoryOwner<byte> plainMemory = allocator.Allocate<byte>(width * MaxCharsPerPixelRgb); |
|||
Span<byte> plainSpan = plainMemory.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.ToRgb24( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan); |
|||
|
|||
int written = 0; |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
Utf8Formatter.TryFormat(rowSpan[x].R, plainSpan.Slice(written), out int bytesWritten, DecimalFormat); |
|||
written += bytesWritten; |
|||
plainSpan[written++] = Space; |
|||
Utf8Formatter.TryFormat(rowSpan[x].G, plainSpan.Slice(written), out bytesWritten, DecimalFormat); |
|||
written += bytesWritten; |
|||
plainSpan[written++] = Space; |
|||
Utf8Formatter.TryFormat(rowSpan[x].B, plainSpan.Slice(written), out bytesWritten, DecimalFormat); |
|||
written += bytesWritten; |
|||
plainSpan[written++] = Space; |
|||
} |
|||
|
|||
plainSpan[written - 1] = NewLine; |
|||
stream.Write(plainSpan, 0, written); |
|||
} |
|||
} |
|||
|
|||
private static void WriteWideRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Width; |
|||
int height = image.Height; |
|||
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<Rgb48> row = allocator.Allocate<Rgb48>(width); |
|||
Span<Rgb48> rowSpan = row.GetSpan(); |
|||
using IMemoryOwner<byte> plainMemory = allocator.Allocate<byte>(width * MaxCharsPerPixelRgbWide); |
|||
Span<byte> plainSpan = plainMemory.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.ToRgb48( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan); |
|||
|
|||
int written = 0; |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
Utf8Formatter.TryFormat(rowSpan[x].R, plainSpan.Slice(written), out int bytesWritten, DecimalFormat); |
|||
written += bytesWritten; |
|||
plainSpan[written++] = Space; |
|||
Utf8Formatter.TryFormat(rowSpan[x].G, plainSpan.Slice(written), out bytesWritten, DecimalFormat); |
|||
written += bytesWritten; |
|||
plainSpan[written++] = Space; |
|||
Utf8Formatter.TryFormat(rowSpan[x].B, plainSpan.Slice(written), out bytesWritten, DecimalFormat); |
|||
written += bytesWritten; |
|||
plainSpan[written++] = Space; |
|||
} |
|||
|
|||
plainSpan[written - 1] = NewLine; |
|||
stream.Write(plainSpan, 0, written); |
|||
} |
|||
} |
|||
|
|||
private static void WriteBlackAndWhite<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Width; |
|||
int height = image.Height; |
|||
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
|||
Span<L8> rowSpan = row.GetSpan(); |
|||
using IMemoryOwner<byte> plainMemory = allocator.Allocate<byte>(width * MaxCharsPerPixelBlackAndWhite); |
|||
Span<byte> plainSpan = plainMemory.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.ToL8( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan); |
|||
|
|||
int written = 0; |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
byte value = (rowSpan[x].PackedValue < 128) ? One : Zero; |
|||
plainSpan[written++] = value; |
|||
plainSpan[written++] = Space; |
|||
} |
|||
|
|||
plainSpan[written - 1] = NewLine; |
|||
stream.Write(plainSpan, 0, written); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue