Browse Source

Fix Gray8

af/merge-core
James Jackson-South 7 years ago
parent
commit
1bd63b33b3
  1. 67
      src/ImageSharp/Common/Helpers/ImageMaths.cs
  2. 105
      src/ImageSharp/PixelFormats/Alpha8.cs
  3. 188
      src/ImageSharp/PixelFormats/Gray8.cs

67
src/ImageSharp/Common/Helpers/ImageMaths.cs

@ -13,6 +13,73 @@ namespace SixLabors.ImageSharp
/// </summary>
internal static class ImageMaths
{
/// <summary>
/// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709.
/// </summary>
/// <param name="r">The red component.</param>
/// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param>
/// <returns>The <see cref="byte"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static byte GetBT709LuminanceBytes(byte r, byte g, byte b) => (byte)((r * .2126F) + (g * .7152F) + (b * .0722F));
/// <summary>
/// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709.
/// </summary>
/// <param name="r">The red component.</param>
/// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param>
/// <returns>The <see cref="ushort"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static ushort GetBT709Luminance(ushort r, ushort g, ushort b) => (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F));
/// <summary>
/// Scales a value from a 16 bit <see cref="ushort"/> to it's 8 bit <see cref="byte"/> equivalent.
/// </summary>
/// <param name="component">The 8 bit compoonent value.</param>
/// <returns>The <see cref="byte"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static byte DownScaleFrom16BitTo8Bit(ushort component)
{
// To scale to 8 bits From a 16-bit value V the required value (from the PNG specification) is:
//
// (V * 255) / 65535
//
// This reduces to round(V / 257), or floor((V + 128.5)/257)
//
// Represent V as the two byte value vhi.vlo. Make a guess that the
// result is the top byte of V, vhi, then the correction to this value
// is:
//
// error = floor(((V-vhi.vhi) + 128.5) / 257)
// = floor(((vlo-vhi) + 128.5) / 257)
//
// This can be approximated using integer arithmetic (and a signed
// shift):
//
// error = (vlo-vhi+128) >> 8;
//
// The approximate differs from the exact answer only when (vlo-vhi) is
// 128; it then gives a correction of +1 when the exact correction is
// 0. This gives 128 errors. The exact answer (correct for all 16-bit
// input values) is:
//
// error = (vlo-vhi+128)*65535 >> 24;
//
// An alternative arithmetic calculation which also gives no errors is:
//
// (V * 255 + 32895) >> 16
return (byte)(((component * 255) + 32895) >> 16);
}
/// <summary>
/// Scales a value from an 8 bit <see cref="byte"/> to it's 16 bit <see cref="ushort"/> equivalent.
/// </summary>
/// <param name="component">The 8 bit compoonent value.</param>
/// <returns>The <see cref="ushort"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static ushort UpscaleFrom8BitTo16Bit(byte component) => (ushort)(component * 257);
/// <summary>
/// Returns the absolute value of a 32-bit signed integer. Uses bit shifting to speed up the operation.
/// </summary>

105
src/ImageSharp/PixelFormats/Alpha8.cs

@ -19,10 +19,7 @@ namespace SixLabors.ImageSharp.PixelFormats
/// Initializes a new instance of the <see cref="Alpha8"/> struct.
/// </summary>
/// <param name="alpha">The alpha component</param>
public Alpha8(float alpha)
{
this.PackedValue = Pack(alpha);
}
public Alpha8(float alpha) => this.PackedValue = Pack(alpha);
/// <inheritdoc />
public byte PackedValue { get; set; }
@ -40,10 +37,7 @@ namespace SixLabors.ImageSharp.PixelFormats
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Alpha8 left, Alpha8 right)
{
return left.PackedValue == right.PackedValue;
}
public static bool operator ==(Alpha8 left, Alpha8 right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="Alpha8"/> objects for equality.
@ -54,77 +48,48 @@ namespace SixLabors.ImageSharp.PixelFormats
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Alpha8 left, Alpha8 right)
{
return left.PackedValue != right.PackedValue;
}
public static bool operator !=(Alpha8 left, Alpha8 right) => !left.Equals(right);
/// <inheritdoc />
public PixelOperations<Alpha8> CreatePixelOperations() => new PixelOperations<Alpha8>();
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromScaledVector4(Vector4 vector)
{
this.PackFromVector4(vector);
}
public void PackFromScaledVector4(Vector4 vector) => this.PackFromVector4(vector);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToScaledVector4()
{
return this.ToVector4();
}
public Vector4 ToScaledVector4() => this.ToVector4();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromVector4(Vector4 vector)
{
this.PackedValue = Pack(vector.W);
}
public void PackFromVector4(Vector4 vector) => this.PackedValue = Pack(vector.W);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToVector4()
{
return new Vector4(0, 0, 0, this.PackedValue / 255F);
}
public Vector4 ToVector4() => new Vector4(0, 0, 0, this.PackedValue / 255F);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromRgba32(Rgba32 source)
{
this.PackedValue = source.A;
}
public void PackFromRgba32(Rgba32 source) => this.PackedValue = source.A;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromArgb32(Argb32 source)
{
this.PackedValue = source.A;
}
public void PackFromArgb32(Argb32 source) => this.PackedValue = source.A;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromBgra32(Bgra32 source)
{
this.PackedValue = source.A;
}
public void PackFromBgra32(Bgra32 source) => this.PackedValue = source.A;
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ToRgb24(ref Rgb24 dest)
{
dest = default(Rgb24);
}
public void ToRgb24(ref Rgb24 dest) => dest = default;
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ToRgba32(ref Rgba32 dest)
{
dest.R = 0;
dest.G = 0;
dest.B = 0;
dest.Rgb = default;
dest.A = this.PackedValue;
}
@ -140,10 +105,7 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ToBgr24(ref Bgr24 dest)
{
dest = default(Bgr24);
}
public void ToBgr24(ref Bgr24 dest) => dest = default;
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -161,28 +123,23 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ToRgb48(ref Rgb48 dest)
{
dest.R = 0;
dest.G = 0;
dest.B = 0;
}
public void ToRgb48(ref Rgb48 dest) => dest = default;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromGray8(Gray8 source) => this.PackedValue = 255;
public void PackFromGray8(Gray8 source) => this.PackedValue = byte.MaxValue;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ToGray8(ref Gray8 dest) => dest.PackedValue = 0;
public void ToGray8(ref Gray8 dest) => dest = default;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromGray16(Gray16 source) => this.PackedValue = 255;
public void PackFromGray16(Gray16 source) => this.PackedValue = byte.MaxValue;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ToGray16(ref Gray16 dest) => dest.PackedValue = 0;
public void ToGray16(ref Gray16 dest) => dest = default;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -190,17 +147,18 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4());
public void ToRgba64(ref Rgba64 dest)
{
dest.Rgb = default;
dest.A = ImageMaths.UpscaleFrom8BitTo16Bit(this.PackedValue);
}
/// <summary>
/// Compares an object with the packed vector.
/// </summary>
/// <param name="obj">The object to compare.</param>
/// <returns>True if the object is equal to the packed vector.</returns>
public override bool Equals(object obj)
{
return obj is Alpha8 other && this.Equals(other);
}
public override bool Equals(object obj) => obj is Alpha8 other && this.Equals(other);
/// <summary>
/// Compares another Alpha8 packed vector with the packed vector.
@ -208,19 +166,13 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <param name="other">The Alpha8 packed vector to compare.</param>
/// <returns>True if the packed vectors are equal.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Alpha8 other)
{
return this.PackedValue == other.PackedValue;
}
public bool Equals(Alpha8 other) => this.PackedValue.Equals(other.PackedValue);
/// <summary>
/// Gets a string representation of the packed vector.
/// </summary>
/// <returns>A string representation of the packed vector.</returns>
public override string ToString()
{
return (this.PackedValue / 255F).ToString();
}
public override string ToString() => (this.PackedValue / 255F).ToString();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -232,9 +184,6 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <param name="alpha">The float containing the value to pack.</param>
/// <returns>The <see cref="byte"/> containing the packed values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte Pack(float alpha)
{
return (byte)Math.Round(alpha.Clamp(0, 1) * 255F);
}
private static byte Pack(float alpha) => (byte)Math.Round(alpha.Clamp(0, 1) * 255F);
}
}

188
src/ImageSharp/PixelFormats/Gray8.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
@ -30,14 +29,21 @@ namespace SixLabors.ImageSharp.PixelFormats
/// </summary>
private const float Bx = .0722F;
/// <summary>
/// The maximum byte value.
/// </summary>
private static readonly Vector4 MaxBytes = new Vector4(255F);
/// <summary>
/// The half vector value.
/// </summary>
private static readonly Vector4 Half = new Vector4(0.5F);
/// <summary>
/// Initializes a new instance of the <see cref="Gray8"/> struct.
/// </summary>
/// <param name="gray">The gray component</param>
public Gray8(byte gray)
{
this.PackedValue = gray;
}
public Gray8(byte gray) => this.PackedValue = gray;
/// <inheritdoc />
public byte PackedValue { get; set; }
@ -45,20 +51,13 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <summary>
/// Compares two <see cref="Gray8"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Gray8"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Gray8"/> on the right side of the operand.
/// </param>
/// <param name="left">The <see cref="Gray8"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="Gray8"/> on the right side of the operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Gray8 left, Gray8 right)
{
return left.PackedValue == right.PackedValue;
}
[MethodImpl(InliningOptions.ShortMethod)]
public static bool operator ==(Gray8 left, Gray8 right) => left.PackedValue.Equals(right.PackedValue);
/// <summary>
/// Compares two <see cref="Gray8"/> objects for equality.
@ -68,67 +67,54 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <returns>
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Gray8 left, Gray8 right)
{
return left.PackedValue != right.PackedValue;
}
[MethodImpl(InliningOptions.ShortMethod)]
public static bool operator !=(Gray8 left, Gray8 right) => !left.PackedValue.Equals(right.PackedValue);
/// <inheritdoc />
public PixelOperations<Gray8> CreatePixelOperations() => new PixelOperations<Gray8>();
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromScaledVector4(Vector4 vector)
{
this.PackFromVector4(vector);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void PackFromScaledVector4(Vector4 vector) => this.PackFromVector4(vector);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToScaledVector4()
{
var scaledGray = this.PackedValue / 255f;
return new Vector4(scaledGray, scaledGray, scaledGray, 1.0f);
}
[MethodImpl(InliningOptions.ShortMethod)]
public Vector4 ToScaledVector4() => this.ToVector4();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public void PackFromVector4(Vector4 vector)
{
this.PackedValue = Pack(vector.X, vector.Y, vector.Z);
vector *= MaxBytes;
vector += Half;
vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes);
float luminance = (vector.X * Rx) + (vector.Y * Gx) + (vector.Z * Bx);
this.PackedValue = (byte)luminance;
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public Vector4 ToVector4()
{
return new Vector4(this.PackedValue, this.PackedValue, this.PackedValue, 1.0f);
float rgb = this.PackedValue / 255F;
return new Vector4(rgb, rgb, rgb, 1F);
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromRgba32(Rgba32 source)
{
this.PackedValue = Pack(source.R, source.G, source.B);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void PackFromRgba32(Rgba32 source) => this.PackedValue = ImageMaths.GetBT709LuminanceBytes(source.R, source.G, source.B);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromArgb32(Argb32 source)
{
this.PackedValue = Pack(source.R, source.G, source.B);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void PackFromArgb32(Argb32 source) => this.PackedValue = ImageMaths.GetBT709LuminanceBytes(source.R, source.G, source.B);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromBgra32(Bgra32 source)
{
this.PackedValue = Pack(source.R, source.G, source.B);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void PackFromBgra32(Bgra32 source) => this.PackedValue = ImageMaths.GetBT709LuminanceBytes(source.R, source.G, source.B);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public void ToRgb24(ref Rgb24 dest)
{
dest.R = this.PackedValue;
@ -137,27 +123,27 @@ namespace SixLabors.ImageSharp.PixelFormats
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public void ToRgba32(ref Rgba32 dest)
{
dest.R = this.PackedValue;
dest.G = this.PackedValue;
dest.B = this.PackedValue;
dest.A = 255;
dest.A = byte.MaxValue;
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public void ToArgb32(ref Argb32 dest)
{
dest.R = this.PackedValue;
dest.G = this.PackedValue;
dest.B = this.PackedValue;
dest.A = 255;
dest.A = byte.MaxValue;
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public void ToBgr24(ref Bgr24 dest)
{
dest.R = this.PackedValue;
@ -166,101 +152,91 @@ namespace SixLabors.ImageSharp.PixelFormats
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public void ToBgra32(ref Bgra32 dest)
{
dest.R = this.PackedValue;
dest.G = this.PackedValue;
dest.B = this.PackedValue;
dest.A = 255;
dest.A = byte.MaxValue;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromRgb48(Rgb48 source) =>
this.PackedValue = Pack(source.R, source.G, source.B);
[MethodImpl(InliningOptions.ShortMethod)]
public void PackFromRgb48(Rgb48 source)
=> this.PackedValue = ImageMaths.GetBT709LuminanceBytes(
ImageMaths.DownScaleFrom16BitTo8Bit(source.R),
ImageMaths.DownScaleFrom16BitTo8Bit(source.G),
ImageMaths.DownScaleFrom16BitTo8Bit(source.B));
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public void ToRgb48(ref Rgb48 dest)
{
ushort gray = (ushort)(this.PackedValue * 255);
dest.R = gray;
dest.G = gray;
dest.B = gray;
ushort luminance = ImageMaths.UpscaleFrom8BitTo16Bit(this.PackedValue);
dest.R = luminance;
dest.G = luminance;
dest.B = luminance;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromRgba64(Rgba64 source) =>
this.PackFromScaledVector4(source.ToScaledVector4());
[MethodImpl(InliningOptions.ShortMethod)]
public void PackFromRgba64(Rgba64 source)
=> this.PackedValue = ImageMaths.GetBT709LuminanceBytes(
ImageMaths.DownScaleFrom16BitTo8Bit(source.R),
ImageMaths.DownScaleFrom16BitTo8Bit(source.G),
ImageMaths.DownScaleFrom16BitTo8Bit(source.B));
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public void PackFromGray8(Gray8 source) => this.PackedValue = source.PackedValue;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromGray16(Gray16 source) => this.PackedValue = (byte)(source.PackedValue / 255);
[MethodImpl(InliningOptions.ShortMethod)]
public void PackFromGray16(Gray16 source) => this.PackedValue = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4());
[MethodImpl(InliningOptions.ShortMethod)]
public void ToRgba64(ref Rgba64 dest)
{
ushort luminance = ImageMaths.UpscaleFrom8BitTo16Bit(this.PackedValue);
dest.R = luminance;
dest.G = luminance;
dest.B = luminance;
dest.A = ushort.MaxValue;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public void ToGray8(ref Gray8 dest) => dest.PackedValue = this.PackedValue;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ToGray16(ref Gray16 dest) => dest.PackedValue = (ushort)(this.PackedValue * 255);
[MethodImpl(InliningOptions.ShortMethod)]
public void ToGray16(ref Gray16 dest) => dest.PackedValue = ImageMaths.UpscaleFrom8BitTo16Bit(this.PackedValue);
/// <summary>
/// Compares an object with the packed vector.
/// </summary>
/// <param name="obj">The object to compare.</param>
/// <returns>True if the object is equal to the packed vector.</returns>
public override bool Equals(object obj)
{
return obj is Gray8 other && this.Equals(other);
}
public override bool Equals(object obj) => obj is Gray8 other && this.Equals(other);
/// <summary>
/// Compares another <see cref="Gray8" /> packed vector with the packed vector.
/// </summary>
/// <param name="other">The Gray8 packed vector to compare.</param>
/// <returns>True if the packed vectors are equal.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Gray8 other)
{
return this.PackedValue == other.PackedValue;
}
[MethodImpl(InliningOptions.ShortMethod)]
public bool Equals(Gray8 other) => this.PackedValue.Equals(other.PackedValue);
/// <summary>
/// Gets a string representation of the packed vector.
/// </summary>
/// <returns>A string representation of the packed vector.</returns>
public override string ToString()
{
return (this.PackedValue / 255F).ToString();
}
public override string ToString() => $"Gray8({this.PackedValue}";
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public override int GetHashCode() => this.PackedValue.GetHashCode();
/// <summary>
/// Packs a <see cref="float"/> into a byte.
/// </summary>
/// <param name="r">Red value of the color to pack.</param>
/// <param name="g">Green value of the color to pack.</param>
/// <param name="b">Blue value of the color to pack.</param>
/// <returns>The <see cref="byte"/> containing the packed value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte Pack(float r, float g, float b)
{
float val = (r * Rx) + (g * Gx) + (b * Bx);
return (byte)Math.Round(val * 255);
}
}
}
Loading…
Cancel
Save