From ca61df8e64408be8e5616f4eb33dba1ccca51bdc Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 28 Nov 2018 00:51:15 +0100 Subject: [PATCH] rename KernelMap to ResizeKernelMap, introduce MosaicKernelMap, move source code --- src/ImageSharp/Common/Helpers/ImageMaths.cs | 3 +- src/ImageSharp/ImageSharp.csproj.DotSettings | 4 +- .../{ => Resamplers}/BicubicResampler.cs | 0 .../{ => Resamplers}/BoxResampler.cs | 0 .../{ => Resamplers}/CatmullRomResampler.cs | 0 .../{ => Resamplers}/HermiteResampler.cs | 0 .../{ => Resamplers}/Lanczos2Resampler.cs | 0 .../{ => Resamplers}/Lanczos3Resampler.cs | 0 .../{ => Resamplers}/Lanczos5Resampler.cs | 0 .../{ => Resamplers}/Lanczos8Resampler.cs | 0 .../MitchellNetravaliResampler.cs | 0 .../NearestNeighborResampler.cs | 0 .../{ => Resamplers}/RobidouxResampler.cs | 0 .../RobidouxSharpResampler.cs | 0 .../{ => Resamplers}/SplineResampler.cs | 0 .../{ => Resamplers}/TriangleResampler.cs | 0 .../{ => Resamplers}/WelchResampler.cs | 0 .../Transforms/{ => Resize}/ResizeKernel.cs | 22 +++--- .../Resize/ResizeKernelMap.MosaicKernelMap.cs | 46 ++++++++++++ .../ResizeKernelMap.cs} | 70 ++++++++++++------- .../{ => Resize}/ResizeProcessor.cs | 8 +-- .../KernelMapTests.ReferenceKernelMap.cs | 2 +- .../Processors/Transforms/KernelMapTests.cs | 4 +- .../Processors/Transforms/ResizeTests.cs | 1 + 24 files changed, 115 insertions(+), 45 deletions(-) rename src/ImageSharp/Processing/Processors/Transforms/{ => Resamplers}/BicubicResampler.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Resamplers}/BoxResampler.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Resamplers}/CatmullRomResampler.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Resamplers}/HermiteResampler.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Resamplers}/Lanczos2Resampler.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Resamplers}/Lanczos3Resampler.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Resamplers}/Lanczos5Resampler.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Resamplers}/Lanczos8Resampler.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Resamplers}/MitchellNetravaliResampler.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Resamplers}/NearestNeighborResampler.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Resamplers}/RobidouxResampler.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Resamplers}/RobidouxSharpResampler.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Resamplers}/SplineResampler.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Resamplers}/TriangleResampler.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Resamplers}/WelchResampler.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Resize}/ResizeKernel.cs (81%) create mode 100644 src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.MosaicKernelMap.cs rename src/ImageSharp/Processing/Processors/Transforms/{KernelMap.cs => Resize/ResizeKernelMap.cs} (69%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Resize}/ResizeProcessor.cs (98%) diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index f07ccb03b..45cb52fd9 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -5,6 +5,7 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.Primitives; namespace SixLabors.ImageSharp @@ -98,7 +99,7 @@ namespace SixLabors.ImageSharp /// /// Determine the Least Common Multiple (LCM) of two numbers. - /// TODO: This method might be useful for building a more compact + /// TODO: This method might be useful for building a more compact /// public static int LeastCommonMultiple(int a, int b) { diff --git a/src/ImageSharp/ImageSharp.csproj.DotSettings b/src/ImageSharp/ImageSharp.csproj.DotSettings index cd75f91b7..a7337240a 100644 --- a/src/ImageSharp/ImageSharp.csproj.DotSettings +++ b/src/ImageSharp/ImageSharp.csproj.DotSettings @@ -5,4 +5,6 @@ True True True - True \ No newline at end of file + True + True + True \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/BicubicResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/BicubicResampler.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/BoxResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/BoxResampler.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/CatmullRomResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/CatmullRomResampler.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/HermiteResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/HermiteResampler.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/Lanczos2Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/Lanczos2Resampler.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/Lanczos3Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/Lanczos3Resampler.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/Lanczos5Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/Lanczos5Resampler.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/Lanczos8Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/Lanczos8Resampler.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/MitchellNetravaliResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/MitchellNetravaliResampler.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/NearestNeighborResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/NearestNeighborResampler.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/RobidouxResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/RobidouxResampler.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/RobidouxSharpResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/RobidouxSharpResampler.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/SplineResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/SplineResampler.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/TriangleResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/TriangleResampler.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/WelchResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/WelchResampler.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs similarity index 81% rename from src/ImageSharp/Processing/Processors/Transforms/ResizeKernel.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index 1ce9c9c91..1183de754 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -11,18 +11,21 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Points to a collection of of weights allocated in . + /// Points to a collection of of weights allocated in . /// - internal struct ResizeKernel + internal unsafe struct ResizeKernel { + private readonly float* bufferPtr; + /// /// Initializes a new instance of the struct. /// [MethodImpl(InliningOptions.ShortMethod)] - internal ResizeKernel(int left, Memory bufferSlice) + internal ResizeKernel(int left, float* bufferPtr, int length) { this.Left = left; - this.BufferSlice = bufferSlice; + this.bufferPtr = bufferPtr; + this.Length = length; } /// @@ -30,22 +33,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public int Left { get; } - /// - /// Gets the slice of the buffer containing the weights values. - /// - public Memory BufferSlice { get; } - /// /// Gets the the length of the kernel /// - public int Length => this.BufferSlice.Length; + public int Length { get; } /// - /// Gets the span representing the portion of the that this window covers + /// Gets the span representing the portion of the that this window covers /// /// The [MethodImpl(InliningOptions.ShortMethod)] - public Span GetValues() => this.BufferSlice.Span; + public Span GetValues() => new Span(this.bufferPtr, this.Length); /// /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance. diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.MosaicKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.MosaicKernelMap.cs new file mode 100644 index 000000000..b815d05cb --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.MosaicKernelMap.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.Memory; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Contains + /// + internal partial class ResizeKernelMap + { + /// + /// Memory-optimized where repeating rows are stored only once. + /// + private sealed class MosaicKernelMap : ResizeKernelMap + { + private readonly int period; + + private readonly int cornerInterval; + + public MosaicKernelMap( + MemoryAllocator memoryAllocator, + IResampler sampler, + int sourceSize, + int destinationSize, + float ratio, + float scale, + int radius, + int period, + int cornerInterval) + : base( + memoryAllocator, + sampler, + sourceSize, + destinationSize, + (cornerInterval * 2) + period, + ratio, + scale, + radius) + { + this.cornerInterval = cornerInterval; + this.period = period; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs similarity index 69% rename from src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index 96eb97649..443db72d9 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -11,9 +12,10 @@ using SixLabors.Memory; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Holds the values in an optimized contigous memory region. + /// Provides values from an optimized, + /// contigous memory region. /// - internal class KernelMap : IDisposable + internal partial class ResizeKernelMap : IDisposable { private readonly IResampler sampler; @@ -25,11 +27,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly int radius; + private readonly MemoryHandle pinHandle; + private readonly Buffer2D data; private readonly ResizeKernel[] kernels; - private KernelMap( + private ResizeKernelMap( MemoryAllocator memoryAllocator, IResampler sampler, int sourceSize, @@ -47,16 +51,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.DestinationSize = destinationSize; int maxWidth = (radius * 2) + 1; this.data = memoryAllocator.Allocate2D(maxWidth, bufferHeight, AllocationOptions.Clean); + this.pinHandle = this.data.Memory.Pin(); this.kernels = new ResizeKernel[destinationSize]; } public int DestinationSize { get; } /// - /// Disposes instance releasing it's backing buffer. + /// Disposes instance releasing it's backing buffer. /// public void Dispose() { + this.pinHandle.Dispose(); this.data.Dispose(); } @@ -73,8 +79,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The destination size /// The source size /// The to use for buffer allocations - /// The - public static KernelMap Calculate( + /// The + public static ResizeKernelMap Calculate( IResampler sampler, int destinationSize, int sourceSize, @@ -88,25 +94,40 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms scale = 1F; } - int period = ImageMaths.LeastCommonMultiple(sourceSize, destinationSize) / sourceSize; int radius = (int)MathF.Ceiling(scale * sampler.Radius); - - var result = new KernelMap( - memoryAllocator, - sampler, - sourceSize, - destinationSize, - destinationSize, - ratio, - scale, - radius); - - result.BasicInit(); + int period = ImageMaths.LeastCommonMultiple(sourceSize, destinationSize) / sourceSize; + float center0 = (ratio - 1) * 0.5f; + int cornerInterval = (int)MathF.Ceiling((radius - center0 - 1) / ratio); + + bool useMosaic = 2 * (cornerInterval + period) < destinationSize; + + ResizeKernelMap result = useMosaic + ? new ResizeKernelMap( + memoryAllocator, + sampler, + sourceSize, + destinationSize, + destinationSize, + ratio, + scale, + radius) + : new MosaicKernelMap( + memoryAllocator, + sampler, + sourceSize, + destinationSize, + ratio, + scale, + radius, + period, + cornerInterval); + + result.Init(); return result; } - private void BasicInit() + protected virtual void Init() { for (int i = 0; i < this.DestinationSize; i++) { @@ -157,7 +178,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// Slices a weights value at the given positions. /// - private ResizeKernel CreateKernel(int destIdx, int left, int rightIdx) + private unsafe ResizeKernel CreateKernel(int destIdx, int left, int rightIdx) { int length = rightIdx - left + 1; @@ -166,10 +187,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms throw new InvalidOperationException($"Error in KernelMap.CreateKernel({destIdx},{left},{rightIdx}): left > this.data.Width"); } - int flatStartIndex = destIdx * this.data.Width; + Span rowSpan = this.data.GetRowSpan(destIdx); - Memory bufferSlice = this.data.Memory.Slice(flatStartIndex, length); - return new ResizeKernel(left, bufferSlice); + ref float rowReference = ref MemoryMarshal.GetReference(rowSpan); + float* rowPtr = (float*)Unsafe.AsPointer(ref rowReference); + return new ResizeKernel(left, rowPtr, length); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs similarity index 98% rename from src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs index 7c9d39fc5..189e21de7 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs @@ -27,8 +27,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms where TPixel : struct, IPixel { // The following fields are not immutable but are optionally created on demand. - private KernelMap horizontalKernelMap; - private KernelMap verticalKernelMap; + private ResizeKernelMap horizontalKernelMap; + private ResizeKernelMap verticalKernelMap; /// /// Initializes a new instance of the class. @@ -165,13 +165,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { // Since all image frame dimensions have to be the same we can calculate this for all frames. MemoryAllocator memoryAllocator = source.GetMemoryAllocator(); - this.horizontalKernelMap = KernelMap.Calculate( + this.horizontalKernelMap = ResizeKernelMap.Calculate( this.Sampler, this.ResizeRectangle.Width, sourceRectangle.Width, memoryAllocator); - this.verticalKernelMap = KernelMap.Calculate( + this.verticalKernelMap = ResizeKernelMap.Calculate( this.Sampler, this.ResizeRectangle.Height, sourceRectangle.Height, diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.ReferenceKernelMap.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.ReferenceKernelMap.cs index 3786ec6e3..85a930fb9 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.ReferenceKernelMap.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.ReferenceKernelMap.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms public partial class KernelMapTests { /// - /// Simplified reference implementation for functionality. + /// Simplified reference implementation for functionality. /// internal class ReferenceKernelMap { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs index 69de5dbbf..4d005576c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms IResampler resampler = TestUtils.GetResampler(resamplerName); var referenceMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize); - var kernelMap = KernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); + var kernelMap = ResizeKernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); #if DEBUG this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n"); @@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } - private static string PrintKernelMap(KernelMap kernelMap) => + private static string PrintKernelMap(ResizeKernelMap kernelMap) => PrintKernelMap(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i)); private static string PrintKernelMap(ReferenceKernelMap kernelMap) => diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 42cb083f1..839d26e71 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -58,6 +58,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } + // TODO: Merge with the previous theory + add test images [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba32, nameof(KnownResamplers.Bicubic), 1)] [WithTestPatternImages(100, 100, PixelTypes.Rgba32, nameof(KnownResamplers.Bicubic), 10)]