diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 338a18a403..1d9622aa49 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -246,27 +246,23 @@ namespace SixLabors.ImageSharp return this.Clone() as ImageFrame; } - Func scaleFunc = PackedPixelConverterHelper.ComputeScaleFunction(); - var target = new ImageFrame(this.MemoryManager, this.Width, this.Height, this.MetaData.Clone()); - using (PixelAccessor pixels = this.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - 0, - target.Height, - Configuration.Default.ParallelOptions, - y => + Parallel.For( + 0, + target.Height, + Configuration.Default.ParallelOptions, + y => + { + Span sourceRow = this.GetPixelRowSpan(y); + Span targetRow = target.GetPixelRowSpan(y); + + for (int x = 0; x < target.Width; x++) { - for (int x = 0; x < target.Width; x++) - { - var color = default(TPixel2); - color.PackFromVector4(scaleFunc(pixels[x, y].ToVector4())); - targetPixels[x, y] = color; - } - }); - } + ref var pixel = ref targetRow[x]; + pixel.PackFromScaledVector4(sourceRow[x].ToScaledVector4()); + } + }); return target; } diff --git a/src/ImageSharp/PixelFormats/PackedPixelConverterHelper.cs b/src/ImageSharp/PixelFormats/PackedPixelConverterHelper.cs deleted file mode 100644 index ae5f785a96..0000000000 --- a/src/ImageSharp/PixelFormats/PackedPixelConverterHelper.cs +++ /dev/null @@ -1,365 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; - -namespace SixLabors.ImageSharp.PixelFormats -{ - /// - /// Assists with the conversion of known packed pixel formats from one to another. - /// - internal static class PackedPixelConverterHelper - { - /// - /// A non operative function. Simply returns the original vector. - /// - private static readonly Func Noop = vector4 => vector4; - - /// - /// Returns the correct scaling function for the given types The compute scale function. - /// - /// The source pixel format. - /// The target pixel format. - /// The - public static Func ComputeScaleFunction() - { - Type source = typeof(TPixel); - Type target = typeof(TPixel2); - - // Normalized standard - if (IsStandardNormalizedType(source)) - { - return FromStandardNormalizedType(target); - } - - // Standard - if (IsStandardType(source)) - { - return FromStandardType(target); - } - - // Normalized offsets. All four components. - if (IsOffsetNormalizedType(source)) - { - return FromOffsetNormalizedType(target); - } - - // Offset. All four components. - if (IsOffsetType(source)) - { - return FromOffsetType(target); - } - - // Normalized offsets. First component pair only. - if (IsOffsetTwoComponentNormalizedType(source)) - { - return FromOffsetTwoComponentNormalizedType(target); - } - - // Offsets. First component pair only. - if (IsOffsetTwoComponentType(source)) - { - return FromOffsetTwoComponentType(target); - } - - return Noop; - } - - /// - /// Returns the correct conversion function to convert from types having vector values representing all four components - /// ranging from 0 to 1. - /// - /// The target type - /// The - private static Func FromStandardNormalizedType(Type target) - { - if (IsStandardType(target)) - { - return vector4 => 255F * vector4; - } - - if (IsOffsetNormalizedType(target) || IsOffsetTwoComponentNormalizedType(target)) - { - // Expand the range then offset the center down. - return vector4 => (2F * vector4) - Vector4.One; - } - - if (IsOffsetType(target) || IsOffsetTwoComponentType(target)) - { - return vector4 => (65534F * vector4) - new Vector4(32767F); - } - - return Noop; - } - - /// - /// Returns the correct conversion function to convert from types having vector values representing all four components - /// ranging from 0 to 255. - /// - /// The target type - /// The - private static Func FromStandardType(Type target) - { - // Scale down - if (IsStandardNormalizedType(target)) - { - return vector4 => vector4 / 255F; - } - - if (IsOffsetNormalizedType(target) || IsOffsetTwoComponentNormalizedType(target)) - { - // Expand the range, divide, then offset the center down. - return vector4 => ((2F * (vector4 / 255F)) - Vector4.One); - } - - if (IsOffsetType(target) || IsOffsetTwoComponentType(target)) - { - return vector4 => (65534F * (vector4 / 255F)) - new Vector4(32767F); - } - - return Noop; - } - - /// - /// Returns the correct conversion function to convert from types having vector values representing all four components - /// ranging from -1 to 1. - /// - /// The target type - /// The - private static Func FromOffsetNormalizedType(Type target) - { - if (IsStandardNormalizedType(target)) - { - // Compress the range then offset the center up. - return vector4 => (vector4 / 2F) + new Vector4(.5F); - } - - if (IsStandardType(target)) - { - // Compress the range, multiply, then offset the center up. - return vector4 => ((vector4 / 2F) * 255F) + new Vector4(127.5F); - } - - if (IsOffsetType(target) || IsOffsetTwoComponentType(target)) - { - // Multiply out the range, two component won't read the last two values. - return vector4 => (vector4 * 32767F); - } - - return Noop; - } - - /// - /// Returns the correct conversion function to convert from types having vector values representing all four components - /// ranging from -32767 to 32767. - /// - /// The target type - /// The - private static Func FromOffsetType(Type target) - { - if (IsStandardNormalizedType(target)) - { - // Compress the range then offset the center up. - return vector4 => (vector4 / 65534F) + new Vector4(.5F); - } - - if (IsStandardType(target)) - { - // Compress the range, multiply, then offset the center up. - return vector4 => ((vector4 / 65534F) * 255F) + new Vector4(127.5F); - } - - if (IsOffsetNormalizedType(target) || IsOffsetTwoComponentNormalizedType(target)) - { - // Compress the range. Two component won't read the last two values. - return vector4 => (vector4 / 32767F); - } - - return Noop; - } - - /// - /// Returns the correct conversion function to convert from types having vector with the first component pair ranging from -1 to 1. - /// and the second component pair ranging from 0 to 1. - /// - /// The target type - /// The - private static Func FromOffsetTwoComponentNormalizedType(Type target) - { - if (IsStandardNormalizedType(target)) - { - return vector4 => - { - // Compress the range then offset the center up for first pair. - Vector4 v = (vector4 / 2F) + new Vector4(.5F); - return new Vector4(v.X, v.Y, 0F, 1F); - }; - } - - if (IsStandardType(target)) - { - return vector4 => - { - // Compress the range, multiply, then offset the center up for first pair. - Vector4 v = ((vector4 / 2F) * 255F) + new Vector4(127.5F); - return new Vector4(v.X, v.Y, 0F, 255F); - }; - } - - if (IsOffsetNormalizedType(target)) - { - // Copy the first two components and set second pair to 0 and 1 equivalent. - return vector4 => new Vector4(vector4.X, vector4.Y, -1F, 1F); - } - - if (IsOffsetTwoComponentType(target)) - { - // Multiply. Two component won't read the last two values. - return vector4 => (vector4 * 32767F); - } - - if (IsOffsetType(target)) - { - return vector4 => - { - // Multiply the first two components and set second pair to 0 and 1 equivalent. - Vector4 v = vector4 * 32767F; - return new Vector4(v.X, v.Y, -32767F, 32767F); - }; - } - - return Noop; - } - - /// - /// Returns the correct conversion function to convert from types having vector with the first component pair ranging from -32767 to 32767. - /// and the second component pair ranging from 0 to 1. - /// - /// The target type - /// The - private static Func FromOffsetTwoComponentType(Type target) - { - if (IsStandardNormalizedType(target)) - { - return vector4 => - { - Vector4 v = (vector4 / 65534F) + new Vector4(.5F); - return new Vector4(v.X, v.Y, 0, 1); - }; - } - - if (IsStandardType(target)) - { - return vector4 => - { - Vector4 v = ((vector4 / 65534F) * 255F) + new Vector4(127.5F); - return new Vector4(v.X, v.Y, 0, 255F); - }; - } - - if (IsOffsetType(target)) - { - // Copy the first two components and set second pair to 0 and 1 equivalent. - return vector4 => new Vector4(vector4.X, vector4.Y, -32767F, 32767F); - } - - if (IsOffsetNormalizedType(target)) - { - return vector4 => - { - // Divide the first two components and set second pair to 0 and 1 equivalent. - Vector4 v = vector4 / 32767F; - return new Vector4(v.X, v.Y, -1F, 1F); - }; - } - - if (IsOffsetTwoComponentNormalizedType(target)) - { - // Divide. Two component won't read the last two values. - return vector4 => (vector4 / 32767F); - } - - return Noop; - } - - /// - /// Identifies the type as having vector component values ranging from 0 to 1. - /// - /// The type to test. - /// The - private static bool IsStandardNormalizedType(Type type) - { - return - type == typeof(Alpha8) - || type == typeof(Argb32) - || type == typeof(Bgr24) - || type == typeof(Bgra32) - || type == typeof(Bgr565) - || type == typeof(Bgra4444) - || type == typeof(Bgra5551) - || type == typeof(HalfSingle) - || type == typeof(HalfVector2) - || type == typeof(HalfVector4) - || type == typeof(Rg32) - || type == typeof(Rgb24) - || type == typeof(Rgba32) - || type == typeof(Rgba64) - || type == typeof(Rgba1010102); - } - - /// - /// Identifies the type as having vector component values ranging from 0 to 255. - /// - /// The type to test. - /// The - private static bool IsStandardType(Type type) - { - return type == typeof(Byte4); - } - - /// - /// Identifies the type as having vector values representing the first component pair ranging from -1 to 1. - /// and the second component pair ranging from 0 to 1. - /// - /// The type to test. - /// The - private static bool IsOffsetTwoComponentNormalizedType(Type type) - { - return type == typeof(NormalizedByte2) - || type == typeof(NormalizedShort2); - } - - /// - /// Identifies the type as having vector values representing all four components ranging from -1 to 1. - /// - /// The type to test. - /// The - private static bool IsOffsetNormalizedType(Type type) - { - return type == typeof(NormalizedByte4) - || type == typeof(NormalizedShort4); - } - - /// - /// Identifies the type as having vector values representing the first component pair ranging from -32767 to 32767. - /// and the second component pair ranging from 0 to 1. - /// - /// The type to test. - /// The - private static bool IsOffsetTwoComponentType(Type type) - { - return type == typeof(Short2); - } - - /// - /// Identifies the type as having vector values representing all four components ranging from -32767 to 32767. - /// - /// The type to test. - /// The - private static bool IsOffsetType(Type type) - { - return type == typeof(Short4); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs index 12e0fc8834..82864f1562 100644 --- a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs @@ -21,13 +21,89 @@ namespace SixLabors.ImageSharp.Tests for (int x = 0; x < image.Width; x++) { - Rgba32 rgba = row[x]; - Bgra32 bgra = rowClone[x]; + Rgba32 expected = row[x]; + Bgra32 actual = rowClone[x]; - Assert.Equal(rgba.R, bgra.R); - Assert.Equal(rgba.G, bgra.G); - Assert.Equal(rgba.B, bgra.B); - Assert.Equal(rgba.A, bgra.A); + Assert.Equal(expected.R, actual.R); + Assert.Equal(expected.G, actual.G); + Assert.Equal(expected.B, actual.B); + Assert.Equal(expected.A, actual.A); + } + } + } + } + + [Theory] + [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] + public void CloneAs_ToBgr24(TestImageProvider provider) + { + using (Image image = provider.GetImage()) + using (Image clone = image.CloneAs()) + { + for (int y = 0; y < image.Height; y++) + { + Span row = image.GetPixelRowSpan(y); + Span rowClone = clone.GetPixelRowSpan(y); + + for (int x = 0; x < image.Width; x++) + { + Rgba32 expected = row[x]; + Bgr24 actual = rowClone[x]; + + Assert.Equal(expected.R, actual.R); + Assert.Equal(expected.G, actual.G); + Assert.Equal(expected.B, actual.B); + } + } + } + } + + [Theory] + [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] + public void CloneAs_ToArgb32(TestImageProvider provider) + { + using (Image image = provider.GetImage()) + using (Image clone = image.CloneAs()) + { + for (int y = 0; y < image.Height; y++) + { + Span row = image.GetPixelRowSpan(y); + Span rowClone = clone.GetPixelRowSpan(y); + + for (int x = 0; x < image.Width; x++) + { + Rgba32 expected = row[x]; + Argb32 actual = rowClone[x]; + + Assert.Equal(expected.R, actual.R); + Assert.Equal(expected.G, actual.G); + Assert.Equal(expected.B, actual.B); + Assert.Equal(expected.A, actual.A); + } + } + } + } + + [Theory] + [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] + public void CloneAs_ToRgb24(TestImageProvider provider) + { + using (Image image = provider.GetImage()) + using (Image clone = image.CloneAs()) + { + for (int y = 0; y < image.Height; y++) + { + Span row = image.GetPixelRowSpan(y); + Span rowClone = clone.GetPixelRowSpan(y); + + for (int x = 0; x < image.Width; x++) + { + Rgba32 expected = row[x]; + Rgb24 actual = rowClone[x]; + + Assert.Equal(expected.R, actual.R); + Assert.Equal(expected.G, actual.G); + Assert.Equal(expected.B, actual.B); } } }