diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 7308a9d..9195bfb 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -1,25 +1,34 @@ -name: Tests +name: build and test on: push: - branches: [ master ] pull_request: - branches: [ master ] + branches: [ main ] + paths: + - '**.cs' + - '**.csproj' + +env: + DOTNET_VERSION: '9.0.x' # The .NET SDK version to use jobs: - build: + build-and-test: + name: build-and-test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 with: - dotnet-version: 3.1.101 + dotnet-version: ${{ env.DOTNET_VERSION }} + - name: Install dependencies run: dotnet restore + - name: Build run: dotnet build --configuration Release --no-restore + - name: Test - run: dotnet test --no-restore --verbosity normal + run: dotnet test --no-restore --verbosity normal \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4ce6fdd..041b4f3 100644 --- a/.gitignore +++ b/.gitignore @@ -337,4 +337,8 @@ ASALocalRun/ .localhistory/ # BeatPulse healthcheck temp database -healthchecksdb \ No newline at end of file +healthchecksdb + +# Rider DotSettings +*.DotSettings.user +*.DotSettings diff --git a/BCnEnc.Net/AssemblyInfo.cs b/BCnEnc.Net/AssemblyInfo.cs index 6d23fdb..aea5d1b 100644 --- a/BCnEnc.Net/AssemblyInfo.cs +++ b/BCnEnc.Net/AssemblyInfo.cs @@ -1,3 +1,4 @@ -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("BCnEncTests")] +[assembly: InternalsVisibleTo("BCnEncoder.Net.TextureFormats")] diff --git a/BCnEnc.Net/BCnEncoder.csproj b/BCnEnc.Net/BCnEncoder.csproj index d7cd509..6c206e7 100644 --- a/BCnEnc.Net/BCnEncoder.csproj +++ b/BCnEnc.Net/BCnEncoder.csproj @@ -1,8 +1,8 @@ - netstandard2.1 - + net9.0 + true true snupkg @@ -47,7 +47,7 @@ Supported formats are: - + diff --git a/BCnEnc.Net/Decoder/BcBlockDecoder.cs b/BCnEnc.Net/Decoder/BcBlockDecoder.cs index 7706639..87c8e08 100644 --- a/BCnEnc.Net/Decoder/BcBlockDecoder.cs +++ b/BCnEnc.Net/Decoder/BcBlockDecoder.cs @@ -5,31 +5,31 @@ using System.Threading; using System.Threading.Tasks; using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Decoder { - internal interface IBcBlockDecoder where T : unmanaged + internal interface IBcBlockDecoder : IBcDecoder { - T[] Decode(ReadOnlyMemory data, OperationContext context); - T DecodeBlock(ReadOnlySpan data); + RawBlock4X4RgbaFloat[] Decode(ReadOnlyMemory data, OperationContext context); + RawBlock4X4RgbaFloat DecodeBlock(ReadOnlySpan data); } - internal abstract class BaseBcBlockDecoder : IBcBlockDecoder where T : unmanaged where TBlock : unmanaged + internal abstract class BaseBcBlockDecoder : IBcBlockDecoder + where TEncodedBlock : unmanaged { private static readonly object lockObj = new object(); - public TBlock[] Decode(ReadOnlyMemory data, OperationContext context) + public RawBlock4X4RgbaFloat[] Decode(ReadOnlyMemory data, OperationContext context) { - - if (data.Length % Unsafe.SizeOf() != 0) + if (data.Length % Unsafe.SizeOf() != 0) { throw new InvalidDataException("Given data does not align with the block length."); } - var blockCount = data.Length / Unsafe.SizeOf(); - var output = new TBlock[blockCount]; + var blockCount = data.Length / Unsafe.SizeOf(); + var output = new RawBlock4X4RgbaFloat[blockCount]; - var currentBlocks = 0; if (context.IsParallel) { var options = new ParallelOptions @@ -39,76 +39,99 @@ public TBlock[] Decode(ReadOnlyMemory data, OperationContext context) }; Parallel.For(0, blockCount, options, i => { - var encodedBlocks = MemoryMarshal.Cast(data.Span); + var encodedBlocks = MemoryMarshal.Cast(data.Span); output[i] = DecodeBlock(encodedBlocks[i]); + output[i].ColorConvert(context.ColorConversionMode); if (context.Progress != null) { lock (lockObj) { - context.Progress.Report(++currentBlocks); + context.Progress.Report(1); } } }); } else { - var encodedBlocks = MemoryMarshal.Cast(data.Span); + var encodedBlocks = MemoryMarshal.Cast(data.Span); for (var i = 0; i < blockCount; i++) { context.CancellationToken.ThrowIfCancellationRequested(); output[i] = DecodeBlock(encodedBlocks[i]); + output[i].ColorConvert(context.ColorConversionMode); - context.Progress?.Report(++currentBlocks); + context.Progress?.Report(1); } } + + return output; } - public TBlock DecodeBlock(ReadOnlySpan data) + public RawBlock4X4RgbaFloat DecodeBlock(ReadOnlySpan data) { - var encodedBlock = MemoryMarshal.Cast(data)[0]; + var encodedBlock = MemoryMarshal.Cast(data)[0]; return DecodeBlock(encodedBlock); } - protected abstract TBlock DecodeBlock(T block); + protected abstract RawBlock4X4RgbaFloat DecodeBlock(TEncodedBlock block); + + public byte[] Decode(ReadOnlyMemory data, int width, int height, OperationContext context) + { + byte[] output = new byte[width * height * Unsafe.SizeOf()]; + Span colors = MemoryMarshal.Cast(output); + + ImageToBlocks.ColorsFromRawBlocks(Decode(data, context), colors, width, height); + + return output; + } + + public ColorRgbaFloat[] DecodeColor(ReadOnlyMemory data, int width, int height, OperationContext context) + { + ColorRgbaFloat[] output = new ColorRgbaFloat[width * height]; + + ImageToBlocks.ColorsFromRawBlocks(Decode(data, context), output, width, height); + + return output; + } } - internal class Bc1NoAlphaDecoder : BaseBcBlockDecoder + internal class Bc1NoAlphaDecoder : BaseBcBlockDecoder { - protected override RawBlock4X4Rgba32 DecodeBlock(Bc1Block block) + protected override RawBlock4X4RgbaFloat DecodeBlock(Bc1Block block) { - return block.Decode(false); + return block.Decode(true, false); } } - internal class Bc1ADecoder : BaseBcBlockDecoder + internal class Bc1ADecoder : BaseBcBlockDecoder { - protected override RawBlock4X4Rgba32 DecodeBlock(Bc1Block block) + protected override RawBlock4X4RgbaFloat DecodeBlock(Bc1Block block) { - return block.Decode(true); + return block.Decode(true, true); } } - internal class Bc2Decoder : BaseBcBlockDecoder + internal class Bc2Decoder : BaseBcBlockDecoder { - protected override RawBlock4X4Rgba32 DecodeBlock(Bc2Block block) + protected override RawBlock4X4RgbaFloat DecodeBlock(Bc2Block block) { return block.Decode(); } } - internal class Bc3Decoder : BaseBcBlockDecoder + internal class Bc3Decoder : BaseBcBlockDecoder { - protected override RawBlock4X4Rgba32 DecodeBlock(Bc3Block block) + protected override RawBlock4X4RgbaFloat DecodeBlock(Bc3Block block) { return block.Decode(); } } - internal class Bc4Decoder : BaseBcBlockDecoder + internal class Bc4Decoder : BaseBcBlockDecoder { private readonly ColorComponent component; @@ -117,72 +140,74 @@ public Bc4Decoder(ColorComponent component) this.component = component; } - protected override RawBlock4X4Rgba32 DecodeBlock(Bc4Block block) + protected override RawBlock4X4RgbaFloat DecodeBlock(Bc4Block block) { return block.Decode(component); } } - internal class Bc5Decoder : BaseBcBlockDecoder + internal class Bc5Decoder : BaseBcBlockDecoder { private readonly ColorComponent component1; private readonly ColorComponent component2; + private readonly ColorComponent componentCalculated; - public Bc5Decoder(ColorComponent component1, ColorComponent component2) + public Bc5Decoder(ColorComponent component1, ColorComponent component2, ColorComponent componentCalculated) { this.component1 = component1; this.component2 = component2; + this.componentCalculated = componentCalculated; } - protected override RawBlock4X4Rgba32 DecodeBlock(Bc5Block block) + protected override RawBlock4X4RgbaFloat DecodeBlock(Bc5Block block) { - return block.Decode(component1, component2); + return block.Decode(component1, component2, componentCalculated); } } - internal class Bc6UDecoder : BaseBcBlockDecoder + internal class Bc6UDecoder : BaseBcBlockDecoder { - protected override RawBlock4X4RgbFloat DecodeBlock(Bc6Block block) + protected override RawBlock4X4RgbaFloat DecodeBlock(Bc6Block block) { return block.Decode(false); } } - internal class Bc6SDecoder : BaseBcBlockDecoder + internal class Bc6SDecoder : BaseBcBlockDecoder { - protected override RawBlock4X4RgbFloat DecodeBlock(Bc6Block block) + protected override RawBlock4X4RgbaFloat DecodeBlock(Bc6Block block) { return block.Decode(true); } } - internal class Bc7Decoder : BaseBcBlockDecoder + internal class Bc7Decoder : BaseBcBlockDecoder { - protected override RawBlock4X4Rgba32 DecodeBlock(Bc7Block block) + protected override RawBlock4X4RgbaFloat DecodeBlock(Bc7Block block) { return block.Decode(); } } - internal class AtcDecoder : BaseBcBlockDecoder + internal class AtcDecoder : BaseBcBlockDecoder { - protected override RawBlock4X4Rgba32 DecodeBlock(AtcBlock block) + protected override RawBlock4X4RgbaFloat DecodeBlock(AtcBlock block) { return block.Decode(); } } - internal class AtcExplicitAlphaDecoder : BaseBcBlockDecoder + internal class AtcExplicitAlphaDecoder : BaseBcBlockDecoder { - protected override RawBlock4X4Rgba32 DecodeBlock(AtcExplicitAlphaBlock block) + protected override RawBlock4X4RgbaFloat DecodeBlock(AtcExplicitAlphaBlock block) { return block.Decode(); } } - internal class AtcInterpolatedAlphaDecoder : BaseBcBlockDecoder + internal class AtcInterpolatedAlphaDecoder : BaseBcBlockDecoder { - protected override RawBlock4X4Rgba32 DecodeBlock(AtcInterpolatedAlphaBlock block) + protected override RawBlock4X4RgbaFloat DecodeBlock(AtcInterpolatedAlphaBlock block) { return block.Decode(); } diff --git a/BCnEnc.Net/Decoder/BcDecoder.cs b/BCnEnc.Net/Decoder/BcDecoder.cs index 6c37ad9..2b7caf3 100644 --- a/BCnEnc.Net/Decoder/BcDecoder.cs +++ b/BCnEnc.Net/Decoder/BcDecoder.cs @@ -2,25 +2,19 @@ using BCnEncoder.Shared; using System; using System.IO; -using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using BCnEncoder.Shared.ImageFiles; -using Microsoft.Toolkit.HighPerformance; +using BCnEncoder.Shared.Colors; +using CommunityToolkit.HighPerformance; namespace BCnEncoder.Decoder { /// - /// Decodes compressed files into Rgba Format. + /// Decodes block-compressed formats into Rgba. /// public class BcDecoder { - /// - /// The input options of the decoder. - /// - public DecoderInputOptions InputOptions { get; } = new DecoderInputOptions(); - /// /// The options for the decoder. /// @@ -30,284 +24,35 @@ public class BcDecoder /// The output options of the decoder. /// public DecoderOutputOptions OutputOptions { get; } = new DecoderOutputOptions(); - #region LDR - #region Async Api - - /// - /// Decode a single encoded image from raw bytes. - /// This method will read the expected amount of bytes from the given input stream and decode it. - /// Make sure there is no file header information left in the stream before the encoded data. - /// - /// The stream containing the encoded data. - /// The Format the encoded data is in. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task DecodeRawAsync(Stream inputStream, CompressionFormat format, int pixelWidth, int pixelHeight, CancellationToken token = default) - { - var dataArray = new byte[GetBufferSize(format, pixelWidth, pixelHeight)]; - inputStream.Read(dataArray, 0, dataArray.Length); - - return Task.Run(() => DecodeRawInternal(dataArray, pixelWidth, pixelHeight, format, token), token); - } - - /// - /// Decode a single encoded image from raw bytes. - /// - /// The containing the encoded data. - /// The Format the encoded data is in. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task DecodeRawAsync(ReadOnlyMemory input, CompressionFormat format, int pixelWidth, int pixelHeight, CancellationToken token = default) - { - return Task.Run(() => DecodeRawInternal(input, pixelWidth, pixelHeight, format, token), token); - } - - /// - /// Decode the main image from a Ktx file. - /// - /// The loaded Ktx file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task DecodeAsync(KtxFile file, CancellationToken token = default) - { - return Task.Run(() => DecodeInternal(file, false, token)[0], token); - } - - /// - /// Decode all available mipmaps from a Ktx file. - /// - /// The loaded Ktx file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task DecodeAllMipMapsAsync(KtxFile file, CancellationToken token = default) - { - return Task.Run(() => DecodeInternal(file, true, token), token); - } - - /// - /// Decode the main image from a Dds file. - /// - /// The loaded Dds file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task DecodeAsync(DdsFile file, CancellationToken token = default) - { - return Task.Run(() => DecodeInternal(file, false, token)[0], token); - } - - /// - /// Decode all available mipmaps from a Dds file. - /// - /// The loaded Dds file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task DecodeAllMipMapsAsync(DdsFile file, CancellationToken token = default) - { - return Task.Run(() => DecodeInternal(file, true, token), token); - } - - /// - /// Decode a single encoded image from raw bytes. - /// This method will read the expected amount of bytes from the given input stream and decode it. - /// Make sure there is no file header information left in the stream before the encoded data. - /// - /// The stream containing the raw encoded data. - /// The Format the encoded data is in. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task> DecodeRaw2DAsync(Stream inputStream, int pixelWidth, int pixelHeight, CompressionFormat format, CancellationToken token = default) - { - var dataArray = new byte[GetBufferSize(format, pixelWidth, pixelHeight)]; - inputStream.Read(dataArray, 0, dataArray.Length); - - return Task.Run(() => DecodeRawInternal(dataArray, pixelWidth, pixelHeight, format, token) - .AsMemory().AsMemory2D(pixelHeight, pixelWidth), token); - } - - /// - /// Decode a single encoded image from raw bytes. - /// - /// The containing the encoded data. - /// The Format the encoded data is in. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task> DecodeRaw2DAsync(ReadOnlyMemory input, int pixelWidth, int pixelHeight, CompressionFormat format, CancellationToken token = default) - { - return Task.Run(() => DecodeRawInternal(input, pixelWidth, pixelHeight, format, token) - .AsMemory().AsMemory2D(pixelHeight, pixelWidth), token); - } - - /// - /// Read a Ktx or Dds file from a stream and decode the main image from it. - /// The type of file will be detected automatically. - /// - /// The stream containing a Ktx or Dds file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task> Decode2DAsync(Stream inputStream, CancellationToken token = default) - { - return Task.Run(() => DecodeFromStreamInternal2D(inputStream, false, token)[0], token); - } - - /// - /// Read a Ktx or Dds file from a stream and decode all available mipmaps from it. - /// The type of file will be detected automatically. - /// - /// The stream containing a Ktx or Dds file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task[]> DecodeAllMipMaps2DAsync(Stream inputStream, CancellationToken token = default) - { - return Task.Run(() => DecodeFromStreamInternal2D(inputStream, false, token), token); - } - - /// - /// Decode the main image from a Ktx file. - /// - /// The loaded Ktx file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task> Decode2DAsync(KtxFile file, CancellationToken token = default) - { - return Task.Run(() => DecodeInternal(file, false, token)[0] - .AsMemory().AsMemory2D((int)file.header.PixelHeight, (int)file.header.PixelWidth), token); - } - - /// - /// Decode all available mipmaps from a Ktx file. - /// - /// The loaded Ktx file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task[]> DecodeAllMipMaps2DAsync(KtxFile file, CancellationToken token = default) - { - return Task.Run(() => - { - var decoded = DecodeInternal(file, true, token); - var mem2Ds = new Memory2D[decoded.Length]; - for (var i = 0; i < decoded.Length; i++) - { - var mip = file.MipMaps[i]; - mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width); - } - return mem2Ds; - }, token); - } - - /// - /// Decode the main image from a Dds file. - /// - /// The loaded Dds file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task> Decode2DAsync(DdsFile file, CancellationToken token = default) - { - return Task.Run(() => DecodeInternal(file, false, token)[0] - .AsMemory().AsMemory2D((int)file.header.dwHeight, (int)file.header.dwWidth), token); - } - - /// - /// Decode all available mipmaps from a Dds file. - /// - /// The loaded Dds file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task[]> DecodeAllMipMaps2DAsync(DdsFile file, CancellationToken token = default) - { - return Task.Run(() => - { - var decoded = DecodeInternal(file, true, token); - var mem2Ds = new Memory2D[decoded.Length]; - for (var i = 0; i < decoded.Length; i++) - { - var mip = file.Faces[0].MipMaps[i]; - mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width); - } - return mem2Ds; - }, token); - } - - #endregion - - #region Sync API - - /// - /// Decode a single encoded image from raw bytes. - /// This method will read the expected amount of bytes from the given input stream and decode it. - /// Make sure there is no file header information left in the stream before the encoded data. - /// - /// The stream containing the raw encoded data. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The Format the encoded data is in. - /// The decoded image. - public ColorRgba32[] DecodeRaw(Stream inputStream, int pixelWidth, int pixelHeight, CompressionFormat format) - { - var dataArray = new byte[GetBufferSize(format, pixelWidth, pixelHeight)]; - inputStream.Read(dataArray, 0, dataArray.Length); - return DecodeRaw(dataArray, pixelWidth, pixelHeight, format); - } + public BcDecoder() { } - /// - /// Decode a single encoded image from raw bytes. - /// - /// The byte array containing the raw encoded data. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The Format the encoded data is in. - /// The decoded image. - public ColorRgba32[] DecodeRaw(byte[] input, int pixelWidth, int pixelHeight, CompressionFormat format) + public BcDecoder(DecoderOptions options) { - return DecodeRawInternal(input, pixelWidth, pixelHeight, format, default); + Options = options; } - /// - /// Decode the main image from a Ktx file. - /// - /// The loaded Ktx file. - /// The decoded image. - public ColorRgba32[] Decode(KtxFile file) + public BcDecoder(DecoderOutputOptions outputOptions) { - return DecodeInternal(file, false, default)[0]; + OutputOptions = outputOptions; } - /// - /// Decode all available mipmaps from a Ktx file. - /// - /// The loaded Ktx file. - /// An array of decoded images. - public ColorRgba32[][] DecodeAllMipMaps(KtxFile file) + public BcDecoder(DecoderOptions options, DecoderOutputOptions outputOptions) { - return DecodeInternal(file, true, default); + Options = options; + OutputOptions = outputOptions; } - /// - /// Decode the main image from a Dds file. - /// - /// The loaded Dds file. - /// The decoded image. - public ColorRgba32[] Decode(DdsFile file) + /// + public Task DecodeAsync(BCnTextureData texture, CompressionFormat outputFormat, CancellationToken token = default) { - return DecodeInternal(file, false, default)[0]; + return Task.Run(() => DecodeInternal(texture, outputFormat, token), token); } - /// - /// Decode all available mipmaps from a Dds file. - /// - /// The loaded Dds file. - /// An array of decoded images. - public ColorRgba32[][] DecodeAllMipMaps(DdsFile file) + /// + public BCnTextureData Decode(BCnTextureData texture, CompressionFormat outputFormat) { - return DecodeInternal(file, true, default); + return DecodeInternal(texture, outputFormat, CancellationToken.None); } /// @@ -315,1294 +60,385 @@ public ColorRgba32[][] DecodeAllMipMaps(DdsFile file) /// This method will read the expected amount of bytes from the given input stream and decode it. /// Make sure there is no file header information left in the stream before the encoded data. /// - /// The stream containing the encoded data. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The Format the encoded data is in. - /// The decoded image. - public Memory2D DecodeRaw2D(Stream inputStream, int pixelWidth, int pixelHeight, CompressionFormat format) - { - var dataArray = new byte[GetBufferSize(format, pixelWidth, pixelHeight)]; - inputStream.Read(dataArray, 0, dataArray.Length); - - var decoded = DecodeRaw(dataArray, pixelWidth, pixelHeight, format); - return decoded.AsMemory().AsMemory2D(pixelHeight, pixelWidth); - } - - /// - /// Decode a single encoded image from raw bytes. - /// - /// The byte array containing the raw encoded data. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The Format the encoded data is in. - /// The decoded image. - public Memory2D DecodeRaw2D(byte[] input, int pixelWidth, int pixelHeight, CompressionFormat format) + public async Task DecodeRawAsync(Stream inputStream, Memory output, int pixelWidth, int pixelHeight, CompressionFormat inputFormat, CompressionFormat outputFormat, CancellationToken token = default) + where TOut : unmanaged { - var decoded = DecodeRawInternal(input, pixelWidth, pixelHeight, format, default); - return decoded.AsMemory().AsMemory2D(pixelHeight, pixelWidth); - } - - /// - /// Read a Ktx or Dds file from a stream and decode the main image from it. - /// The type of file will be detected automatically. - /// - /// The stream containing a Ktx or Dds file. - /// The decoded image. - public Memory2D Decode2D(Stream inputStream) - { - return DecodeFromStreamInternal2D(inputStream, false, default)[0]; - } + var dataArray = new byte[GetBufferSize(inputFormat, pixelWidth, pixelHeight, 1)]; + await inputStream.ReadExactlyAsync(dataArray, 0, dataArray.Length, token); - /// - /// Read a Ktx or Dds file from a stream and decode all available mipmaps from it. - /// The type of file will be detected automatically. - /// - /// The stream containing a Ktx or Dds file. - /// An array of decoded images. - public Memory2D[] DecodeAllMipMaps2D(Stream inputStream) - { - return DecodeFromStreamInternal2D(inputStream, true, default); + await Task.Run(() => DecodeRawInternal(dataArray, output.Cast(), pixelWidth, pixelHeight, 1, inputFormat, outputFormat, token), token); } - /// - /// Decode the main image from a Ktx file. - /// - /// The loaded Ktx file. - /// The decoded image. - public Memory2D Decode2D(KtxFile file) + public Task DecodeRawAsync(ReadOnlyMemory input, Memory output, int pixelWidth, int pixelHeight, CompressionFormat inputFormat, CompressionFormat outputFormat, CancellationToken token = default) + where TOut : unmanaged { - return DecodeInternal(file, false, default)[0].AsMemory().AsMemory2D((int)file.header.PixelHeight, (int)file.header.PixelWidth); + return Task.Run(() => DecodeRawInternal(input, output.Cast(), pixelWidth, pixelHeight, 1, inputFormat, outputFormat, token), token); } - /// - /// Decode all available mipmaps from a Ktx file. - /// - /// The loaded Ktx file. - /// An array of decoded images. - public Memory2D[] DecodeAllMipMaps2D(KtxFile file) + public async Task DecodeRawAsync(Stream inputStream, int pixelWidth, int pixelHeight, CompressionFormat inputFormat, CompressionFormat outputFormat, CancellationToken token = default) + where TOut : unmanaged { - var decoded = DecodeInternal(file, true, default); - var mem2Ds = new Memory2D[decoded.Length]; - for (var i = 0; i < decoded.Length; i++) - { - var mip = file.MipMaps[i]; - mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width); - } - return mem2Ds; - } + var dataArray = new byte[GetBufferSize(inputFormat, pixelWidth, pixelHeight, 1)]; + await inputStream.ReadExactlyAsync(dataArray, 0, dataArray.Length, token); - /// - /// Decode the main image from a Dds file. - /// - /// The loaded Dds file. - /// The decoded image. - public Memory2D Decode2D(DdsFile file) - { - return DecodeInternal(file, false, default)[0].AsMemory().AsMemory2D((int)file.header.dwHeight, (int)file.header.dwWidth); - } + var output = AllocateOutputBuffer(pixelWidth, pixelHeight, 1, outputFormat); - /// - /// Decode all available mipmaps from a Dds file. - /// - /// The loaded Dds file. - /// An array of decoded images. - public Memory2D[] DecodeAllMipMaps2D(DdsFile file) - { - var decoded = DecodeInternal(file, true, default); - var mem2Ds = new Memory2D[decoded.Length]; - for (var i = 0; i < decoded.Length; i++) - { - var mip = file.Faces[0].MipMaps[i]; - mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width); - } - return mem2Ds; - } + await Task.Run(() => DecodeRawInternal(dataArray, output.AsMemory().Cast(), pixelWidth, pixelHeight, 1, inputFormat, outputFormat, token), token); - /// - /// Decode a single block from raw bytes and return it as a . - /// Input Span size needs to equal the block size. - /// To get the block size (in bytes) of the compression format used, see . - /// - /// The encoded block in bytes. - /// The compression format used. - /// The decoded 4x4 block. - public Memory2D DecodeBlock(ReadOnlySpan blockData, CompressionFormat format) - { - var output = new ColorRgba32[4, 4]; - DecodeBlockInternal(blockData, format, output); return output; } - /// - /// Decode a single block from raw bytes and write it to the given output span. - /// Output span size must be exactly 4x4 and input Span size needs to equal the block size. - /// To get the block size (in bytes) of the compression format used, see . - /// - /// The encoded block in bytes. - /// The compression format used. - /// The destination span of the decoded data. - public void DecodeBlock(ReadOnlySpan blockData, CompressionFormat format, Span2D outputSpan) - { - if (outputSpan.Width != 4 || outputSpan.Height != 4) - { - throw new ArgumentException($"Single block decoding needs an output span of exactly 4x4"); - } - DecodeBlockInternal(blockData, format, outputSpan); - } - - /// - /// Decode a single block from a stream and write it to the given output span. - /// Output span size must be exactly 4x4. - /// - /// The stream to read encoded blocks from. - /// The compression format used. - /// The destination span of the decoded data. - /// The number of bytes read from the stream. Zero (0) if reached the end of stream. - public int DecodeBlock(Stream inputStream, CompressionFormat format, Span2D outputSpan) - { - if (outputSpan.Width != 4 || outputSpan.Height != 4) - { - throw new ArgumentException($"Single block decoding needs an output span of exactly 4x4"); - } - - Span input = stackalloc byte[16]; - input = input.Slice(0, GetBlockSize(format)); - - var bytesRead = inputStream.Read(input); - - if (bytesRead == 0) - { - return 0; //End of stream - } - - if (bytesRead != input.Length) - { - throw new Exception("Input stream does not have enough data available for a full block."); - } - - DecodeBlockInternal(input, format, outputSpan); - return bytesRead; - } - - /// - /// Check whether a file is encoded in a supported format. - /// - /// The loaded ktx file to check - /// If the format of the file is one of the supported formats. - public bool IsSupportedFormat(KtxFile file) - { - return GetCompressionFormat(file.header.GlInternalFormat) != CompressionFormat.Unknown; - } - - /// - /// Check whether a file is encoded in a supported format. - /// - /// The loaded dds file to check - /// If the format of the file is one of the supported formats. - public bool IsSupportedFormat(DdsFile file) - { - return GetCompressionFormat(file) != CompressionFormat.Unknown; - } - - /// - /// Gets the format of the file. - /// - /// The loaded ktx file to check - /// The of the file. - public CompressionFormat GetFormat(KtxFile file) - { - return GetCompressionFormat(file.header.GlInternalFormat); - } - - /// - /// Gets the format of the file. - /// - /// The loaded dds file to check - /// The of the file. - public CompressionFormat GetFormat(DdsFile file) + public async Task DecodeRawAsync(ReadOnlyMemory input, int pixelWidth, int pixelHeight, CompressionFormat inputFormat, CompressionFormat outputFormat, CancellationToken token = default) + where TOut : unmanaged { - return GetCompressionFormat(file); - } - - - #endregion - #endregion + var output = AllocateOutputBuffer(pixelWidth, pixelHeight, 1, outputFormat); - #region HDR - #region Async Api - - /// - /// Decode a single encoded image from raw bytes. - /// This method will read the expected amount of bytes from the given input stream and decode it. - /// Make sure there is no file header information left in the stream before the encoded data. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The stream containing the encoded data. - /// The Format the encoded data is in. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task DecodeRawHdrAsync(Stream inputStream, CompressionFormat format, int pixelWidth, int pixelHeight, CancellationToken token = default) - { - var dataArray = new byte[GetBufferSize(format, pixelWidth, pixelHeight)]; - inputStream.Read(dataArray, 0, dataArray.Length); + await Task.Run(() => DecodeRawInternal(input, output.AsMemory().Cast(), pixelWidth, pixelHeight, 1, inputFormat, outputFormat, token), token); - return Task.Run(() => DecodeRawInternalHdr(dataArray, pixelWidth, pixelHeight, format, token), token); + return output; } - /// - /// Decode a single encoded image from raw bytes. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The containing the encoded data. - /// The Format the encoded data is in. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task DecodeRawHdrAsync(ReadOnlyMemory input, CompressionFormat format, int pixelWidth, int pixelHeight, CancellationToken token = default) + public void DecodeRaw(Stream inputStream, Memory output, int pixelWidth, int pixelHeight, CompressionFormat inputFormat, CompressionFormat outputFormat) + where TOut : unmanaged { - return Task.Run(() => DecodeRawInternalHdr(input, pixelWidth, pixelHeight, format, token), token); - } + var dataArray = new byte[GetBufferSize(inputFormat, pixelWidth, pixelHeight, 1)]; + inputStream.ReadExactly(dataArray, 0, dataArray.Length); - /// - /// Decode the main image from a Ktx file. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The loaded Ktx file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task DecodeHdrAsync(KtxFile file, CancellationToken token = default) - { - return Task.Run(() => DecodeInternalHdr(file, false, token)[0], token); + DecodeRawInternal(dataArray, output.Cast(), pixelWidth, pixelHeight, 1, inputFormat, outputFormat, CancellationToken.None); } - /// - /// Decode all available mipmaps from a Ktx file. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The loaded Ktx file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task DecodeAllMipMapsHdrAsync(KtxFile file, CancellationToken token = default) + public void DecodeRaw(ReadOnlyMemory input, Memory output, int pixelWidth, int pixelHeight, CompressionFormat inputFormat, CompressionFormat outputFormat) + where TOut : unmanaged { - return Task.Run(() => DecodeInternalHdr(file, true, token), token); + DecodeRawInternal(input, output.Cast(), pixelWidth, pixelHeight, 1,inputFormat, outputFormat, CancellationToken.None); } - /// - /// Decode the main image from a Dds file. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The loaded Dds file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task DecodeHdrAsync(DdsFile file, CancellationToken token = default) + public TOut[] DecodeRaw(Stream inputStream, int pixelWidth, int pixelHeight, CompressionFormat inputFormat, CompressionFormat outputFormat) + where TOut : unmanaged { - return Task.Run(() => DecodeInternalHdr(file, false, token)[0], token); - } + var dataArray = new byte[GetBufferSize(inputFormat, pixelWidth, pixelHeight, 1)]; + inputStream.ReadExactly(dataArray, 0, dataArray.Length); - /// - /// Decode all available mipmaps from a Dds file. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The loaded Dds file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task DecodeAllMipMapsHdrAsync(DdsFile file, CancellationToken token = default) - { - return Task.Run(() => DecodeInternalHdr(file, true, token), token); - } + var output = AllocateOutputBuffer(pixelWidth, pixelHeight, 1, outputFormat); - /// - /// Decode a single encoded image from raw bytes. - /// This method will read the expected amount of bytes from the given input stream and decode it. - /// Make sure there is no file header information left in the stream before the encoded data. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The stream containing the raw encoded data. - /// The Format the encoded data is in. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task> DecodeRawHdr2DAsync(Stream inputStream, int pixelWidth, int pixelHeight, CompressionFormat format, CancellationToken token = default) - { - var dataArray = new byte[GetBufferSize(format, pixelWidth, pixelHeight)]; - inputStream.Read(dataArray, 0, dataArray.Length); + DecodeRawInternal(dataArray, output.AsMemory().Cast(), pixelWidth, pixelHeight, 1, inputFormat, outputFormat, CancellationToken.None); - return Task.Run(() => DecodeRawInternalHdr(dataArray, pixelWidth, pixelHeight, format, token) - .AsMemory().AsMemory2D(pixelHeight, pixelWidth), token); + return output; } - /// - /// Decode a single encoded image from raw bytes. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The containing the encoded data. - /// The Format the encoded data is in. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task> DecodeRawHdr2DAsync(ReadOnlyMemory input, int pixelWidth, int pixelHeight, CompressionFormat format, CancellationToken token = default) - { - return Task.Run(() => DecodeRawInternalHdr(input, pixelWidth, pixelHeight, format, token) - .AsMemory().AsMemory2D(pixelHeight, pixelWidth), token); - } - - /// - /// Read a Ktx or Dds file from a stream and decode the main image from it. - /// The type of file will be detected automatically. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The stream containing a Ktx or Dds file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task> DecodeHdr2DAsync(Stream inputStream, CancellationToken token = default) - { - return Task.Run(() => DecodeFromStreamInternalHdr2D(inputStream, false, token)[0], token); - } - - /// - /// Read a Ktx or Dds file from a stream and decode all available mipmaps from it. - /// The type of file will be detected automatically. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The stream containing a Ktx or Dds file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task[]> DecodeAllMipMapsHdr2DAsync(Stream inputStream, CancellationToken token = default) - { - return Task.Run(() => DecodeFromStreamInternalHdr2D(inputStream, false, token), token); - } - - /// - /// Decode the main image from a Ktx file. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The loaded Ktx file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task> DecodeHdr2DAsync(KtxFile file, CancellationToken token = default) - { - return Task.Run(() => DecodeInternalHdr(file, false, token)[0] - .AsMemory().AsMemory2D((int)file.header.PixelHeight, (int)file.header.PixelWidth), token); - } - - /// - /// Decode all available mipmaps from a Ktx file. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The loaded Ktx file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task[]> DecodeAllMipMapsHdr2DAsync(KtxFile file, CancellationToken token = default) - { - return Task.Run(() => - { - var decoded = DecodeInternalHdr(file, true, token); - var mem2Ds = new Memory2D[decoded.Length]; - for (var i = 0; i < decoded.Length; i++) - { - var mip = file.MipMaps[i]; - mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width); - } - return mem2Ds; - }, token); - } - - /// - /// Decode the main image from a Dds file. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The loaded Dds file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task> DecodeHdr2DAsync(DdsFile file, CancellationToken token = default) - { - return Task.Run(() => DecodeInternalHdr(file, false, token)[0] - .AsMemory().AsMemory2D((int)file.header.dwHeight, (int)file.header.dwWidth), token); - } - - /// - /// Decode all available mipmaps from a Dds file. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The loaded Dds file. - /// The cancellation token for this asynchronous operation. - /// The awaitable operation to retrieve the decoded image. - public Task[]> DecodeAllMipMapsHdr2DAsync(DdsFile file, CancellationToken token = default) - { - return Task.Run(() => - { - var decoded = DecodeInternalHdr(file, true, token); - var mem2Ds = new Memory2D[decoded.Length]; - for (var i = 0; i < decoded.Length; i++) - { - var mip = file.Faces[0].MipMaps[i]; - mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width); - } - return mem2Ds; - }, token); - } - - #endregion - - #region Sync API - - /// - /// Decode a single encoded image from raw bytes. - /// This method will read the expected amount of bytes from the given input stream and decode it. - /// Make sure there is no file header information left in the stream before the encoded data. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The stream containing the raw encoded data. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The Format the encoded data is in. - /// The decoded image. - public ColorRgbFloat[] DecodeRawHdr(Stream inputStream, int pixelWidth, int pixelHeight, CompressionFormat format) - { - var dataArray = new byte[GetBufferSize(format, pixelWidth, pixelHeight)]; - inputStream.Read(dataArray, 0, dataArray.Length); - - return DecodeRawHdr(dataArray, pixelWidth, pixelHeight, format); - } - - /// - /// Decode a single encoded image from raw bytes. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The byte array containing the raw encoded data. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The Format the encoded data is in. - /// The decoded image. - public ColorRgbFloat[] DecodeRawHdr(byte[] input, int pixelWidth, int pixelHeight, CompressionFormat format) - { - return DecodeRawInternalHdr(input, pixelWidth, pixelHeight, format, default); - } - - /// - /// Decode the main image from a Ktx file. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The loaded Ktx file. - /// The decoded image. - public ColorRgbFloat[] DecodeHdr(KtxFile file) - { - return DecodeInternalHdr(file, false, default)[0]; - } - - /// - /// Decode all available mipmaps from a Ktx file. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The loaded Ktx file. - /// An array of decoded images. - public ColorRgbFloat[][] DecodeAllMipMapsHdr(KtxFile file) - { - return DecodeInternalHdr(file, true, default); - } - - /// - /// Decode the main image from a Dds file. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The loaded Dds file. - /// The decoded image. - public ColorRgbFloat[] DecodeHdr(DdsFile file) - { - return DecodeInternalHdr(file, false, default)[0]; - } - - /// - /// Decode all available mipmaps from a Dds file. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The loaded Dds file. - /// An array of decoded images. - public ColorRgbFloat[][] DecodeAllMipMapsHdr(DdsFile file) - { - return DecodeInternalHdr(file, true, default); - } - - /// - /// Decode a single encoded image from raw bytes. - /// This method will read the expected amount of bytes from the given input stream and decode it. - /// Make sure there is no file header information left in the stream before the encoded data. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The stream containing the encoded data. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The Format the encoded data is in. - /// The decoded image. - public Memory2D DecodeRawHdr2D(Stream inputStream, int pixelWidth, int pixelHeight, CompressionFormat format) - { - var dataArray = new byte[GetBufferSize(format, pixelWidth, pixelHeight)]; - inputStream.Read(dataArray, 0, dataArray.Length); - - var decoded = DecodeRawHdr(dataArray, pixelWidth, pixelHeight, format); - return decoded.AsMemory().AsMemory2D(pixelHeight, pixelWidth); - } - - /// - /// Decode a single encoded image from raw bytes. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The byte array containing the raw encoded data. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The Format the encoded data is in. - /// The decoded image. - public Memory2D DecodeRawHdr2D(byte[] input, int pixelWidth, int pixelHeight, CompressionFormat format) - { - var decoded = DecodeRawInternalHdr(input, pixelWidth, pixelHeight, format, default); - return decoded.AsMemory().AsMemory2D(pixelHeight, pixelWidth); - } - - /// - /// Read a Ktx or Dds file from a stream and decode the main image from it. - /// The type of file will be detected automatically. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The stream containing a Ktx or Dds file. - /// The decoded image. - public Memory2D DecodeHdr2D(Stream inputStream) - { - return DecodeFromStreamInternalHdr2D(inputStream, false, default)[0]; - } - - /// - /// Read a Ktx or Dds file from a stream and decode all available mipmaps from it. - /// The type of file will be detected automatically. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The stream containing a Ktx or Dds file. - /// An array of decoded images. - public Memory2D[] DecodeAllMipMapsHdr2D(Stream inputStream) - { - return DecodeFromStreamInternalHdr2D(inputStream, true, default); - } - - /// - /// Decode the main image from a Ktx file. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The loaded Ktx file. - /// The decoded image. - public Memory2D DecodeHdr2D(KtxFile file) - { - return DecodeInternalHdr(file, false, default)[0].AsMemory().AsMemory2D((int)file.header.PixelHeight, (int)file.header.PixelWidth); - } - - /// - /// Decode all available mipmaps from a Ktx file. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The loaded Ktx file. - /// An array of decoded images. - public Memory2D[] DecodeAllMipMapsHdr2D(KtxFile file) - { - var decoded = DecodeInternalHdr(file, true, default); - var mem2Ds = new Memory2D[decoded.Length]; - for (var i = 0; i < decoded.Length; i++) - { - var mip = file.MipMaps[i]; - mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width); - } - return mem2Ds; - } - - /// - /// Decode the main image from a Dds file. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The loaded Dds file. - /// The decoded image. - public Memory2D DecodeHdr2D(DdsFile file) - { - return DecodeInternalHdr(file, false, default)[0].AsMemory().AsMemory2D((int)file.header.dwHeight, (int)file.header.dwWidth); - } - - /// - /// Decode all available mipmaps from a Dds file. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The loaded Dds file. - /// An array of decoded images. - public Memory2D[] DecodeAllMipMapsHdr2D(DdsFile file) - { - var decoded = DecodeInternalHdr(file, true, default); - var mem2Ds = new Memory2D[decoded.Length]; - for (var i = 0; i < decoded.Length; i++) - { - var mip = file.Faces[0].MipMaps[i]; - mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width); - } - return mem2Ds; - } - - /// - /// Decode a single block from raw bytes and return it as a . - /// Input Span size needs to equal the block size. - /// To get the block size (in bytes) of the compression format used, see . - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The encoded block in bytes. - /// The compression format used. - /// The decoded 4x4 block. - public Memory2D DecodeBlockHdr(ReadOnlySpan blockData, CompressionFormat format) - { - var output = new ColorRgbFloat[4, 4]; - DecodeBlockInternalHdr(blockData, format, output); - return output; - } - - /// - /// Decode a single block from raw bytes and write it to the given output span. - /// Output span size must be exactly 4x4 and input Span size needs to equal the block size. - /// To get the block size (in bytes) of the compression format used, see . - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The encoded block in bytes. - /// The compression format used. - /// The destination span of the decoded data. - public void DecodeBlockHdr(ReadOnlySpan blockData, CompressionFormat format, Span2D outputSpan) - { - if (outputSpan.Width != 4 || outputSpan.Height != 4) - { - throw new ArgumentException($"Single block decoding needs an output span of exactly 4x4"); - } - DecodeBlockInternalHdr(blockData, format, outputSpan); - } - - /// - /// Decode a single block from a stream and write it to the given output span. - /// Output span size must be exactly 4x4. - /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats. - /// - /// The stream to read encoded blocks from. - /// The compression format used. - /// The destination span of the decoded data. - /// The number of bytes read from the stream. Zero (0) if reached the end of stream. - public int DecodeBlockHdr(Stream inputStream, CompressionFormat format, Span2D outputSpan) - { - if (outputSpan.Width != 4 || outputSpan.Height != 4) - { - throw new ArgumentException($"Single block decoding needs an output span of exactly 4x4"); - } - - Span input = stackalloc byte[16]; - input = input.Slice(0, GetBlockSize(format)); - - var bytesRead = inputStream.Read(input); - - if (bytesRead == 0) - { - return 0; //End of stream - } - - if (bytesRead != input.Length) - { - throw new Exception("Input stream does not have enough data available for a full block."); - } - - DecodeBlockInternalHdr(input, format, outputSpan); - return bytesRead; - } - - /// - /// Check whether a file is encoded in a supported HDR format. - /// - /// The loaded ktx file to check - /// If the format of the file is one of the supported HDR formats. - public bool IsHdrFormat(KtxFile file) - { - return GetCompressionFormat(file.header.GlInternalFormat).IsHdrFormat(); - } - - /// - /// Check whether a file is encoded in a supported HDR format. - /// - /// The loaded dds file to check - /// If the format of the file is one of the supported HDR formats. - public bool IsHdrFormat(DdsFile file) - { - return GetCompressionFormat(file).IsHdrFormat(); - } - - #endregion - #endregion - /// - /// Load a stream and extract either the main image or all mip maps. - /// - /// The stream containing the image file. - /// If all mip maps or only the main image should be decoded. - /// The cancellation token for this operation. Can be default, if the operation is not asynchronous. - /// An array of decoded Rgba32 images. - private Memory2D[] DecodeFromStreamInternal2D(Stream stream, bool allMipMaps, CancellationToken token) - { - var format = ImageFile.DetermineImageFormat(stream); - - switch (format) - { - case ImageFileFormat.Dds: - { - var file = DdsFile.Load(stream); - var decoded = DecodeInternal(file, allMipMaps, token); - var mem2Ds = new Memory2D[decoded.Length]; - for (var i = 0; i < decoded.Length; i++) - { - var mip = file.Faces[0].MipMaps[i]; - mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width); - } - - return mem2Ds; - } - - case ImageFileFormat.Ktx: - { - var file = KtxFile.Load(stream); - var decoded = DecodeInternal(file, allMipMaps, token); - var mem2Ds = new Memory2D[decoded.Length]; - for (var i = 0; i < decoded.Length; i++) - { - var mip = file.MipMaps[i]; - mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width); - } - - return mem2Ds; - } - - default: - throw new InvalidOperationException("Unknown image format."); - } - } - - /// - /// Load a KTX file and extract either the main image or all mip maps. - /// - /// The Ktx file to decode. - /// If all mip maps or only the main image should be decoded. - /// The cancellation token for this operation. Can be default, if the operation is not asynchronous. - /// An array of decoded Rgba32 images. - private ColorRgba32[][] DecodeInternal(KtxFile file, bool allMipMaps, CancellationToken token) - { - var mipMaps = allMipMaps ? file.MipMaps.Count : 1; - var colors = new ColorRgba32[mipMaps][]; - - var context = new OperationContext - { - CancellationToken = token, - IsParallel = Options.IsParallel, - TaskCount = Options.TaskCount - }; - - // Calculate total blocks - var blockSize = GetBlockSize(file.header.GlInternalFormat); - var totalBlocks = file.MipMaps.Take(mipMaps).Sum(m => m.Faces[0].Data.Length / blockSize); - - context.Progress = new OperationProgress(Options.Progress, totalBlocks); - - if (IsSupportedRawFormat(file.header.GlInternalFormat)) - { - var decoder = GetRawDecoder(file.header.GlInternalFormat); - - for (var mip = 0; mip < mipMaps; mip++) - { - var data = file.MipMaps[mip].Faces[0].Data; - - colors[mip] = decoder.Decode(data, context); - - context.Progress.SetProcessedBlocks(file.MipMaps.Take(mip + 1).Sum(x => x.Faces[0].Data.Length / blockSize)); - } - } - else - { - var decoder = GetRgba32Decoder(file.header.GlInternalFormat); - var format = GetCompressionFormat(file.header.GlInternalFormat); - if (format.IsHdrFormat()) - { - throw new NotSupportedException($"This Format is not an RGBA32 compatible format: {format}, please use the HDR versions of the decode methods."); - } - if (decoder == null) - { - throw new NotSupportedException($"This Format is not supported: {file.header.GlInternalFormat}"); - } - - for (var mip = 0; mip < mipMaps; mip++) - { - var data = file.MipMaps[mip].Faces[0].Data; - var pixelWidth = file.MipMaps[mip].Width; - var pixelHeight = file.MipMaps[mip].Height; - - var blocks = decoder.Decode(data, context); - - colors[mip] = ImageToBlocks.ColorsFromRawBlocks(blocks, (int)pixelWidth, (int)pixelHeight); - - context.Progress.SetProcessedBlocks(file.MipMaps.Take(mip + 1).Sum(x => x.Faces[0].Data.Length / blockSize)); - } - } - - return colors; - } - - /// - /// Load a DDS file and extract either the main image or all mip maps. - /// - /// The Dds file to decode. - /// If all mip maps or only the main image should be decoded. - /// The cancellation token for this operation. Can be default, if the operation is not asynchronous. - /// An array of decoded Rgba32 images. - private ColorRgba32[][] DecodeInternal(DdsFile file, bool allMipMaps, CancellationToken token) - { - var mipMaps = allMipMaps ? file.header.dwMipMapCount : 1; - var colors = new ColorRgba32[mipMaps][]; - - var context = new OperationContext - { - CancellationToken = token, - IsParallel = Options.IsParallel, - TaskCount = Options.TaskCount - }; - - // Calculate total blocks - var blockSize = GetBlockSize(file); - var totalBlocks = file.Faces[0].MipMaps.Take((int)mipMaps).Sum(m => m.Data.Length / blockSize); - - context.Progress = new OperationProgress(Options.Progress, totalBlocks); - - if (IsSupportedRawFormat(file)) - { - var decoder = GetRawDecoder(file); - - for (var mip = 0; mip < mipMaps; mip++) - { - var data = file.Faces[0].MipMaps[mip].Data; - - colors[mip] = decoder.Decode(data, context); - - context.Progress.SetProcessedBlocks(file.Faces[0].MipMaps.Take(mip + 1).Sum(x => x.Data.Length / blockSize)); - } - } - else - { - var dxtFormat = file.header.ddsPixelFormat.IsDxt10Format - ? file.dx10Header.dxgiFormat - : file.header.ddsPixelFormat.DxgiFormat; - var format = GetCompressionFormat(file); - var decoder = GetRgba32Decoder(format); - - if (format.IsHdrFormat()) - { - throw new NotSupportedException($"This Format is not an RGBA32 compatible format: {format}, please use the HDR versions of the decode methods."); - } - if (decoder == null) - { - throw new NotSupportedException($"This Format is not supported: {dxtFormat}"); - } - - for (var mip = 0; mip < mipMaps; mip++) - { - var data = file.Faces[0].MipMaps[mip].Data; - var pixelWidth = file.Faces[0].MipMaps[mip].Width; - var pixelHeight = file.Faces[0].MipMaps[mip].Height; - - var blocks = decoder.Decode(data, context); - - var image = ImageToBlocks.ColorsFromRawBlocks(blocks, (int)pixelWidth, (int)pixelHeight); - - colors[mip] = image; - - context.Progress.SetProcessedBlocks(file.Faces[0].MipMaps.Take(mip + 1).Sum(x => x.Data.Length / blockSize)); - } - } - - return colors; - } - - /// - /// Decode raw encoded image asynchronously. - /// - /// The containing the encoded data. - /// The width of the image. - /// The height of the image. - /// The Format the encoded data is in. - /// The cancellation token for this operation. May be default, if the operation is not asynchronous. - /// The decoded Rgba32 image. - private ColorRgba32[] DecodeRawInternal(ReadOnlyMemory input, int pixelWidth, int pixelHeight, CompressionFormat format, CancellationToken token) - { - if (input.Length % GetBlockSize(format) != 0) - { - throw new ArgumentException("The size of the input buffer does not align with the compression format."); - } - - var context = new OperationContext - { - CancellationToken = token, - IsParallel = Options.IsParallel, - TaskCount = Options.TaskCount - }; - - // Calculate total blocks - var blockSize = GetBlockSize(format); - var totalBlocks = input.Length / blockSize; - - context.Progress = new OperationProgress(Options.Progress, totalBlocks); - - var isCompressedFormat = format.IsCompressedFormat(); - if (isCompressedFormat) - { - // DecodeInternal as compressed data - var decoder = GetRgba32Decoder(format); - - if (format.IsHdrFormat()) - { - throw new NotSupportedException($"This Format is not an RGBA32 compatible format: {format}, please use the HDR versions of the decode methods."); - } - if (decoder == null) - { - throw new NotSupportedException($"This Format is not supported: {format}"); - } - - var blocks = decoder.Decode(input, context); - - return ImageToBlocks.ColorsFromRawBlocks(blocks, pixelWidth, pixelHeight); ; - } - - // DecodeInternal as raw data - var rawDecoder = GetRawDecoder(format); - - return rawDecoder.Decode(input, context); - } - - private void DecodeBlockInternal(ReadOnlySpan blockData, CompressionFormat format, Span2D outputSpan) - { - var decoder = GetRgba32Decoder(format); - if (format.IsHdrFormat()) - { - throw new NotSupportedException($"This Format is not an RGBA32 compatible format: {format}, please use the HDR versions of the decode methods."); - } - if (decoder == null) - { - throw new NotSupportedException($"This Format is not supported: {format}"); - } - if (blockData.Length != GetBlockSize(format)) - { - throw new ArgumentException("The size of the input buffer does not align with the compression format."); - } - - var rawBlock = decoder.DecodeBlock(blockData); - var pixels = rawBlock.AsSpan; - - pixels.Slice(0, 4).CopyTo(outputSpan.GetRowSpan(0)); - pixels.Slice(4, 4).CopyTo(outputSpan.GetRowSpan(1)); - pixels.Slice(8, 4).CopyTo(outputSpan.GetRowSpan(2)); - pixels.Slice(12, 4).CopyTo(outputSpan.GetRowSpan(3)); - } - - #region Hdr internals - - /// - /// Load a stream and extract either the main image or all mip maps. - /// - /// The stream containing the image file. - /// If all mip maps or only the main image should be decoded. - /// The cancellation token for this operation. Can be default, if the operation is not asynchronous. - /// An array of decoded Rgba32 images. - private Memory2D[] DecodeFromStreamInternalHdr2D(Stream stream, bool allMipMaps, CancellationToken token) - { - var format = ImageFile.DetermineImageFormat(stream); - - switch (format) - { - case ImageFileFormat.Dds: - { - var file = DdsFile.Load(stream); - var decoded = DecodeInternalHdr(file, allMipMaps, token); - var mem2Ds = new Memory2D[decoded.Length]; - for (var i = 0; i < decoded.Length; i++) - { - var mip = file.Faces[0].MipMaps[i]; - mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width); - } - - return mem2Ds; - } + public TOut[] DecodeRaw(ReadOnlyMemory input, int pixelWidth, int pixelHeight, CompressionFormat inputFormat, CompressionFormat outputFormat) + where TOut : unmanaged + { + var output = AllocateOutputBuffer(pixelWidth, pixelHeight, 1, outputFormat); - case ImageFileFormat.Ktx: - { - var file = KtxFile.Load(stream); - var decoded = DecodeInternalHdr(file, allMipMaps, token); - var mem2Ds = new Memory2D[decoded.Length]; - for (var i = 0; i < decoded.Length; i++) - { - var mip = file.MipMaps[i]; - mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width); - } + DecodeRawInternal(input, output.AsMemory().Cast(), pixelWidth, pixelHeight, 1, inputFormat, outputFormat, CancellationToken.None); - return mem2Ds; - } + return output; + } - default: - throw new InvalidOperationException("Unknown image format."); + /// + /// Decode a single block from raw bytes and write it to the given output span. + /// Output span size must be exactly 4x4 and input Span size needs to equal the block size. + /// To get the block size (in bytes) of the compression format used, see . + /// + /// The encoded block in bytes. + /// The compression format used. + /// The destination span of the decoded data. + public void DecodeBlock(ReadOnlySpan blockData, Memory2D output, CompressionFormat inputFormat, CompressionFormat outputFormat) + where TOut : unmanaged + { + if (output.Width != 4 || output.Height != 4) + { + throw new ArgumentException($"Single block decoding needs an output span of exactly 4x4"); } + DecodeBlockInternal(blockData, output, inputFormat, outputFormat); } /// - /// Load a KTX file and extract either the main image or all mip maps. + /// Decode a single block from a stream and write it to the given output span. + /// Output span size must be exactly 4x4. /// - /// The Ktx file to decode. - /// If all mip maps or only the main image should be decoded. - /// The cancellation token for this operation. Can be default, if the operation is not asynchronous. - /// An array of decoded Rgba32 images. - private ColorRgbFloat[][] DecodeInternalHdr(KtxFile file, bool allMipMaps, CancellationToken token) + /// The stream to read encoded blocks from. + /// The compression format used. + /// The destination span of the decoded data. + /// The number of bytes read from the stream. Zero (0) if reached the end of stream. + public int DecodeBlock(Stream inputStream, Memory2D output, CompressionFormat inputFormat, CompressionFormat outputFormat) + where TOut : unmanaged { - var mipMaps = allMipMaps ? file.MipMaps.Count : 1; - var colors = new ColorRgbFloat[mipMaps][]; - - var context = new OperationContext + if (output.Width != 4 || output.Height != 4) { - CancellationToken = token, - IsParallel = Options.IsParallel, - TaskCount = Options.TaskCount - }; + throw new ArgumentException($"Single block decoding needs an output span of exactly 4x4"); + } - // Calculate total blocks - var blockSize = GetBlockSize(file.header.GlInternalFormat); - var totalBlocks = file.MipMaps.Take(mipMaps).Sum(m => m.Faces[0].Data.Length / blockSize); + Span input = stackalloc byte[16]; + input = input.Slice(0, GetBlockSize(inputFormat)); - context.Progress = new OperationProgress(Options.Progress, totalBlocks); + var bytesRead = inputStream.Read(input); - var decoder = GetRgbFloatDecoder(file.header.GlInternalFormat); - var format = GetCompressionFormat(file.header.GlInternalFormat); - if (!format.IsHdrFormat()) - { - throw new NotSupportedException($"This Format is not an HDR format: {format}, please use the non-HDR versions of the decode methods."); - } - if (decoder == null) + if (bytesRead == 0) { - throw new NotSupportedException($"This Format is not supported: {file.header.GlInternalFormat}"); + return 0; //End of stream } - for (var mip = 0; mip < mipMaps; mip++) + if (bytesRead != input.Length) { - var data = file.MipMaps[mip].Faces[0].Data; - var pixelWidth = file.MipMaps[mip].Width; - var pixelHeight = file.MipMaps[mip].Height; - - var blocks = decoder.Decode(data, context); - - colors[mip] = ImageToBlocks.ColorsFromRawBlocks(blocks, (int)pixelWidth, (int)pixelHeight); - - context.Progress.SetProcessedBlocks(file.MipMaps.Take(mip + 1).Sum(x => x.Faces[0].Data.Length / blockSize)); + throw new Exception("Input stream does not have enough data available for a full block."); } - return colors; + DecodeBlockInternal(input, output, inputFormat, outputFormat); + return bytesRead; } /// - /// Load a DDS file and extract either the main image or all mip maps. + /// Decode all faces and mipmaps of a into for ldr formats, + /// and for hdr formats. /// - /// The Dds file to decode. - /// If all mip maps or only the main image should be decoded. + /// The texture data to decode. + /// The output format. /// The cancellation token for this operation. Can be default, if the operation is not asynchronous. - /// An array of decoded Rgba32 images. - private ColorRgbFloat[][] DecodeInternalHdr(DdsFile file, bool allMipMaps, CancellationToken token) + /// A new containing the decoded data. + private BCnTextureData DecodeInternal(BCnTextureData texture, CompressionFormat outputFormat, CancellationToken token) { - var mipMaps = allMipMaps ? file.header.dwMipMapCount : 1; - var colors = new ColorRgbFloat[mipMaps][]; + if (!outputFormat.IsRawPixelFormat()) + { + throw new NotSupportedException($"The given output format is not a raw pixel format: {outputFormat}"); + } var context = new OperationContext { CancellationToken = token, IsParallel = Options.IsParallel, - TaskCount = Options.TaskCount + TaskCount = Options.TaskCount, + ColorConversionMode = GetColorConversionInput(texture.Format) }; - // Calculate total blocks - var blockSize = GetBlockSize(file); - var totalBlocks = file.Faces[0].MipMaps.Take((int)mipMaps).Sum(m => m.Data.Length / blockSize); + var blockSize = texture.Format.GetBytesPerBlock(); + var totalBlocks = 0L; + for (var m = 0; m < texture.NumMips; m++) + { + totalBlocks += texture.Mips[m].SizeInBytes / blockSize; + } + totalBlocks *= texture.NumFaces; + totalBlocks *= texture.NumArrayElements; context.Progress = new OperationProgress(Options.Progress, totalBlocks); - var dxtFormat = file.header.ddsPixelFormat.IsDxt10Format - ? file.dx10Header.dxgiFormat - : file.header.ddsPixelFormat.DxgiFormat; - var format = GetCompressionFormat(file); - var decoder = GetRgbFloatDecoder(format); + var decoder = GetDecoder(texture.Format); - if (!format.IsHdrFormat()) - { - throw new NotSupportedException($"This Format is not an HDR format: {format}, please use the non-HDR versions of the decode methods."); - } if (decoder == null) { - throw new NotSupportedException($"This Format is not supported: {dxtFormat}"); + throw new NotSupportedException($"This Format is not supported: {texture.Format}"); } - for (var mip = 0; mip < mipMaps; mip++) + var outputAlphaChannelhint = OutputOptions.AlphaHandling == DecoderAlphaHandling.Unpremultiply + ? AlphaChannelHint.Straight + : texture.AlphaChannelHint; + + var outputData = new BCnTextureData(outputFormat, texture.Width, texture.Height, texture.Depth, + texture.NumMips, texture.NumArrayElements, texture.IsCubeMap, true, outputAlphaChannelhint); + + for (var m = 0; m < texture.NumMips; m++) { - var data = file.Faces[0].MipMaps[mip].Data; - var pixelWidth = file.Faces[0].MipMaps[mip].Width; - var pixelHeight = file.Faces[0].MipMaps[mip].Height; + for (var f = 0; f < texture.NumFaces; f++) + { + for (var a = 0; a < texture.NumArrayElements; a++) + { + var data = texture.Mips[m][(CubeMapFaceDirection)f, a].Data; + + var decoded = decoder.Decode(data, texture.Mips[m].Width, + texture.Mips[m].Height, context); + + // Get as span to process in-place + var resultSpan = decoded.AsSpan().Cast(); + + // Convert to UNorm if needed + if (texture.Format.IsSNormFormat() && outputFormat.IsUNormFormat() && OutputOptions.RescaleSnormToUnorm) + { + SNormToUNorm(resultSpan); + } - var blocks = decoder.Decode(data, context); + // Apply alpha handling if needed + if (texture.Format.SupportsAlpha() && !texture.Format.IsHdrFormat()) + { + if (OutputOptions.AlphaHandling == DecoderAlphaHandling.Unpremultiply && + texture.AlphaChannelHint == AlphaChannelHint.Premultiplied) + { - var image = ImageToBlocks.ColorsFromRawBlocks(blocks, (int)pixelWidth, (int)pixelHeight); + // Unpremultiply alpha + AlphaHandlingHelper.UnpremultiplyAlpha(resultSpan); + } + } - colors[mip] = image; + ColorConversionMode outColorConversionMode = GetColorConversionOutput(texture.Format, outputFormat); - context.Progress.SetProcessedBlocks(file.Faces[0].MipMaps.Take(mip + 1).Sum(x => x.Data.Length / blockSize)); + ColorExtensions.InternalConvertToAsBytesFromBytes(decoded, outputData.Mips[m][(CubeMapFaceDirection)f, a].Data, CompressionFormat.RgbaFloat, + outputFormat, outColorConversionMode); + } + } } - return colors; + return outputData; } /// - /// Decode raw encoded image asynchronously. + /// Decode raw encoded image. /// /// The containing the encoded data. /// The width of the image. /// The height of the image. - /// The Format the encoded data is in. + /// The depth of the image. + /// The Format the encoded data is in. /// The cancellation token for this operation. May be default, if the operation is not asynchronous. /// The decoded Rgba32 image. - private ColorRgbFloat[] DecodeRawInternalHdr(ReadOnlyMemory input, int pixelWidth, int pixelHeight, CompressionFormat format, CancellationToken token) + private void DecodeRawInternal(ReadOnlyMemory input, Memory output, int pixelWidth, int pixelHeight, int pixelDepth, CompressionFormat inputFormat, CompressionFormat outputFormat, CancellationToken token) { - if (input.Length % GetBlockSize(format) != 0) + if (input.Length != GetBufferSize(inputFormat, pixelWidth, pixelHeight, pixelDepth)) { - throw new ArgumentException("The size of the input buffer does not align with the compression format."); + throw new ArgumentException("The size of the input buffer does not align with the compression format. Expected: " + GetBufferSize(inputFormat, pixelWidth, pixelHeight, pixelDepth) + ", Actual: " + input.Length); + } + if (output.Length != GetBufferSize(outputFormat, pixelWidth, pixelHeight, pixelDepth)) + { + throw new ArgumentException("The size of the output buffer does not align with the output format. Expected: " + GetBufferSize(outputFormat, pixelWidth, pixelHeight, pixelDepth) + ", Actual: " + output.Length); + } + if (!outputFormat.IsRawPixelFormat()) + { + throw new NotSupportedException($"The given output format is not a raw pixel format: {outputFormat}"); } var context = new OperationContext { CancellationToken = token, IsParallel = Options.IsParallel, - TaskCount = Options.TaskCount + TaskCount = Options.TaskCount, + ColorConversionMode = GetColorConversionInput(inputFormat) }; // Calculate total blocks - var blockSize = GetBlockSize(format); + var blockSize = GetBlockSize(inputFormat); var totalBlocks = input.Length / blockSize; context.Progress = new OperationProgress(Options.Progress, totalBlocks); - var decoder = GetRgbFloatDecoder(format); + var decoder = GetDecoder(inputFormat); - if (!format.IsHdrFormat()) + if (decoder == null) { - throw new NotSupportedException($"This Format is not an HDR format: {format}, please use the non-HDR versions of the decode methods."); + throw new NotSupportedException($"This Format is not supported: {inputFormat}"); } - if (decoder == null) + + var result = decoder.Decode(input, pixelWidth, pixelHeight, context); + var resultSpan = result.AsSpan().Cast(); + + // Convert to UNorm if needed + if (inputFormat.IsSNormFormat() && outputFormat.IsUNormFormat() && OutputOptions.RescaleSnormToUnorm) { - throw new NotSupportedException($"This Format is not supported: {format}"); + SNormToUNorm(resultSpan); } - var blocks = decoder.Decode(input, context); + // Apply alpha handling if needed + if (inputFormat.SupportsAlpha() && !inputFormat.IsHdrFormat()) + { + if (OutputOptions.AlphaHandling == DecoderAlphaHandling.Unpremultiply) + { + AlphaHandlingHelper.UnpremultiplyAlpha(resultSpan); + } + } - return ImageToBlocks.ColorsFromRawBlocks(blocks, pixelWidth, pixelHeight); + ColorExtensions.InternalConvertToAsBytesFromBytes(result, output, CompressionFormat.RgbaFloat, + outputFormat, GetColorConversionOutput(inputFormat, outputFormat)); } - private void DecodeBlockInternalHdr(ReadOnlySpan blockData, CompressionFormat format, Span2D outputSpan) + private void DecodeBlockInternal(ReadOnlySpan blockData, Memory2D output, CompressionFormat inputFormat, CompressionFormat outputFormat) + where TOut: unmanaged { - var decoder = GetRgbFloatDecoder(format); - if (!format.IsHdrFormat()) + if (!inputFormat.IsBlockCompressedFormat()) { - throw new NotSupportedException($"This Format is not an HDR format: {format}, please use the non-HDR versions of the decode methods."); + throw new NotSupportedException($"This Format is not a block-compressed format: {inputFormat}."); } + var decoder = GetDecoder(inputFormat) as IBcBlockDecoder; if (decoder == null) { - throw new NotSupportedException($"This Format is not supported: {format}"); + throw new NotSupportedException($"This Format is not supported: {inputFormat}"); } - if (blockData.Length != GetBlockSize(format)) + if (blockData.Length != GetBlockSize(inputFormat)) { throw new ArgumentException("The size of the input buffer does not align with the compression format."); } + if (output.Height != 4 || output.Width != 4) + { + throw new ArgumentException("The output memory must be 4x4."); + } + if (Unsafe.SizeOf() != outputFormat.GetBytesPerBlock()) + { + throw new ArgumentException($"The size of {typeof(TOut).Name} does not match the output format!"); + } + if (!outputFormat.IsRawPixelFormat()) + { + throw new NotSupportedException($"The given output format is not a raw pixel format: {outputFormat}"); + } var rawBlock = decoder.DecodeBlock(blockData); - var pixels = rawBlock.AsSpan; - - pixels.Slice(0, 4).CopyTo(outputSpan.GetRowSpan(0)); - pixels.Slice(4, 4).CopyTo(outputSpan.GetRowSpan(1)); - pixels.Slice(8, 4).CopyTo(outputSpan.GetRowSpan(2)); - pixels.Slice(12, 4).CopyTo(outputSpan.GetRowSpan(3)); - } - #endregion + var pixels = rawBlock.AsSpan.Cast(); - #region Support - - #region Is supported format + byte[] floatBytes = new byte[16 * Unsafe.SizeOf()]; + pixels.CopyTo(floatBytes); - private bool IsSupportedRawFormat(GlInternalFormat format) - { - return IsSupportedRawFormat(GetCompressionFormat(format)); - } + byte [] outputBytes = new byte[16 * Unsafe.SizeOf()]; - private bool IsSupportedRawFormat(DdsFile file) - { - return IsSupportedRawFormat(GetCompressionFormat(file)); - } + ColorExtensions.InternalConvertToAsBytesFromBytes(floatBytes, outputBytes, CompressionFormat.RgbaFloat, + outputFormat, ColorConversionMode.None); - private bool IsSupportedRawFormat(CompressionFormat format) - { - switch (format) - { - case CompressionFormat.R: - case CompressionFormat.Rg: - case CompressionFormat.Rgb: - case CompressionFormat.Rgba: - case CompressionFormat.Bgra: - return true; + Span outSpan = outputBytes.AsSpan().Cast(); - default: - return false; - } + outSpan.Slice(0, 4).CopyTo(output.Span.GetRowSpan(0)); + outSpan.Slice(4, 4).CopyTo(output.Span.GetRowSpan(1)); + outSpan.Slice(8, 4).CopyTo(output.Span.GetRowSpan(2)); + outSpan.Slice(12, 4).CopyTo(output.Span.GetRowSpan(3)); } - #endregion + #region Support #region Get decoder - - private IBcBlockDecoder GetRgba32Decoder(GlInternalFormat format) - { - return GetRgba32Decoder(GetCompressionFormat(format)); - } - - private IBcBlockDecoder GetRgba32Decoder(DdsFile file) - { - return GetRgba32Decoder(GetCompressionFormat(file)); - } - - private IBcBlockDecoder GetRgba32Decoder(CompressionFormat format) + private IBcDecoder GetDecoder(CompressionFormat format) { + if (format.IsRawPixelFormat()) + { + return Activator.CreateInstance(typeof(RawDecoder<>).MakeGenericType(format.GetPixelType())) as IBcDecoder; + } switch (format) { case CompressionFormat.Bc1: + case CompressionFormat.Bc1_sRGB: return new Bc1NoAlphaDecoder(); case CompressionFormat.Bc1WithAlpha: + case CompressionFormat.Bc1WithAlpha_sRGB: return new Bc1ADecoder(); case CompressionFormat.Bc2: + case CompressionFormat.Bc2_sRGB: return new Bc2Decoder(); case CompressionFormat.Bc3: + case CompressionFormat.Bc3_sRGB: return new Bc3Decoder(); case CompressionFormat.Bc4: return new Bc4Decoder(OutputOptions.Bc4Component); case CompressionFormat.Bc5: - return new Bc5Decoder(OutputOptions.Bc5Component1, OutputOptions.Bc5Component2); + return new Bc5Decoder(OutputOptions.Bc5Component1, OutputOptions.Bc5Component2, OutputOptions.Bc5ComponentCalculated); case CompressionFormat.Bc7: + case CompressionFormat.Bc7_sRGB: return new Bc7Decoder(); + case CompressionFormat.Bc6S: + return new Bc6SDecoder(); + + case CompressionFormat.Bc6U: + return new Bc6UDecoder(); + case CompressionFormat.Atc: return new AtcDecoder(); @@ -1611,110 +447,65 @@ private IBcBlockDecoder GetRgba32Decoder(CompressionFormat fo case CompressionFormat.AtcInterpolatedAlpha: return new AtcInterpolatedAlphaDecoder(); - default: return null; } } + #endregion - private IBcBlockDecoder GetRgbFloatDecoder(GlInternalFormat format) - { - return GetRgbFloatDecoder(GetCompressionFormat(format)); - } - - private IBcBlockDecoder GetRgbFloatDecoder(DdsFile file) + private ColorConversionMode GetColorConversionInput(CompressionFormat sourceFormat) { - return GetRgbFloatDecoder(GetCompressionFormat(file)); - } + if (OutputOptions.OutputColorSpace == OutputColorSpaceTarget.KeepAsIs) + return ColorConversionMode.None; - private IBcBlockDecoder GetRgbFloatDecoder(CompressionFormat format) - { - switch (format) + // Determine input color space based on settings + bool inputIsSrgb = sourceFormat.IsSRGBFormat(); + if (OutputOptions.InputColorSpace == InputColorSpaceAssumption.ForceSrgb) { - case CompressionFormat.Bc6S: - return new Bc6SDecoder(); - case CompressionFormat.Bc6U: - return new Bc6UDecoder(); - default: - return null; + inputIsSrgb = true; + } + else if (OutputOptions.InputColorSpace == InputColorSpaceAssumption.ForceLinear) + { + inputIsSrgb = false; } - } - - #endregion - #region Get raw decoder + if (inputIsSrgb) + return ColorConversionMode.SrgbToLinear; - private IRawDecoder GetRawDecoder(GlInternalFormat format) - { - return GetRawDecoder(GetCompressionFormat(format)); + return ColorConversionMode.None; } - private IRawDecoder GetRawDecoder(DdsFile file) + private ColorConversionMode GetColorConversionOutput(CompressionFormat sourceFormat, CompressionFormat targetFormat) { - return GetRawDecoder(GetCompressionFormat(file)); - } + CompressionFormat processFormat = CompressionFormat.RgbaFloat; - private IRawDecoder GetRawDecoder(CompressionFormat format) - { - switch (format) + bool sourceIsSrgb = sourceFormat.IsSRGBFormat(); + + // Apply output color space preferences + switch (OutputOptions.OutputColorSpace) { - case CompressionFormat.R: - return new RawRDecoder(OutputOptions.RedAsLuminance); + case OutputColorSpaceTarget.KeepAsIs: + // No conversion needed + return ColorConversionMode.None; - case CompressionFormat.Rg: - return new RawRgDecoder(); + case OutputColorSpaceTarget.Linear: + return ColorConversionMode.None; - case CompressionFormat.Rgb: - return new RawRgbDecoder(); + case OutputColorSpaceTarget.Srgb: + return ColorConversionMode.LinearToSrgb; - case CompressionFormat.Rgba: - return new RawRgbaDecoder(); + case OutputColorSpaceTarget.Auto: + // Auto mode determines the best conversion based on formats + return processFormat.GetColorConversionMode(targetFormat); - case CompressionFormat.Bgra: - return new RawBgraDecoder(); + case OutputColorSpaceTarget.ProcessLinearPreserveColorSpace: + return sourceIsSrgb ? ColorConversionMode.LinearToSrgb : ColorConversionMode.None; default: - throw new ArgumentOutOfRangeException(nameof(format), format, null); + return ColorConversionMode.None; } } - #endregion - - #region Get block size - - /// - /// Gets the number of total blocks in an image with the given pixel width and height. - /// - /// The pixel width of the image - /// The pixel height of the image - /// The total number of blocks. - public int GetBlockCount(int pixelWidth, int pixelHeight) - { - return ImageToBlocks.CalculateNumOfBlocks(pixelWidth, pixelHeight); - } - - /// - /// Gets the number of blocks in an image with the given pixel width and height. - /// - /// The pixel width of the image - /// The pixel height of the image - /// The amount of blocks in the x-axis - /// The amount of blocks in the y-axis - public void GetBlockCount(int pixelWidth, int pixelHeight, out int blocksWidth, out int blocksHeight) - { - ImageToBlocks.CalculateNumOfBlocks(pixelWidth, pixelHeight, out blocksWidth, out blocksHeight); - } - - private int GetBlockSize(GlInternalFormat format) - { - return GetBlockSize(GetCompressionFormat(format)); - } - - private int GetBlockSize(DdsFile file) - { - return GetBlockSize(GetCompressionFormat(file)); - } - /// /// Get the size of blocks for the given compression format in bytes. /// @@ -1722,242 +513,47 @@ private int GetBlockSize(DdsFile file) /// The size of a single block in bytes. public int GetBlockSize(CompressionFormat format) { - switch (format) - { - case CompressionFormat.R: - return 1; - - case CompressionFormat.Rg: - return 2; - - case CompressionFormat.Rgb: - return 3; - - case CompressionFormat.Rgba: - return 4; - - case CompressionFormat.Bgra: - return 4; - - case CompressionFormat.Bc1: - case CompressionFormat.Bc1WithAlpha: - return Unsafe.SizeOf(); - - case CompressionFormat.Bc2: - return Unsafe.SizeOf(); - - case CompressionFormat.Bc3: - return Unsafe.SizeOf(); - - case CompressionFormat.Bc4: - return Unsafe.SizeOf(); - - case CompressionFormat.Bc5: - return Unsafe.SizeOf(); - - case CompressionFormat.Bc6S: - case CompressionFormat.Bc6U: - return Unsafe.SizeOf(); - - case CompressionFormat.Bc7: - return Unsafe.SizeOf(); - - case CompressionFormat.Atc: - return Unsafe.SizeOf(); - - case CompressionFormat.AtcExplicitAlpha: - return Unsafe.SizeOf(); - - case CompressionFormat.AtcInterpolatedAlpha: - return Unsafe.SizeOf(); - - case CompressionFormat.Unknown: - return 0; - - default: - throw new ArgumentOutOfRangeException(nameof(format), format, null); - } + return format.GetBytesPerBlock(); } - #endregion - - private CompressionFormat GetCompressionFormat(GlInternalFormat format) + private long GetBufferSize(CompressionFormat format, int pixelWidth, int pixelHeight, int pixelDepth) { - switch (format) - { - case GlInternalFormat.GlR8: - return CompressionFormat.R; - - case GlInternalFormat.GlRg8: - return CompressionFormat.Rg; - - case GlInternalFormat.GlRgb8: - return CompressionFormat.Rgb; - - case GlInternalFormat.GlRgba8: - return CompressionFormat.Rgba; - - // HINT: Bgra is not supported by default. The format enum is added by an extension by Apple. - case GlInternalFormat.GlBgra8Extension: - return CompressionFormat.Bgra; - - case GlInternalFormat.GlCompressedRgbS3TcDxt1Ext: - return CompressionFormat.Bc1; - - case GlInternalFormat.GlCompressedRgbaS3TcDxt1Ext: - return CompressionFormat.Bc1WithAlpha; - - case GlInternalFormat.GlCompressedRgbaS3TcDxt3Ext: - return CompressionFormat.Bc2; - - case GlInternalFormat.GlCompressedRgbaS3TcDxt5Ext: - return CompressionFormat.Bc3; - - case GlInternalFormat.GlCompressedRedRgtc1Ext: - return CompressionFormat.Bc4; - - case GlInternalFormat.GlCompressedRedGreenRgtc2Ext: - return CompressionFormat.Bc5; - - case GlInternalFormat.GlCompressedRgbBptcUnsignedFloatArb: - return CompressionFormat.Bc6U; - - case GlInternalFormat.GlCompressedRgbBptcSignedFloatArb: - return CompressionFormat.Bc6S; - - // TODO: Not sure what to do with SRGB input. - case GlInternalFormat.GlCompressedRgbaBptcUnormArb: - case GlInternalFormat.GlCompressedSrgbAlphaBptcUnormArb: - return CompressionFormat.Bc7; - - case GlInternalFormat.GlCompressedRgbAtc: - return CompressionFormat.Atc; - - case GlInternalFormat.GlCompressedRgbaAtcExplicitAlpha: - return CompressionFormat.AtcExplicitAlpha; - - case GlInternalFormat.GlCompressedRgbaAtcInterpolatedAlpha: - return CompressionFormat.AtcInterpolatedAlpha; - - default: - return CompressionFormat.Unknown; - } + return format.CalculateMipByteSize(pixelWidth, pixelHeight, pixelDepth); } - private CompressionFormat GetCompressionFormat(DdsFile file) + private static TOut[] AllocateOutputBuffer(int pixelWidth, int pixelHeight, int pixelDepth, CompressionFormat outputFormat) + where TOut : unmanaged { - var format = file.header.ddsPixelFormat.IsDxt10Format ? - file.dx10Header.dxgiFormat : - file.header.ddsPixelFormat.DxgiFormat; - - switch (format) - { - case DxgiFormat.DxgiFormatR8Unorm: - return CompressionFormat.R; - - case DxgiFormat.DxgiFormatR8G8Unorm: - return CompressionFormat.Rg; - - // HINT: R8G8B8 has no DxgiFormat to convert from - - case DxgiFormat.DxgiFormatR8G8B8A8Unorm: - return CompressionFormat.Rgba; - - case DxgiFormat.DxgiFormatB8G8R8A8Unorm: - return CompressionFormat.Bgra; - - case DxgiFormat.DxgiFormatBc1Unorm: - case DxgiFormat.DxgiFormatBc1UnormSrgb: - case DxgiFormat.DxgiFormatBc1Typeless: - if (file.header.ddsPixelFormat.dwFlags.HasFlag(PixelFormatFlags.DdpfAlphaPixels)) - return CompressionFormat.Bc1WithAlpha; - - if (InputOptions.DdsBc1ExpectAlpha) - return CompressionFormat.Bc1WithAlpha; - - return CompressionFormat.Bc1; - - case DxgiFormat.DxgiFormatBc2Unorm: - case DxgiFormat.DxgiFormatBc2UnormSrgb: - case DxgiFormat.DxgiFormatBc2Typeless: - return CompressionFormat.Bc2; + if (outputFormat.IsBlockCompressedFormat()) + throw new ArgumentException("The output format must be a raw pixel format."); - case DxgiFormat.DxgiFormatBc3Unorm: - case DxgiFormat.DxgiFormatBc3UnormSrgb: - case DxgiFormat.DxgiFormatBc3Typeless: - return CompressionFormat.Bc3; + var tOutSize = Unsafe.SizeOf(); + var realTOutSize = outputFormat.GetBytesPerBlock(); - case DxgiFormat.DxgiFormatBc4Unorm: - case DxgiFormat.DxgiFormatBc4Snorm: - case DxgiFormat.DxgiFormatBc4Typeless: - return CompressionFormat.Bc4; - - case DxgiFormat.DxgiFormatBc5Unorm: - case DxgiFormat.DxgiFormatBc5Snorm: - case DxgiFormat.DxgiFormatBc5Typeless: - return CompressionFormat.Bc5; - - case DxgiFormat.DxgiFormatBc6HTypeless: - case DxgiFormat.DxgiFormatBc6HUf16: - return CompressionFormat.Bc6U; - - case DxgiFormat.DxgiFormatBc6HSf16: - return CompressionFormat.Bc6S; - - case DxgiFormat.DxgiFormatBc7Unorm: - case DxgiFormat.DxgiFormatBc7UnormSrgb: - case DxgiFormat.DxgiFormatBc7Typeless: - return CompressionFormat.Bc7; - - case DxgiFormat.DxgiFormatAtcExt: - return CompressionFormat.Atc; - - case DxgiFormat.DxgiFormatAtcExplicitAlphaExt: - return CompressionFormat.AtcExplicitAlpha; + if (tOutSize > realTOutSize) + { + throw new ArgumentException($"SizeOf must be smaller than or equal to {realTOutSize} bytes in size."); + } + if (realTOutSize % tOutSize != 0) + { + throw new ArgumentException($"SizeOf must be a multiple of {realTOutSize} bytes in size."); + } - case DxgiFormat.DxgiFormatAtcInterpolatedAlphaExt: - return CompressionFormat.AtcInterpolatedAlpha; + var tOutMultiplier = realTOutSize / tOutSize; - default: - return CompressionFormat.Unknown; - } + var output = new TOut[pixelWidth * pixelHeight * pixelDepth * tOutMultiplier]; + return output; } - private int GetBufferSize(CompressionFormat format, int pixelWidth, int pixelHeight) + private static void SNormToUNorm(Span colors) { - switch (format) + for (int i = 0; i < colors.Length; i++) { - case CompressionFormat.R: - return pixelWidth * pixelHeight; - - case CompressionFormat.Rg: - return 2 * pixelWidth * pixelHeight; - - case CompressionFormat.Rgb: - return 3 * pixelWidth * pixelHeight; - - case CompressionFormat.Rgba: - case CompressionFormat.Bgra: - return 4 * pixelWidth * pixelHeight; - - case CompressionFormat.Bc1: - case CompressionFormat.Bc1WithAlpha: - case CompressionFormat.Bc2: - case CompressionFormat.Bc3: - case CompressionFormat.Bc4: - case CompressionFormat.Bc5: - case CompressionFormat.Bc6S: - case CompressionFormat.Bc6U: - case CompressionFormat.Bc7: - case CompressionFormat.Atc: - case CompressionFormat.AtcExplicitAlpha: - case CompressionFormat.AtcInterpolatedAlpha: - return GetBlockSize(format) * ImageToBlocks.CalculateNumOfBlocks(pixelWidth, pixelHeight); - - case CompressionFormat.Unknown: - return 0; - default: - throw new ArgumentOutOfRangeException(nameof(format), format, null); + colors[i] = new ColorRgbaFloat( + colors[i].r * 0.5f + 0.5f, + colors[i].g * 0.5f + 0.5f, + colors[i].b * 0.5f + 0.5f + ); } } diff --git a/BCnEnc.Net/Decoder/BcFloatBlockDecoder.cs b/BCnEnc.Net/Decoder/BcFloatBlockDecoder.cs deleted file mode 100644 index b585bc3..0000000 --- a/BCnEnc.Net/Decoder/BcFloatBlockDecoder.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace BCnEncoder.Decoder -{ - internal class Bc6FloatBlockDecoder - { - private static readonly object lockObj = new object(); - - } -} diff --git a/BCnEnc.Net/Decoder/IBcDecoder.cs b/BCnEnc.Net/Decoder/IBcDecoder.cs new file mode 100644 index 0000000..d76cf10 --- /dev/null +++ b/BCnEnc.Net/Decoder/IBcDecoder.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; +using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; + +namespace BCnEncoder.Decoder +{ + internal interface IBcDecoder + { + byte[] Decode(ReadOnlyMemory data, int width, int height, OperationContext context); + ColorRgbaFloat[] DecodeColor(ReadOnlyMemory data, int width, int height, OperationContext context); + } +} diff --git a/BCnEnc.Net/Decoder/Options/DecoderInputOptions.cs b/BCnEnc.Net/Decoder/Options/DecoderInputOptions.cs deleted file mode 100644 index ed2e650..0000000 --- a/BCnEnc.Net/Decoder/Options/DecoderInputOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace BCnEncoder.Decoder.Options -{ - /// - /// A class for the decoder input options. - /// - public class DecoderInputOptions - { - /// - /// The DDS file Format doesn't seem to have a standard for indicating whether a BC1 texture - /// includes 1bit of alpha. This option will assume that all Bc1 textures contain alpha. - /// If this option is false, but the dds header includes a DDPF_ALPHAPIXELS flag, alpha will be included. - /// Default is true. - /// - public bool DdsBc1ExpectAlpha { get; set; } = true; - } -} diff --git a/BCnEnc.Net/Decoder/Options/DecoderOutputOptions.cs b/BCnEnc.Net/Decoder/Options/DecoderOutputOptions.cs index f221800..efa39fc 100644 --- a/BCnEnc.Net/Decoder/Options/DecoderOutputOptions.cs +++ b/BCnEnc.Net/Decoder/Options/DecoderOutputOptions.cs @@ -4,21 +4,92 @@ namespace BCnEncoder.Decoder.Options { /// - /// A class for the decoder output options. + /// Controls how the color space of the input data should be interpreted. /// - public class DecoderOutputOptions + public enum InputColorSpaceAssumption + { + /// + /// [DEFAULT] Use the color space indicated by the format. + /// For formats that explicitly support sRGB (like BC1_SRGB), those indicators will be respected. + /// + Auto, + + /// + /// Assume the input data is in sRGB color space (with gamma curve), regardless of format indicators. + /// Useful for older textures or those from sources that didn't properly tag their color space. + /// + ForceSrgb, + + /// + /// Assume the input data is in linear color space (without gamma curve), regardless of format indicators. + /// Useful for normal maps, physical property maps, or other technical textures that store linear data. + /// + ForceLinear + } + + /// + /// Controls the target color space for the decoded output. + /// + public enum OutputColorSpaceTarget + { + /// + /// Colors are converted to linear colorspace for processing. Output will match the input colorspace regardless of chosen output format. + /// + ProcessLinearPreserveColorSpace, + + /// + /// Keep the color space the same as the input (or assumed input) color space. + /// No color space conversion will be performed. + /// + KeepAsIs, + + /// + /// Convert to linear color space for the output. + /// Use this when you need physically accurate color values for lighting calculations or HDR rendering. + /// + Linear, + + /// + /// Convert to sRGB color space for the output. + /// Use this when preparing images for display or when working with photo/UI editing software. + /// + Srgb, + + /// + /// Automatically determine the best output color space based on the output format. + /// The decoder will select linear for HDR formats and numeric data, and sRGB for display formats. + /// + Auto + } + + /// + /// Controls how transparency (alpha channel) is handled during decoding. + /// + public enum DecoderAlphaHandling { /// - /// If true, when decoding from R8 raw format, - /// output pixels will have all colors set to the same value (greyscale). - /// Default is true. (Does not apply to BC4 format.) + /// Decode alpha channel exactly as stored without modification. + /// Best option for most cases when you want to preserve the original data. /// - public bool RedAsLuminance { get; set; } = true; + KeepAsIs, + /// + /// Convert from premultiplied alpha (where color channels are already multiplied by alpha) to straight alpha. + /// Use this when preparing textures for storing as conventional image data or when working with photo/UI editing software. + /// Most GPU texture formats (dds, ktx) are premultiplied alpha by default, while most standard image formats (png, etc..) are straight alpha. + /// + Unpremultiply + } + + /// + /// A class for the decoder output options. + /// + public class DecoderOutputOptions + { /// /// The color channel to populate with the values of a BC4 block. /// - public ColorComponent Bc4Component { get; set; } = ColorComponent.R; + public ColorComponent Bc4Component { get; set; } = ColorComponent.RGB; /// /// The color channel to populate with the values of the first BC5 block. @@ -29,5 +100,31 @@ public class DecoderOutputOptions /// The color channel to populate with the values of the second BC5 block. /// public ColorComponent Bc5Component2 { get; set; } = ColorComponent.G; + + /// + /// The color channel to populate with the values calculated from the first and second BC5 blocks. + /// z = Sqrt(1 - x^2 - y^2) + /// + public ColorComponent Bc5ComponentCalculated { get; set; } = ColorComponent.None; + + /// + /// Controls how the color space of the input data should be interpreted. + /// + public InputColorSpaceAssumption InputColorSpace { get; set; } = InputColorSpaceAssumption.Auto; + + /// + /// Controls the target color space for the decoded output. + /// + public OutputColorSpaceTarget OutputColorSpace { get; set; } = OutputColorSpaceTarget.ProcessLinearPreserveColorSpace; + + /// + /// Controls how transparency (alpha channel) is handled during decoding. + /// + public DecoderAlphaHandling AlphaHandling { get; set; } = DecoderAlphaHandling.KeepAsIs; + + /// + /// Whether to rescale signed normalized values [-1, 1] to unsigned normalized values [0, 1], when applicable. + /// + public bool RescaleSnormToUnorm { get; set; } = true; } } diff --git a/BCnEnc.Net/Decoder/RawDecoder.cs b/BCnEnc.Net/Decoder/RawDecoder.cs index 880485d..ab19dd1 100644 --- a/BCnEnc.Net/Decoder/RawDecoder.cs +++ b/BCnEnc.Net/Decoder/RawDecoder.cs @@ -1,191 +1,40 @@ using System; +using System.Runtime.CompilerServices; using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; +using CommunityToolkit.HighPerformance; namespace BCnEncoder.Decoder { - internal interface IRawDecoder + internal class RawDecoder : IBcDecoder where TColor : unmanaged, IColor { - ColorRgba32[] Decode(ReadOnlyMemory data, OperationContext context); - } - - /// - /// A class to decode data to R components. - /// - public class RawRDecoder : IRawDecoder - { - private readonly bool redAsLuminance; - - /// - /// Create a new instance of . - /// - /// If the decoded component should be used as the red component or luminance. - public RawRDecoder(bool redAsLuminance) - { - this.redAsLuminance = redAsLuminance; - } - /// - /// Decode the data to color components. - /// - /// The data to decode. - /// The context of the current operation. - /// The decoded color components. - public ColorRgba32[] Decode(ReadOnlyMemory data, OperationContext context) + /// + public byte[] Decode(ReadOnlyMemory data, int width, int height, OperationContext context) { - var output = new ColorRgba32[data.Length]; - - // HINT: Ignoring parallel execution since we wouldn't gain performance from it. - - var span = data.Span; - for (var i = 0; i < output.Length; i++) + if (data.Length != width * height * Unsafe.SizeOf()) { - context.CancellationToken.ThrowIfCancellationRequested(); - - if (redAsLuminance) - { - output[i].r = span[i]; - output[i].g = span[i]; - output[i].b = span[i]; - } - else - { - output[i].r = span[i]; - output[i].g = 0; - output[i].b = 0; - } - - output[i].a = 255; + throw new ArgumentException("Input data buffer incorrectly sized!"); } - return output; - } - } - - /// - /// A class to decode data to RG components. - /// - public class RawRgDecoder : IRawDecoder - { - /// - /// Decode the data to color components. - /// - /// The data to decode. - /// The context of the current operation. - /// The decoded color components. - public ColorRgba32[] Decode(ReadOnlyMemory data, OperationContext context) - { - var output = new ColorRgba32[data.Length / 2]; - - // HINT: Ignoring parallel execution since we wouldn't gain performance from it. - - var span = data.Span; - for (var i = 0; i < output.Length; i++) - { - context.CancellationToken.ThrowIfCancellationRequested(); - - output[i].r = span[i * 2]; - output[i].g = span[i * 2 + 1]; - output[i].b = 0; - output[i].a = 255; - } - - return output; - } - } - - /// - /// A class to decode data to RGB components. - /// - public class RawRgbDecoder : IRawDecoder - { - /// - /// Decode the data to color components. - /// - /// The data to decode. - /// The context of the current operation. - /// The decoded color components. - public ColorRgba32[] Decode(ReadOnlyMemory data, OperationContext context) - { - var output = new ColorRgba32[data.Length / 3]; - - // HINT: Ignoring parallel execution since we wouldn't gain performance from it. - - var span = data.Span; - for (var i = 0; i < output.Length; i++) - { - context.CancellationToken.ThrowIfCancellationRequested(); - - output[i].r = span[i * 3]; - output[i].g = span[i * 3 + 1]; - output[i].b = span[i * 3 + 2]; - output[i].a = 255; - } + var bytes = data.Cast().ConvertToAsBytes(context.ColorConversionMode); - return output; + context.Progress?.Report(width * height); + return bytes; } - } - /// - /// A class to decode data to RGBA components. - /// - public class RawRgbaDecoder : IRawDecoder - { - /// - /// Decode the data to color components. - /// - /// The data to decode. - /// The context of the current operation. - /// The decoded color components. - public ColorRgba32[] Decode(ReadOnlyMemory data, OperationContext context) + /// + public ColorRgbaFloat[] DecodeColor(ReadOnlyMemory data, int width, int height, OperationContext context) { - var output = new ColorRgba32[data.Length / 4]; - - // HINT: Ignoring parallel execution since we wouldn't gain performance from it. - - var span = data.Span; - for (var i = 0; i < output.Length; i++) + if (data.Length != width * height * Unsafe.SizeOf()) { - context.CancellationToken.ThrowIfCancellationRequested(); - - output[i].r = span[i * 4]; - output[i].g = span[i * 4 + 1]; - output[i].b = span[i * 4 + 2]; - output[i].a = span[i * 4 + 3]; + throw new ArgumentException("Input data buffer incorrectly sized!"); } - return output; - } - } - - /// - /// A class to decode data to BGRA components. - /// - public class RawBgraDecoder : IRawDecoder - { - /// - /// Decode the data to color components. - /// - /// The data to decode. - /// The context of the current operation. - /// The decoded color components. - public ColorRgba32[] Decode(ReadOnlyMemory data, OperationContext context) - { - var output = new ColorRgba32[data.Length / 4]; - - // HINT: Ignoring parallel execution since we wouldn't gain performance from it. - - var span = data.Span; - for (var i = 0; i < output.Length; i++) - { - context.CancellationToken.ThrowIfCancellationRequested(); - - output[i].b = span[i * 4]; - output[i].g = span[i * 4 + 1]; - output[i].r = span[i * 4 + 2]; - output[i].a = span[i * 4 + 3]; - } + var colors = data.Cast().ConvertTo(context.ColorConversionMode); - return output; + context.Progress?.Report(width * height); + return colors; } } } diff --git a/BCnEnc.Net/Encoder/AtcBlockEncoder.cs b/BCnEnc.Net/Encoder/AtcBlockEncoder.cs index 2c3dde2..9a26a8e 100644 --- a/BCnEnc.Net/Encoder/AtcBlockEncoder.cs +++ b/BCnEnc.Net/Encoder/AtcBlockEncoder.cs @@ -1,29 +1,30 @@ +using System; using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder { - internal unsafe class AtcBlockEncoder : BaseBcBlockEncoder + internal unsafe class AtcBlockEncoder : BaseBcBlockEncoder { - private readonly Bc1BlockEncoder bc1BlockEncoder; + private static readonly Bc1BlockEncoder bc1BlockEncoder = new Bc1BlockEncoder(false, false); + public AtcBlockEncoder() { - bc1BlockEncoder = new Bc1BlockEncoder(); } - public override AtcBlock EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality) + public override AtcBlock EncodeBlock(in RgbEncodingContext context) { var atcBlock = new AtcBlock(); // EncodeBlock with BC1 first - var bc1Block = bc1BlockEncoder.EncodeBlock(block, quality); + Bc1Block bc1Block = bc1BlockEncoder.EncodeBlock(context); // Atc specific modifications to BC1 - // According to http://www.guildsoftware.com/papers/2012.Converting.DXTC.to.Atc.pdf + // According to http://www.guildsoftware.com/papers/2012.Converting.DXTC.to.ATC.pdf // Change color0 from rgb565 to rgb555 with method 0 - atcBlock.color0 = new ColorRgb555(bc1Block.color0.R, bc1Block.color0.G, bc1Block.color0.B); + atcBlock.color0 = new ColorB5G5R5M1Packed(bc1Block.color0.R, bc1Block.color0.G, bc1Block.color0.B); atcBlock.color1 = bc1Block.color1; // Remap color indices from BC1 to ATC @@ -35,24 +36,9 @@ public override AtcBlock EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality return atcBlock; } - - public override GlInternalFormat GetInternalFormat() - { - return GlInternalFormat.GlCompressedRgbAtc; - } - - public override GlFormat GetBaseInternalFormat() - { - return GlFormat.GlRgb; - } - - public override DxgiFormat GetDxgiFormat() - { - return DxgiFormat.DxgiFormatAtcExt; - } } - internal class AtcExplicitAlphaBlockEncoder : BaseBcBlockEncoder + internal class AtcExplicitAlphaBlockEncoder : BaseBcBlockEncoder { private readonly AtcBlockEncoder atcBlockEncoder; @@ -61,15 +47,15 @@ public AtcExplicitAlphaBlockEncoder() atcBlockEncoder = new AtcBlockEncoder(); } - public override AtcExplicitAlphaBlock EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality) + public override AtcExplicitAlphaBlock EncodeBlock(in RgbEncodingContext context) { - var atcBlock = atcBlockEncoder.EncodeBlock(block, quality); + var atcBlock = atcBlockEncoder.EncodeBlock(context); // EncodeBlock alpha var bc2AlphaBlock = new Bc2AlphaBlock(); for (var i = 0; i < 16; i++) { - bc2AlphaBlock.SetAlpha(i, block[i].a); + bc2AlphaBlock.SetAlpha(i, context.RawBlock[i].a); } return new AtcExplicitAlphaBlock @@ -78,24 +64,9 @@ public override AtcExplicitAlphaBlock EncodeBlock(RawBlock4X4Rgba32 block, Compr colors = atcBlock }; } - - public override GlInternalFormat GetInternalFormat() - { - return GlInternalFormat.GlCompressedRgbaAtcExplicitAlpha; - } - - public override GlFormat GetBaseInternalFormat() - { - return GlFormat.GlRgba; - } - - public override DxgiFormat GetDxgiFormat() - { - return DxgiFormat.DxgiFormatAtcExplicitAlphaExt; - } } - internal class AtcInterpolatedAlphaBlockEncoder : BaseBcBlockEncoder + internal class AtcInterpolatedAlphaBlockEncoder : BaseBcBlockEncoder { private readonly Bc4ComponentBlockEncoder bc4BlockEncoder; private readonly AtcBlockEncoder atcBlockEncoder; @@ -106,10 +77,10 @@ public AtcInterpolatedAlphaBlockEncoder() atcBlockEncoder = new AtcBlockEncoder(); } - public override AtcInterpolatedAlphaBlock EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality) + public override AtcInterpolatedAlphaBlock EncodeBlock(in RgbEncodingContext context) { - var bc4Block = bc4BlockEncoder.EncodeBlock(block, quality); - var atcBlock = atcBlockEncoder.EncodeBlock(block, quality); + var bc4Block = bc4BlockEncoder.EncodeBlock(context.RawBlock, context.Quality); + var atcBlock = atcBlockEncoder.EncodeBlock(context); return new AtcInterpolatedAlphaBlock { @@ -117,20 +88,5 @@ public override AtcInterpolatedAlphaBlock EncodeBlock(RawBlock4X4Rgba32 block, C colors = atcBlock }; } - - public override GlInternalFormat GetInternalFormat() - { - return GlInternalFormat.GlCompressedRgbaAtcInterpolatedAlpha; - } - - public override GlFormat GetBaseInternalFormat() - { - return GlFormat.GlRgba; - } - - public override DxgiFormat GetDxgiFormat() - { - return DxgiFormat.DxgiFormatAtcInterpolatedAlphaExt; - } } } diff --git a/BCnEnc.Net/Encoder/BaseBcBlockEncoder.cs b/BCnEnc.Net/Encoder/BaseBcBlockEncoder.cs index df5b62b..882865c 100644 --- a/BCnEnc.Net/Encoder/BaseBcBlockEncoder.cs +++ b/BCnEnc.Net/Encoder/BaseBcBlockEncoder.cs @@ -4,19 +4,49 @@ using System.Threading; using System.Threading.Tasks; using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; +using BCnEncoder.Shared.Colors; +using CommunityToolkit.HighPerformance; namespace BCnEncoder.Encoder { - internal abstract class BaseBcBlockEncoder : IBcBlockEncoder where T : unmanaged where TBlock : unmanaged + internal abstract class BaseBcBlockEncoder : IBcBlockEncoder + where TEncodedBlock: unmanaged + where TEncodingContext: struct, IBlockEncodingContext { private static readonly object lockObj = new object(); - public byte[] Encode(TBlock[] blocks, int blockWidth, int blockHeight, CompressionQuality quality, OperationContext context) + public int GetBlockSize() + { + return Unsafe.SizeOf(); + } + + public abstract TEncodedBlock EncodeBlock(in TEncodingContext context); + + public virtual void EncodeBlocks(ReadOnlySpan blocks, Span outputBlocks, OperationContext context) { - var outputData = new byte[blockWidth * blockHeight * Unsafe.SizeOf()]; + for (var i = 0; i < blocks.Length; i++) + { + // EncodeBlock + TEncodingContext encContext = BlockEncodingContextHelpers.Create(blocks[i], context); + outputBlocks[i] = EncodeBlock(encContext); + } + } + + /// + public byte[] Encode( + ReadOnlyMemory pixels, + int width, + int height, + OperationContext context) + { + var blocks = ImageToBlocks.ImageTo4X4(pixels, width, height, out var blocksWidth, out var blocksHeight); + return EncodeBlocks(blocks, blocksWidth, blocksHeight, context); + } + + public byte[] EncodeBlocks(RawBlock4X4RgbaFloat[] blocks, int blockWidth, int blockHeight, OperationContext context) + { + var outputData = new byte[blockWidth * blockHeight * Unsafe.SizeOf()]; - var currentBlocks = 0; if (context.IsParallel) { var options = new ParallelOptions @@ -24,54 +54,47 @@ public byte[] Encode(TBlock[] blocks, int blockWidth, int blockHeight, Compressi CancellationToken = context.CancellationToken, MaxDegreeOfParallelism = context.TaskCount }; - Parallel.For(0, blocks.Length, options, i => - { - var outputBlocks = MemoryMarshal.Cast(outputData); - outputBlocks[i] = EncodeBlock(blocks[i], quality); + Parallel.For(0, blockHeight, options, y => + { + var outputBlocks = MemoryMarshal.Cast(outputData).Slice(y * blockWidth, blockWidth); + var inputBlocks = blocks.AsSpan().Slice(y * blockWidth, blockWidth); - if (context.Progress != null) - { - lock (lockObj) - { - context.Progress.Report(++currentBlocks); - } - } - }); + EncodeBlocks(inputBlocks, outputBlocks, context); + + if (context.Progress != null) + { + lock (lockObj) + { + context.Progress.Report(blockWidth); + } + } + }); } else { - var outputBlocks = MemoryMarshal.Cast(outputData); - for (var i = 0; i < blocks.Length; i++) + for (var y = 0; y < blockHeight; y++) { context.CancellationToken.ThrowIfCancellationRequested(); - outputBlocks[i] = EncodeBlock(blocks[i], quality); + var outputBlocks = MemoryMarshal.Cast(outputData).Slice(y * blockWidth, blockWidth); + var inputBlocks = blocks.AsSpan().Slice(y * blockWidth, blockWidth); + + EncodeBlocks(inputBlocks, outputBlocks, context); - context.Progress?.Report(++currentBlocks); + context.Progress?.Report(blockWidth); } } return outputData; } - public void EncodeBlock(TBlock block, CompressionQuality quality, Span output) + public void EncodeBlocks(ReadOnlySpan blocks, Span output, OperationContext context) { - if (output.Length != Unsafe.SizeOf()) + if (output.Length != Unsafe.SizeOf() * blocks.Length) { throw new Exception("Cannot encode block! Output buffer is not the correct size."); } - var encoded = EncodeBlock(block, quality); - MemoryMarshal.Cast(output)[0] = encoded; + EncodeBlocks(blocks, MemoryMarshal.Cast(output), context); } - - public abstract GlInternalFormat GetInternalFormat(); - public abstract GlFormat GetBaseInternalFormat(); - public abstract DxgiFormat GetDxgiFormat(); - public int GetBlockSize() - { - return Unsafe.SizeOf(); - } - - public abstract T EncodeBlock(TBlock block, CompressionQuality quality); } } diff --git a/BCnEnc.Net/Encoder/Bc1BlockEncoder.cs b/BCnEnc.Net/Encoder/Bc1BlockEncoder.cs index 2904ede..0b40536 100644 --- a/BCnEnc.Net/Encoder/Bc1BlockEncoder.cs +++ b/BCnEnc.Net/Encoder/Bc1BlockEncoder.cs @@ -1,472 +1,625 @@ using System; +using System.Diagnostics; +using System.Numerics; using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder { - internal class Bc1BlockEncoder : BaseBcBlockEncoder + internal class Bc1BlockEncoder : BaseBcBlockEncoder { - public override Bc1Block EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality) - { - switch (quality) - { - case CompressionQuality.Fast: - return Bc1BlockEncoderFast.EncodeBlock(block); - case CompressionQuality.Balanced: - return Bc1BlockEncoderBalanced.EncodeBlock(block); - case CompressionQuality.BestQuality: - return Bc1BlockEncoderSlow.EncodeBlock(block); - - default: - throw new ArgumentOutOfRangeException(nameof(quality), quality, null); - } - } + public bool UseColorModeSwitch { get; private set; } + public bool BlackModeIsTransparent { get; private set; } - public override GlInternalFormat GetInternalFormat() + public Bc1BlockEncoder(bool useColorModeSwitch, bool blackModeIsTransparent) { - return GlInternalFormat.GlCompressedRgbS3TcDxt1Ext; - } + UseColorModeSwitch = useColorModeSwitch; + BlackModeIsTransparent = blackModeIsTransparent; - public override GlFormat GetBaseInternalFormat() - { - return GlFormat.GlRgb; + if (BlackModeIsTransparent && !UseColorModeSwitch) + throw new ArgumentException("BlackModeIsTransparent is only valid when UseColorModeSwitch is true."); } - public override DxgiFormat GetDxgiFormat() + public override Bc1Block EncodeBlock(in RgbEncodingContext context) { - return DxgiFormat.DxgiFormatBc1Unorm; + return EncodeBlockImpl(context); } #region Encoding private stuff - - private static Bc1Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0, ColorRgb565 color1, out float error, float rWeight = 0.3f, float gWeight = 0.6f, float bWeight = 0.1f) + private Bc1Block EncodeBlockImpl(in RgbEncodingContext context) { - var output = new Bc1Block(); + Bc1Block initialBlock = GetInitialBlock(context); - var pixels = rawBlock.AsSpan; + // TODO: Optimize endpoints - output.color0 = color0; - output.color1 = color1; - - var c0 = color0.ToColorRgb24(); - var c1 = color1.ToColorRgb24(); - - ReadOnlySpan colors = output.HasAlphaOrBlack ? - stackalloc ColorRgb24[] { - c0, - c1, - c0.InterpolateHalf(c1), - new ColorRgb24(0, 0, 0) - } : stackalloc ColorRgb24[] { - c0, - c1, - c0.InterpolateThird(c1, 1), - c0.InterpolateThird(c1, 2) - }; - - error = 0; - for (var i = 0; i < 16; i++) - { - var color = pixels[i]; - output[i] = ColorChooser.ChooseClosestColor4(colors, color, rWeight, gWeight, bWeight, out var e); - error += e; - } - - return output; + return initialBlock; } - - #endregion - - #region Encoders - - private static class Bc1BlockEncoderFast + private Bc1Block GetInitialBlock(in RgbEncodingContext context) { + int blacks = 0; + bool hasTransparent = BlackModeIsTransparent && context.RawBlock.HasTransparentPixels(context.AlphaThreshold); + Vector4 mean, pa; + Vector4 min, max; - internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) - { - var output = new Bc1Block(); - - var pixels = rawBlock.AsSpan; - - RgbBoundingBox.Create565(pixels, out var min, out var max); - - var c0 = max; - var c1 = min; + PcaVectors2.Create(context.PerceptualBlock.AsSpan, context.Weights, out mean, out pa, out min, out max); - output = TryColors(rawBlock, c0, c1, out var error); + var (c0, c1) = QuantizeColors(context, false, min, max); - return output; - } - } - - private static class Bc1BlockEncoderBalanced - { - private const int MaxTries = 24 * 2; - private const float ErrorThreshold = 0.05f; + Bc1Block noBlacksBlock = TryColors(context, c0, c1, out float noBlacksError); - internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) + if (UseColorModeSwitch) { - var pixels = rawBlock.AsSpan; + if (BlackModeIsTransparent) + blacks = PcaVectors2.CreateIgnoreTransparent(context.PerceptualBlock.AsSpan, context.Weights, context.AlphaThreshold, out mean, out pa, out min, out max); + else + blacks = PcaVectors2.CreateIgnoreBlack(context.PerceptualBlock.AsSpan, context.Weights, out mean, out pa, out min, out max); - PcaVectors.Create(pixels, out var mean, out var pa); - PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max); + var (c0b, c1b) = QuantizeColors(context, true, min, max); + Bc1Block blacksBlock = TryColors(context, c0b, c1b, out float blacksError); - var c0 = max; - var c1 = min; - - if (c0.data < c1.data) - { - var c = c0; - c0 = c1; - c1 = c; - } - - var best = TryColors(rawBlock, c0, c1, out var bestError); - - for (var i = 0; i < MaxTries; i++) + if (hasTransparent || blacksError < noBlacksError) { - var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); - - if (newC0.data < newC1.data) - { - var c = newC0; - newC0 = newC1; - newC1 = c; - } - - var block = TryColors(rawBlock, newC0, newC1, out var error); - - if (error < bestError) - { - best = block; - bestError = error; - c0 = newC0; - c1 = newC1; - } - - if (bestError < ErrorThreshold) - { - break; - } + return blacksBlock; } - - return best; } - } - - private static class Bc1BlockEncoderSlow - { - private const int MaxTries = 9999; - private const float ErrorThreshold = 0.01f; - - internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) - { - var pixels = rawBlock.AsSpan; - - PcaVectors.Create(pixels, out var mean, out var pa); - PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max); - - var c0 = max; - var c1 = min; - - if (c0.data < c1.data) - { - var c = c0; - c0 = c1; - c1 = c; - } - - var best = TryColors(rawBlock, c0, c1, out var bestError); - var lastChanged = 0; - - for (var i = 0; i < MaxTries; i++) - { - var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); - - if (newC0.data < newC1.data) - { - var c = newC0; - newC0 = newC1; - newC1 = c; - } - - var block = TryColors(rawBlock, newC0, newC1, out var error); - - lastChanged++; - - if (error < bestError) - { - best = block; - bestError = error; - c0 = newC0; - c1 = newC1; - lastChanged = 0; - } - - if (bestError < ErrorThreshold || lastChanged > ColorVariationGenerator.VarPatternCount) - { - break; - } - } - - return best; - } + return noBlacksBlock; } - #endregion - } - - internal class Bc1AlphaBlockEncoder : BaseBcBlockEncoder - { - public override Bc1Block EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality) + private static (ColorB5G6R5Packed, ColorB5G6R5Packed) QuantizeColors(in RgbEncodingContext context, + bool useBlackMode, Vector4 min, Vector4 max) { - switch (quality) + // Convert to sRGB + if (context.ColorConversionMode == ColorConversionMode.LinearToSrgb) { - case CompressionQuality.Fast: - return Bc1AlphaBlockEncoderFast.EncodeBlock(block); - case CompressionQuality.Balanced: - return Bc1AlphaBlockEncoderBalanced.EncodeBlock(block); - case CompressionQuality.BestQuality: - return Bc1AlphaBlockEncoderSlow.EncodeBlock(block); - - default: - throw new ArgumentOutOfRangeException(nameof(quality), quality, null); + min = ColorSpace.Srgb.ToSrgb(min); + max = ColorSpace.Srgb.ToSrgb(max); } - } - - public override GlInternalFormat GetInternalFormat() - { - return GlInternalFormat.GlCompressedRgbaS3TcDxt1Ext; - } - public override GlFormat GetBaseInternalFormat() - { - return GlFormat.GlRgba; - } + var color0 = new ColorB5G6R5Packed(min); + var color1 = new ColorB5G6R5Packed(max); - public override DxgiFormat GetDxgiFormat() - { - return DxgiFormat.DxgiFormatBc1Unorm; + // Color0 <= Color1 means block uses alpha or black pixels in index 3 + return useBlackMode switch + { + false when color0.data < color1.data => (color1, color0), + true when color0.data > color1.data => (color1, color0), + _ => (color0, color1) + }; } - #region Encoding private stuff - - private static Bc1Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0, ColorRgb565 color1, out float error, float rWeight = 0.3f, float gWeight = 0.6f, float bWeight = 0.1f) + private Bc1Block TryColors(in RgbEncodingContext context, ColorB5G6R5Packed color0, ColorB5G6R5Packed color1, out float error) { var output = new Bc1Block(); - var pixels = rawBlock.AsSpan; + // Reference pixels in perceptual space + var referencePixels = context.PerceptualBlock.AsSpan; output.color0 = color0; output.color1 = color1; - var c0 = color0.ToColorRgb24(); - var c1 = color1.ToColorRgb24(); - - var hasAlpha = output.HasAlphaOrBlack; - - ReadOnlySpan colors = hasAlpha ? - stackalloc ColorRgb24[] { - c0, - c1, - c0.InterpolateHalf(c1), - new ColorRgb24(0, 0, 0) - } : stackalloc ColorRgb24[] { - c0, - c1, - c0.InterpolateThird(c1, 1), - c0.InterpolateThird(c1, 2) - }; + // Quantized colors in final space + var c0 = color0.ToColorRgbaFloat().ToVector4(); + var c1 = color1.ToColorRgbaFloat().ToVector4(); + + bool hasAlphaOrBlack = output.HasAlphaOrBlack; + + Span colors = hasAlphaOrBlack ? + stackalloc Vector4[] { + c0, + c1, + c0.InterpolateHalf(c1), + BlackModeIsTransparent ? Vector4.Zero : new Vector4(0, 0, 0, 1) + } : stackalloc Vector4[] { + c0, + c1, + c0.InterpolateThird(c1, 1), + c0.InterpolateThird(c1, 2) + }; + + // Convert from sRGB back to linear (Inverted if here is intended) + if (context.ColorConversionMode == ColorConversionMode.LinearToSrgb) + { + colors[0] = ColorSpace.Srgb.ToLrgb(colors[0]); + colors[1] = ColorSpace.Srgb.ToLrgb(colors[1]); + colors[2] = ColorSpace.Srgb.ToLrgb(colors[2]); + colors[3] = ColorSpace.Srgb.ToLrgb(colors[3]); + } + + // Convert to perceptual + colors[0] = context.Weights.TransformToPerceptual(colors[0]); + colors[1] = context.Weights.TransformToPerceptual(colors[1]); + colors[2] = context.Weights.TransformToPerceptual(colors[2]); + colors[3] = context.Weights.TransformToPerceptual(colors[3]); error = 0; for (var i = 0; i < 16; i++) { - var color = pixels[i]; - output[i] = ColorChooser.ChooseClosestColor4AlphaCutoff(colors, color, rWeight, gWeight, bWeight, - 128, hasAlpha, out var e); + var color = referencePixels[i]; + float e; + if (BlackModeIsTransparent) + output[i] = ColorChooser.ChooseClosestRgbColor4AlphaCutoff(colors, color, context.Weights, context.AlphaThreshold, hasAlphaOrBlack, out e); + else + output[i] = ColorChooser.ChooseClosestRgbColor4(colors, color, context.Weights, out e); error += e; } return output; } - #endregion #region Encoders + // + // internal static class Bc1BlockEncoderFast + // { + // internal static Bc1Block EncodeBlock(RawBlock4X4RgbaFloat rawBlock, OperationContext context, bool useColorModeSwitch) + // { + // var pixels = rawBlock.AsSpan; + // + // int blacks = 0; + // Vector4 mean, pa; + // ColorB5G6R5Packed min, max; + // if (useColorModeSwitch) + // { + // blacks = PcaVectors2.CreateIgnoreBlacks(pixels, out mean, out pa, context.Weights, out min, out max); + // } + // else + // { + // PcaVectors2.Create(pixels, out mean, out pa, context.Weights, out min, out max); + // } + // + // // PcaVectors2.GetMinMaxColor565(pixels, mean, pa, out min, out max); + // + // var c0 = max; + // var c1 = min; + // + // (c0, c1) = useColorModeSwitch switch + // { + // true when c0.data < c1.data && blacks == 0 => (c1, c0), + // true when c0.data > c1.data && blacks > 0 => (c1, c0), + // false when c0.data < c1.data => (c1, c0), + // _ => (c0, c1) + // }; + // + // var best = TryColors(rawBlock, c0, c1, useColorModeSwitch, out var bestError, context.Weights); + // + // return best; + // } + // } + // + // internal static class Bc1BlockEncoderBalanced + // { + // private const int MaxTries = 24 * 2; + // private const float ErrorThreshold = 0.05f; + // + // internal static Bc1Block EncodeBlock(RawBlock4X4RgbaFloat rawBlock, OperationContext context, bool useColorModeSwitch) + // { + // var pixels = rawBlock.AsSpan; + // + // int blacks = 0; + // Vector4 mean, pa; + // + // PcaVectors2.Create(pixels, out mean, out pa, context.Weights, out var ep0, out var ep1); + // // PcaVectors2.GetMinMaxColor565(pixels, mean, pa, out var ep0, out var ep1); + // EndpointOptimizer.Optimize565Endpoints(pixels, mean, pa, ref ep0, ref ep1, context.Weights, false); + // + // if (ep0.data < ep1.data) // swap endpoints + // (ep0, ep1) = (ep1, ep0); + // + // var noBlacksBlock = TryColors(rawBlock, ep0, ep1, true, out var noBlacksError, context.Weights); + // + // if (useColorModeSwitch) + // { + // blacks = PcaVectors2.CreateIgnoreBlacks(pixels, out mean, out pa, context.Weights, out ep0, out ep1); + // // + // // if (blacks == 0) + // // return noBlacksBlock; + // + // // PcaVectors2.GetMinMaxColor565(pixels, mean, pa, out ep0, out ep1); + // EndpointOptimizer.Optimize565Endpoints(pixels, mean, pa, ref ep0, ref ep1, context.Weights, true); + // + // if (ep0.data > ep1.data) // swap endpoints + // (ep0, ep1) = (ep1, ep0); + // + // var useBlacksBlock = TryColors(rawBlock, ep0, ep1, true, out var useBlacksError, context.Weights); + // + // if (useBlacksError < noBlacksError) + // return useBlacksBlock; + // } + // + // return noBlacksBlock; + // } + // } + // + // internal static class Bc1BlockEncoderSlow + // { + // private const int MaxTries = 9999; + // private const float ErrorThreshold = 0.01f; + // + // internal static Bc1Block EncodeBlock(RawBlock4X4RgbaFloat rawBlock, OperationContext context, bool useColorModeSwitch) + // { + // var pixels = rawBlock.AsSpan; + // + // int blacks = 0; + // Vector4 mean, pa; + // + // if (useColorModeSwitch) + // { + // blacks = PcaVectors.CreateIgnoreBlacks(pixels, out mean, out pa, context.Weights); + // } + // else + // { + // PcaVectors.Create(pixels, out mean, out pa, context.Weights); + // } + // + // PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max); + // + // var c0 = max; + // var c1 = min; + // + // (c0, c1) = useColorModeSwitch switch + // { + // true when c0.data < c1.data && blacks == 0 => (c1, c0), + // true when c0.data > c1.data && blacks > 0 => (c1, c0), + // false when c0.data < c1.data => (c1, c0), + // _ => (c0, c1) + // }; + // + // var best = TryColors(rawBlock, c0, c1, useColorModeSwitch, out var bestError, context.Weights); + // + // var lastChanged = 0; + // + // for (var i = 0; i < MaxTries; i++) + // { + // var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); + // + // (newC0, newC1) = useColorModeSwitch switch + // { + // true when newC0.data < newC1.data && blacks == 0 => (newC1, newC0), + // true when newC0.data > newC1.data && blacks > 0 => (newC1, newC0), + // false when newC0.data < newC1.data => (newC1, newC0), + // _ => (newC0, newC1) + // }; + // + // var block = TryColors(rawBlock, newC0, newC1, useColorModeSwitch, out var error, context.Weights); + // + // lastChanged++; + // + // if (error < bestError) + // { + // best = block; + // bestError = error; + // c0 = newC0; + // c1 = newC1; + // lastChanged = 0; + // } + // + // if (bestError < ErrorThreshold || lastChanged > ColorVariationGenerator.VarPatternCount) + // { + // break; + // } + // } + // + // return best; + // } + // } - private static class Bc1AlphaBlockEncoderFast - { - - internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) - { - var output = new Bc1Block(); - - var pixels = rawBlock.AsSpan; - - var hasAlpha = rawBlock.HasTransparentPixels(); + #endregion + } - RgbBoundingBox.Create565AlphaCutoff(pixels, out var min, out var max); + internal class Bc1AlphaBlockEncoder : BaseBcBlockEncoder + { + public override Bc1Block EncodeBlock(in RgbEncodingContext context) + { + bool hasAlpha = context.RawBlockVec.HasTransparentPixels(context.AlphaThreshold); - var c0 = max; - var c1 = min; + Vector4 mean, pa; + Vector4 min, max; - if (hasAlpha && c0.data > c1.data) - { - var c = c0; - c0 = c1; - c1 = c; - } + if (hasAlpha) + { + PcaVectors2.CreateIgnoreTransparent(context.PerceptualBlock.AsSpan, context.Weights, context.AlphaThreshold, + out mean, out pa, out min, out max); + } + else + { + PcaVectors2.Create(context.PerceptualBlock.AsSpan, context.Weights, out mean, out pa, out min, out max); + } - output = TryColors(rawBlock, c0, c1, out var error); + var (c0, c1) = QuantizeColors(context, hasAlpha, min, max); - return output; - } + return TryColors(context, c0, c1, out float bestError); } - private static class Bc1AlphaBlockEncoderBalanced - { - private const int MaxTries = 24 * 2; - private const float ErrorThreshold = 0.05f; - + #region Encoding private stuff - internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) + private static (ColorB5G6R5Packed, ColorB5G6R5Packed) QuantizeColors(in RgbEncodingContext context, + bool useTransparentMode, Vector4 min, Vector4 max) + { + // Convert to sRGB + if (context.ColorConversionMode == ColorConversionMode.LinearToSrgb) { - var pixels = rawBlock.AsSpan; - - var hasAlpha = rawBlock.HasTransparentPixels(); + min = ColorSpace.Srgb.ToSrgb(min); + max = ColorSpace.Srgb.ToSrgb(max); + } - PcaVectors.Create(pixels, out var mean, out var pa); - PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max); + var color0 = new ColorB5G6R5Packed(min); + var color1 = new ColorB5G6R5Packed(max); - var c0 = max; - var c1 = min; + // Color0 <= Color1 means block uses alpha or black pixels in index 3 + return useTransparentMode switch + { + false when color0.data < color1.data => (color1, color0), + true when color0.data > color1.data => (color1, color0), + _ => (color0, color1) + }; + } - if (!hasAlpha && c0.data < c1.data) - { - var c = c0; - c0 = c1; - c1 = c; - } - else if (hasAlpha && c1.data < c0.data) - { - var c = c0; - c0 = c1; - c1 = c; - } + private static Bc1Block TryColors(in RgbEncodingContext context, ColorB5G6R5Packed color0, ColorB5G6R5Packed color1, out float error) + { + var output = new Bc1Block(); - var best = TryColors(rawBlock, c0, c1, out var bestError); + // Reference pixels in perceptual space + var referencePixels = context.PerceptualBlock.AsSpan; - for (var i = 0; i < MaxTries; i++) - { - var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); - - if (!hasAlpha && newC0.data < newC1.data) - { - var c = newC0; - newC0 = newC1; - newC1 = c; - } - else if (hasAlpha && newC1.data < newC0.data) - { - var c = newC0; - newC0 = newC1; - newC1 = c; - } - - var block = TryColors(rawBlock, newC0, newC1, out var error); - - if (error < bestError) - { - best = block; - bestError = error; - c0 = newC0; - c1 = newC1; - } - - if (bestError < ErrorThreshold) - { - break; - } - } + output.color0 = color0; + output.color1 = color1; - return best; + // Quantized colors in final space + var c0 = color0.ToColorRgbaFloat().ToVector4(); + var c1 = color1.ToColorRgbaFloat().ToVector4(); + + bool hasAlpha = output.HasAlphaOrBlack; + + Span colors = hasAlpha ? + stackalloc Vector4[] { + c0, + c1, + c0.InterpolateHalf(c1), + new Vector4(0, 0, 0, 0) + } : stackalloc Vector4[] { + c0, + c1, + c0.InterpolateThird(c1, 1), + c0.InterpolateThird(c1, 2) + }; + + // Convert from sRGB back to linear (Inverted if here is intended) + if (context.ColorConversionMode == ColorConversionMode.LinearToSrgb) + { + colors[0] = ColorSpace.Srgb.ToLrgb(colors[0]); + colors[1] = ColorSpace.Srgb.ToLrgb(colors[1]); + colors[2] = ColorSpace.Srgb.ToLrgb(colors[2]); + colors[3] = ColorSpace.Srgb.ToLrgb(colors[3]); } - } - private static class Bc1AlphaBlockEncoderSlow - { - private const int MaxTries = 9999; - private const float ErrorThreshold = 0.05f; + // Convert to perceptual + colors[0] = context.Weights.TransformToPerceptual(colors[0]); + colors[1] = context.Weights.TransformToPerceptual(colors[1]); + colors[2] = context.Weights.TransformToPerceptual(colors[2]); + colors[3] = context.Weights.TransformToPerceptual(colors[3]); - internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) + error = 0; + for (var i = 0; i < 16; i++) { - var pixels = rawBlock.AsSpan; - - var hasAlpha = rawBlock.HasTransparentPixels(); - - PcaVectors.Create(pixels, out var mean, out var pa); - PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max); - - var c0 = max; - var c1 = min; + var color = referencePixels[i]; + output[i] = ColorChooser.ChooseClosestRgbColor4AlphaCutoff(colors, color, context.Weights, context.AlphaThreshold, hasAlpha, out var e); + error += e; + } - if (!hasAlpha && c0.data < c1.data) - { - var c = c0; - c0 = c1; - c1 = c; - } - else if (hasAlpha && c1.data < c0.data) - { - var c = c0; - c0 = c1; - c1 = c; - } + return output; + } - var best = TryColors(rawBlock, c0, c1, out var bestError); + #endregion - var lastChanged = 0; - for (var i = 0; i < MaxTries; i++) - { - var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); - - if (!hasAlpha && newC0.data < newC1.data) - { - var c = newC0; - newC0 = newC1; - newC1 = c; - } - else if (hasAlpha && newC1.data < newC0.data) - { - var c = newC0; - newC0 = newC1; - newC1 = c; - } - - var block = TryColors(rawBlock, newC0, newC1, out var error); - - lastChanged++; - - if (error < bestError) - { - best = block; - bestError = error; - c0 = newC0; - c1 = newC1; - lastChanged = 0; - } - - if (bestError < ErrorThreshold || lastChanged > ColorVariationGenerator.VarPatternCount) - { - break; - } - } + #region Encoders - return best; - } - } + // private static class Bc1AlphaBlockEncoderFast + // { + // internal static Bc1Block EncodeBlock(RawBlock4X4RgbaFloat rawBlock, OperationContext context) + // { + // var pixels = rawBlock.AsSpan; + // + // var hasAlpha = rawBlock.HasTransparentPixels(context.AlphaThreshold); + // + // Vector4 mean, pa; + // + // if (hasAlpha) + // { + // PcaVectors.CreateIgnoreTransparent(pixels, out mean, out pa, context.AlphaThreshold, context.Weights); + // } + // else + // { + // PcaVectors.Create(pixels, out mean, out pa, context.Weights); + // } + // + // PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max); + // + // var c0 = max; + // var c1 = min; + // + // if (!hasAlpha && c0.data < c1.data) + // { + // var c = c0; + // c0 = c1; + // c1 = c; + // } + // else if (hasAlpha && c1.data < c0.data) + // { + // var c = c0; + // c0 = c1; + // c1 = c; + // } + // + // var best = TryColors(rawBlock, c0, c1, out var bestError, context); + // + // return best; + // } + // } + // + // private static class Bc1AlphaBlockEncoderBalanced + // { + // private const int MaxTries = 24 * 2; + // private const float ErrorThreshold = 0.05f; + // + // + // internal static Bc1Block EncodeBlock(RawBlock4X4RgbaFloat rawBlock, OperationContext context) + // { + // var pixels = rawBlock.AsSpan; + // + // var hasAlpha = rawBlock.HasTransparentPixels(context.AlphaThreshold); + // + // Vector4 mean, pa; + // + // if (hasAlpha) + // { + // PcaVectors.CreateIgnoreTransparent(pixels, out mean, out pa, context.AlphaThreshold, context.Weights); + // } + // else + // { + // PcaVectors.Create(pixels, out mean, out pa, context.Weights); + // } + // + // PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max); + // + // var c0 = max; + // var c1 = min; + // + // if (!hasAlpha && c0.data < c1.data) + // { + // var c = c0; + // c0 = c1; + // c1 = c; + // } + // else if (hasAlpha && c1.data < c0.data) + // { + // var c = c0; + // c0 = c1; + // c1 = c; + // } + // + // var best = TryColors(rawBlock, c0, c1, out var bestError, context); + // + // for (var i = 0; i < MaxTries; i++) + // { + // var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); + // + // if (!hasAlpha && newC0.data < newC1.data) + // { + // var c = newC0; + // newC0 = newC1; + // newC1 = c; + // } + // else if (hasAlpha && newC1.data < newC0.data) + // { + // var c = newC0; + // newC0 = newC1; + // newC1 = c; + // } + // + // var block = TryColors(rawBlock, newC0, newC1, out var error, context); + // + // if (error < bestError) + // { + // best = block; + // bestError = error; + // c0 = newC0; + // c1 = newC1; + // } + // + // if (bestError < ErrorThreshold) + // { + // break; + // } + // } + // + // return best; + // } + // } + // + // private static class Bc1AlphaBlockEncoderSlow + // { + // private const int MaxTries = 9999; + // private const float ErrorThreshold = 0.05f; + // + // internal static Bc1Block EncodeBlock(RawBlock4X4RgbaFloat rawBlock, OperationContext context) + // { + // var pixels = rawBlock.AsSpan; + // + // var hasAlpha = rawBlock.HasTransparentPixels(context.AlphaThreshold); + // + // Vector4 mean, pa; + // + // if (hasAlpha) + // { + // PcaVectors.CreateIgnoreTransparent(pixels, out mean, out pa, context.AlphaThreshold, context.Weights); + // } + // else + // { + // PcaVectors.Create(pixels, out mean, out pa, context.Weights); + // } + // + // PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max); + // + // var c0 = max; + // var c1 = min; + // + // if (!hasAlpha && c0.data < c1.data) + // { + // var c = c0; + // c0 = c1; + // c1 = c; + // } + // else if (hasAlpha && c1.data < c0.data) + // { + // var c = c0; + // c0 = c1; + // c1 = c; + // } + // + // var best = TryColors(rawBlock, c0, c1, out var bestError, context); + // + // var lastChanged = 0; + // for (var i = 0; i < MaxTries; i++) + // { + // var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); + // + // if (!hasAlpha && newC0.data < newC1.data) + // { + // var c = newC0; + // newC0 = newC1; + // newC1 = c; + // } + // else if (hasAlpha && newC1.data < newC0.data) + // { + // var c = newC0; + // newC0 = newC1; + // newC1 = c; + // } + // + // var block = TryColors(rawBlock, newC0, newC1, out var error, context); + // + // lastChanged++; + // + // if (error < bestError) + // { + // best = block; + // bestError = error; + // c0 = newC0; + // c1 = newC1; + // lastChanged = 0; + // } + // + // if (bestError < ErrorThreshold || lastChanged > ColorVariationGenerator.VarPatternCount) + // { + // break; + // } + // } + // + // return best; + // } + // } #endregion diff --git a/BCnEnc.Net/Encoder/Bc2BlockEncoder.cs b/BCnEnc.Net/Encoder/Bc2BlockEncoder.cs index 33b56af..6f70c55 100644 --- a/BCnEnc.Net/Encoder/Bc2BlockEncoder.cs +++ b/BCnEnc.Net/Encoder/Bc2BlockEncoder.cs @@ -1,201 +1,33 @@ using System; using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder { - internal class Bc2BlockEncoder : BaseBcBlockEncoder + internal class Bc2BlockEncoder : BaseBcBlockEncoder { - public override Bc2Block EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality) - { - switch (quality) - { - case CompressionQuality.Fast: - return Bc2BlockEncoderFast.EncodeBlock(block); - case CompressionQuality.Balanced: - return Bc2BlockEncoderBalanced.EncodeBlock(block); - case CompressionQuality.BestQuality: - return Bc2BlockEncoderSlow.EncodeBlock(block); - - default: - throw new ArgumentOutOfRangeException(nameof(quality), quality, null); - } - } - - public override GlInternalFormat GetInternalFormat() - { - return GlInternalFormat.GlCompressedRgbaS3TcDxt3Ext; - } + private static readonly Bc1BlockEncoder bc1BlockEncoder = new Bc1BlockEncoder(false, false); - public override GlFormat GetBaseInternalFormat() + public override Bc2Block EncodeBlock(in RgbEncodingContext context) { - return GlFormat.GlRgba; - } + Bc1Block colorBlock = bc1BlockEncoder.EncodeBlock(context); - public override DxgiFormat GetDxgiFormat() - { - return DxgiFormat.DxgiFormatBc2Unorm; + return EncodeAlpha(colorBlock, context.RawBlockVec); } - #region Encoding private stuff - - private static Bc2Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0, ColorRgb565 color1, out float error, float rWeight = 0.3f, float gWeight = 0.6f, float bWeight = 0.1f) + private static Bc2Block EncodeAlpha(Bc1Block colorBlock, RawBlock4X4Vector4 rawBlock) { - var output = new Bc2Block(); - var pixels = rawBlock.AsSpan; - output.color0 = color0; - output.color1 = color1; + Bc2Block result = new Bc2Block(); + result.colorBlock = colorBlock; - var c0 = color0.ToColorRgb24(); - var c1 = color1.ToColorRgb24(); - - ReadOnlySpan colors = stackalloc ColorRgb24[] { - c0, - c1, - c0.InterpolateThird(c1, 1), - c0.InterpolateThird(c1, 2) - }; - - error = 0; - for (var i = 0; i < 16; i++) + for (int i = 0; i < pixels.Length; i++) { - var color = pixels[i]; - output.SetAlpha(i, color.a); - output[i] = ColorChooser.ChooseClosestColor4(colors, color, rWeight, gWeight, bWeight, out var e); - error += e; + result.SetAlpha(i, pixels[i].W); } - return output; + return result; } - - - #endregion - - #region Encoders - - private static class Bc2BlockEncoderFast - { - - internal static Bc2Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) - { - var pixels = rawBlock.AsSpan; - - PcaVectors.Create(pixels, out var mean, out var principalAxis); - PcaVectors.GetMinMaxColor565(pixels, mean, principalAxis, out var min, out var max); - - var c0 = max; - var c1 = min; - - var output = TryColors(rawBlock, c0, c1, out var _); - - return output; - } - } - - private static class Bc2BlockEncoderBalanced - { - private const int MaxTries = 24 * 2; - private const float ErrorThreshold = 0.05f; - - internal static Bc2Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) - { - var pixels = rawBlock.AsSpan; - - PcaVectors.Create(pixels, out var mean, out var pa); - PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max); - - var c0 = max; - var c1 = min; - - var best = TryColors(rawBlock, c0, c1, out var bestError); - - for (var i = 0; i < MaxTries; i++) - { - var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); - - var block = TryColors(rawBlock, newC0, newC1, out var error); - - if (error < bestError) - { - best = block; - bestError = error; - c0 = newC0; - c1 = newC1; - } - - if (bestError < ErrorThreshold) - { - break; - } - } - - return best; - } - } - - private static class Bc2BlockEncoderSlow - { - private const int MaxTries = 9999; - private const float ErrorThreshold = 0.01f; - - - internal static Bc2Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) - { - var pixels = rawBlock.AsSpan; - - PcaVectors.Create(pixels, out var mean, out var pa); - PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max); - - var c0 = max; - var c1 = min; - - if (c0.data < c1.data) - { - var c = c0; - c0 = c1; - c1 = c; - } - - var best = TryColors(rawBlock, c0, c1, out var bestError); - - var lastChanged = 0; - - for (var i = 0; i < MaxTries; i++) - { - var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); - - if (newC0.data < newC1.data) - { - var c = newC0; - newC0 = newC1; - newC1 = c; - } - - var block = TryColors(rawBlock, newC0, newC1, out var error); - - lastChanged++; - - if (error < bestError) - { - best = block; - bestError = error; - c0 = newC0; - c1 = newC1; - lastChanged = 0; - } - - if (bestError < ErrorThreshold || lastChanged > ColorVariationGenerator.VarPatternCount) - { - break; - } - } - - return best; - } - } - - #endregion } } diff --git a/BCnEnc.Net/Encoder/Bc3BlockEncoder.cs b/BCnEnc.Net/Encoder/Bc3BlockEncoder.cs index d12e55c..cd99f7e 100644 --- a/BCnEnc.Net/Encoder/Bc3BlockEncoder.cs +++ b/BCnEnc.Net/Encoder/Bc3BlockEncoder.cs @@ -1,208 +1,21 @@ using System; using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder { - internal class Bc3BlockEncoder : BaseBcBlockEncoder + internal class Bc3BlockEncoder : BaseBcBlockEncoder { + private static readonly Bc1BlockEncoder bc1BlockEncoder = new Bc1BlockEncoder(false, false); private static readonly Bc4ComponentBlockEncoder bc4BlockEncoder = new Bc4ComponentBlockEncoder(ColorComponent.A); - public override Bc3Block EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality) + public override Bc3Block EncodeBlock(in RgbEncodingContext context) { - switch (quality) - { - case CompressionQuality.Fast: - return Bc3BlockEncoderFast.EncodeBlock(block); - case CompressionQuality.Balanced: - return Bc3BlockEncoderBalanced.EncodeBlock(block); - case CompressionQuality.BestQuality: - return Bc3BlockEncoderSlow.EncodeBlock(block); + Bc3Block result = new Bc3Block(); + result.colorBlock = bc1BlockEncoder.EncodeBlock(context); + result.alphaBlock = bc4BlockEncoder.EncodeBlock(context.RawBlock, context.Quality); - default: - throw new ArgumentOutOfRangeException(nameof(quality), quality, null); - } + return result; } - - public override GlInternalFormat GetInternalFormat() - { - return GlInternalFormat.GlCompressedRgbaS3TcDxt5Ext; - } - - public override GlFormat GetBaseInternalFormat() - { - return GlFormat.GlRgba; - } - - public override DxgiFormat GetDxgiFormat() - { - return DxgiFormat.DxgiFormatBc3Unorm; - } - - #region Encoding private stuff - - private static Bc3Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0, ColorRgb565 color1, out float error, float rWeight = 0.3f, float gWeight = 0.6f, float bWeight = 0.1f) - { - var output = new Bc3Block(); - - var pixels = rawBlock.AsSpan; - - output.color0 = color0; - output.color1 = color1; - - var c0 = color0.ToColorRgb24(); - var c1 = color1.ToColorRgb24(); - - ReadOnlySpan colors = stackalloc ColorRgb24[] { - c0, - c1, - c0.InterpolateThird(c1, 1), - c0.InterpolateThird(c1, 2) - }; - - error = 0; - for (var i = 0; i < 16; i++) - { - var color = pixels[i]; - output[i] = ColorChooser.ChooseClosestColor4(colors, color, rWeight, gWeight, bWeight, out var e); - error += e; - } - - return output; - } - - #endregion - - #region Encoders - - private static class Bc3BlockEncoderFast - { - internal static Bc3Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) - { - var pixels = rawBlock.AsSpan; - - PcaVectors.Create(pixels, out var mean, out var principalAxis); - PcaVectors.GetMinMaxColor565(pixels, mean, principalAxis, out var min, out var max); - - var c0 = max; - var c1 = min; - - if (c0.data <= c1.data) - { - var c = c0; - c0 = c1; - c1 = c; - } - - var output = TryColors(rawBlock, c0, c1, out _); - output.alphaBlock = bc4BlockEncoder.EncodeBlock(rawBlock, CompressionQuality.Fast); - - return output; - } - } - - private static class Bc3BlockEncoderBalanced - { - private const int MaxTries = 24 * 2; - private const float ErrorThreshold = 0.05f; - - internal static Bc3Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) - { - var pixels = rawBlock.AsSpan; - - PcaVectors.Create(pixels, out var mean, out var pa); - PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max); - - var c0 = max; - var c1 = min; - - var best = TryColors(rawBlock, c0, c1, out var bestError); - - for (var i = 0; i < MaxTries; i++) - { - var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); - - var block = TryColors(rawBlock, newC0, newC1, out var error); - - if (error < bestError) - { - best = block; - bestError = error; - c0 = newC0; - c1 = newC1; - } - - if (bestError < ErrorThreshold) - { - break; - } - } - best.alphaBlock = bc4BlockEncoder.EncodeBlock(rawBlock, CompressionQuality.Balanced); - return best; - } - } - - private static class Bc3BlockEncoderSlow - { - private const int MaxTries = 9999; - private const float ErrorThreshold = 0.01f; - - - internal static Bc3Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) - { - var pixels = rawBlock.AsSpan; - - PcaVectors.Create(pixels, out var mean, out var pa); - PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max); - - var c0 = max; - var c1 = min; - - if (c0.data < c1.data) - { - var c = c0; - c0 = c1; - c1 = c; - } - - var best = TryColors(rawBlock, c0, c1, out var bestError); - - var lastChanged = 0; - - for (var i = 0; i < MaxTries; i++) - { - var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); - - if (newC0.data < newC1.data) - { - var c = newC0; - newC0 = newC1; - newC1 = c; - } - - var block = TryColors(rawBlock, newC0, newC1, out var error); - - lastChanged++; - - if (error < bestError) - { - best = block; - bestError = error; - c0 = newC0; - c1 = newC1; - lastChanged = 0; - } - - if (bestError < ErrorThreshold || lastChanged > ColorVariationGenerator.VarPatternCount) - { - break; - } - } - - best.alphaBlock = bc4BlockEncoder.EncodeBlock(rawBlock, CompressionQuality.BestQuality); - return best; - } - } - #endregion } } diff --git a/BCnEnc.Net/Encoder/Bc4BlockEncoder.cs b/BCnEnc.Net/Encoder/Bc4BlockEncoder.cs index fc52784..9b13634 100644 --- a/BCnEnc.Net/Encoder/Bc4BlockEncoder.cs +++ b/BCnEnc.Net/Encoder/Bc4BlockEncoder.cs @@ -1,11 +1,11 @@ using System; using System.Diagnostics; using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder { - internal class Bc4BlockEncoder : BaseBcBlockEncoder + internal class Bc4BlockEncoder : BaseBcBlockEncoder { private readonly Bc4ComponentBlockEncoder bc4Encoder; @@ -14,30 +14,15 @@ public Bc4BlockEncoder(ColorComponent component) bc4Encoder = new Bc4ComponentBlockEncoder(component); } - public override Bc4Block EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality) + public override Bc4Block EncodeBlock(in RgEncodingContext context) { var output = new Bc4Block { - componentBlock = bc4Encoder.EncodeBlock(block, quality) + componentBlock = bc4Encoder.EncodeBlock(context.RawBlock, context.Quality) }; return output; } - - public override GlInternalFormat GetInternalFormat() - { - return GlInternalFormat.GlCompressedRedRgtc1Ext; - } - - public override GlFormat GetBaseInternalFormat() - { - return GlFormat.GlRed; - } - - public override DxgiFormat GetDxgiFormat() - { - return DxgiFormat.DxgiFormatBc4Unorm; - } } internal class Bc4ComponentBlockEncoder @@ -49,7 +34,7 @@ public Bc4ComponentBlockEncoder(ColorComponent component) this.component = component; } - public Bc4ComponentBlock EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality) + public Bc4ComponentBlock EncodeBlock(RawBlock4X4RgbaFloat block, CompressionQuality quality) { var output = new Bc4ComponentBlock(); @@ -57,7 +42,7 @@ public Bc4ComponentBlock EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality var colors = new byte[pixels.Length]; for (var i = 0; i < pixels.Length; i++) - colors[i] = ComponentHelper.ColorToComponent(pixels[i], component); + colors[i] = ComponentHelper.ColorToComponent(pixels[i].As(), component); switch (quality) { diff --git a/BCnEnc.Net/Encoder/Bc5BlockEncoder.cs b/BCnEnc.Net/Encoder/Bc5BlockEncoder.cs index 6f335b4..147c1cd 100644 --- a/BCnEnc.Net/Encoder/Bc5BlockEncoder.cs +++ b/BCnEnc.Net/Encoder/Bc5BlockEncoder.cs @@ -1,9 +1,9 @@ using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder { - internal class Bc5BlockEncoder : BaseBcBlockEncoder + internal class Bc5BlockEncoder : BaseBcBlockEncoder { private readonly Bc4ComponentBlockEncoder redBlockEncoder; private readonly Bc4ComponentBlockEncoder greenBlockEncoder; @@ -14,28 +14,13 @@ public Bc5BlockEncoder(ColorComponent component1, ColorComponent component2) greenBlockEncoder = new Bc4ComponentBlockEncoder(component2); } - public override Bc5Block EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality) + public override Bc5Block EncodeBlock(in RgEncodingContext context) { return new Bc5Block { - redBlock = redBlockEncoder.EncodeBlock(block, quality), - greenBlock = greenBlockEncoder.EncodeBlock(block, quality) + redBlock = redBlockEncoder.EncodeBlock(context.RawBlock, context.Quality), + greenBlock = greenBlockEncoder.EncodeBlock(context.RawBlock, context.Quality) }; } - - public override GlInternalFormat GetInternalFormat() - { - return GlInternalFormat.GlCompressedRedGreenRgtc2Ext; - } - - public override GlFormat GetBaseInternalFormat() - { - return GlFormat.GlRg; - } - - public override DxgiFormat GetDxgiFormat() - { - return DxgiFormat.DxgiFormatBc5Unorm; - } } } diff --git a/BCnEnc.Net/Encoder/BcEncoder.cs b/BCnEnc.Net/Encoder/BcEncoder.cs index 5e17c35..c7627d0 100644 --- a/BCnEnc.Net/Encoder/BcEncoder.cs +++ b/BCnEnc.Net/Encoder/BcEncoder.cs @@ -7,42 +7,11 @@ using BCnEncoder.Encoder.Bptc; using BCnEncoder.Encoder.Options; using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; -using Microsoft.Toolkit.HighPerformance; +using BCnEncoder.Shared.Colors; +using CommunityToolkit.HighPerformance; namespace BCnEncoder.Encoder { - /// - /// The pixel format determining the rgba layout of input data in . - /// - public enum PixelFormat - { - /// - /// 8 bits per channel RGBA. - /// - Rgba32, - - /// - /// 8 bits per channel BGRA. - /// - Bgra32, - - /// - /// 8 bits per channel ARGB. - /// - Argb32, - - /// - /// 8 bits per channel RGB. - /// - Rgb24, - - /// - /// 8 bits per channel BGR. - /// - Bgr24 - } - /// /// Handles all encoding of images into compressed or uncompressed formats. For decoding, /// @@ -73,2147 +42,844 @@ public BcEncoder(CompressionFormat format = CompressionFormat.Bc1) } #region LDR - #region Async Api /// - /// Encodes all mipmap levels into a ktx or a dds file and writes it to the output stream asynchronously. + /// Encode input data as raw bytes into a . /// - /// The input to encode represented by a . - /// The width of the image. - /// The height of the image. - /// The pixel format the given data is in. - /// The stream to write the encoded image to. - /// The cancellation token for this operation. Can be default if cancellation is not needed. - public Task EncodeToStreamAsync(ReadOnlyMemory input, int width, int height, PixelFormat format, Stream outputStream, CancellationToken token = default) + /// The input data to encode. This can be in any supported format. + /// A containing the encoded texture data. + public BCnTextureData Encode(BCnTextureData input) { - return Task.Run(() => - { - EncodeToStreamInternal(ByteToColorMemory(input.Span, width, height, format), outputStream, token); - }, token); + return EncodeInternal(input, default); } /// - /// Encodes all mipmap levels into a ktx or a dds file and writes it to the output stream asynchronously. + /// Encode input data as raw bytes into a . /// - /// The input to encode represented by a . - /// The stream to write the encoded image to. - /// The cancellation token for this operation. Can be default if cancellation is not needed. - public Task EncodeToStreamAsync(ReadOnlyMemory2D input, Stream outputStream, CancellationToken token = default) + /// The input data to encode. This can be in any supported format. + /// A containing the encoded texture data. + public Task EncodeAsync(BCnTextureData input, CancellationToken token = default) { - return Task.Run(() => - { - EncodeToStreamInternal(input, outputStream, default); - }, token); + return Task.Run(() => EncodeInternal(input, token), token); } /// - /// Encodes all mipmap levels into a Ktx file asynchronously. + /// Encode a cubemap as raw bytes into a . + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. /// - /// The input to encode represented by a . - /// The width of the image. - /// The height of the image. - /// The pixel format the given data is in. - /// The cancellation token for this operation. Can be default if cancellation is not needed. - /// The Ktx file containing the encoded image. - public Task EncodeToKtxAsync(ReadOnlyMemory input, int width, int height, PixelFormat format, CancellationToken token = default) + /// The input data to encode. + /// A containing the encoded texture data. + public BCnTextureData EncodeCubeMap(ReadOnlyMemory2D right, ReadOnlyMemory2D left, + ReadOnlyMemory2D top, ReadOnlyMemory2D down, + ReadOnlyMemory2D back, ReadOnlyMemory2D front) { - return Task.Run(() => EncodeToKtxInternal(ByteToColorMemory(input.Span, width, height, format), token), token); - } + var inputData = new BCnTextureData(CompressionFormat.Rgba32, right.Width, right.Height, 1, 1, 1, true, false, AlphaChannelHint.Unknown); - /// - /// Encodes all mipmap levels into a Ktx file asynchronously. - /// - /// The input to encode represented by a . - /// The cancellation token for this operation. Can be default if cancellation is not needed. - /// The Ktx file containing the encoded image. - public Task EncodeToKtxAsync(ReadOnlyMemory2D input, CancellationToken token = default) - { - return Task.Run(() => EncodeToKtxInternal(input, token), token); - } + if ( + right.Width != left.Width || right.Height != left.Height || + right.Width != top.Width || right.Height != top.Height || + right.Width != down.Width || right.Height != down.Height || + right.Width != back.Width || right.Height != back.Height || + right.Width != front.Width || right.Height != front.Height + ) + { + throw new ArgumentException("All faces of a cubeMap must be of equal width and height!"); + } - /// - /// Encodes all mipmap levels into a Dds file asynchronously. - /// - /// The input to encode represented by a . - /// The width of the image. - /// The height of the image. - /// The pixel format the given data is in. - /// The cancellation token for this operation. Can be default if cancellation is not needed. - /// The Dds file containing the encoded image. - public Task EncodeToDdsAsync(ReadOnlyMemory input, int width, int height, PixelFormat format, CancellationToken token = default) - { - return Task.Run(() => EncodeToDdsInternal(ByteToColorMemory(input.Span, width, height, format), token), token); - } + inputData.Mips[0][CubeMapFaceDirection.XPositive].Data = right.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.XNegative].Data = left.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.YPositive].Data = top.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.YNegative].Data = down.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.ZPositive].Data = back.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.ZNegative].Data = front.CopyAsBytes(); - /// - /// Encodes all mipmap levels into a Dds file asynchronously. - /// - /// The input to encode represented by a . - /// The cancellation token for this operation. Can be default if cancellation is not needed. - /// The Dds file containing the encoded image. - public Task EncodeToDdsAsync(ReadOnlyMemory2D input, CancellationToken token = default) - { - return Task.Run(() => EncodeToDdsInternal(input, token), token); + return EncodeInternal(inputData, default); } /// - /// Encodes all mipmap levels into a list of byte buffers asynchronously. This data does not contain any file headers, just the raw encoded pixel data. + /// Encode a cubemap as raw bytes into a . + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. /// - /// The input to encode represented by a . - /// The width of the image. - /// The height of the image. - /// The pixel format the given data is in. - /// The cancellation token for this operation. Can be default if cancellation is not needed. - /// A list of raw encoded mipmap input. - public Task EncodeToRawBytesAsync(ReadOnlyMemory input, int width, int height, PixelFormat format, CancellationToken token = default) + /// The input data to encode. + /// A containing the encoded texture data. + public Task EncodeCubeMapAsync(ReadOnlyMemory2D right, ReadOnlyMemory2D left, + ReadOnlyMemory2D top, ReadOnlyMemory2D down, + ReadOnlyMemory2D back, ReadOnlyMemory2D front, CancellationToken token = default) { - return Task.Run(() => EncodeToRawInternal(ByteToColorMemory(input.Span, width, height, format), token), token); + var inputData = new BCnTextureData(CompressionFormat.Rgba32, right.Width, right.Height, 1, 1, 1, true, false, AlphaChannelHint.Unknown); + + if ( + right.Width != left.Width || right.Height != left.Height || + right.Width != top.Width || right.Height != top.Height || + right.Width != down.Width || right.Height != down.Height || + right.Width != back.Width || right.Height != back.Height || + right.Width != front.Width || right.Height != front.Height + ) + { + throw new ArgumentException("All faces of a cubeMap must be of equal width and height!"); + } + + inputData.Mips[0][CubeMapFaceDirection.XPositive].Data = right.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.XNegative].Data = left.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.YPositive].Data = top.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.YNegative].Data = down.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.ZPositive].Data = back.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.ZNegative].Data = front.CopyAsBytes(); + + return Task.Run(() => EncodeInternal(inputData, token), token); } /// - /// Encodes all mipmap levels into an array of byte buffers asynchronously. This data does not contain any file headers, just the raw encoded pixel data. + /// Encode input data as raw bytes into a . /// - /// The input to encode represented by a . - /// The cancellation token for this operation. Can be default if cancellation is not needed. - /// A list of raw encoded mipmap input. - /// To get the width and height of the encoded mip levels, see . - public Task EncodeToRawBytesAsync(ReadOnlyMemory2D input, CancellationToken token = default) + /// The input data to encode. + /// A containing the encoded texture data. + public BCnTextureData Encode(ReadOnlyMemory2D input) { - return Task.Run(() => EncodeToRawInternal(input, token), token); + var inputData = BCnTextureData.FromSingle(CompressionFormat.Rgba32, input.Width, input.Height, input.CopyAsBytes(), AlphaChannelHint.Unknown); + return EncodeInternal(inputData, default); } /// - /// Encodes a single mip level of the input image to a byte buffer asynchronously. This data does not contain any file headers, just the raw encoded pixel data. + /// Encode input data as raw bytes into a . /// - /// The input to encode represented by a . - /// The width of the image. - /// The height of the image. - /// The pixel format the given data is in. - /// The mipmap to encode. - /// The cancellation token for this operation. Can be default, if the operation is not asynchronous. - /// The raw encoded input. - /// To get the width and height of the encoded mip level, see . - public Task EncodeToRawBytesAsync(ReadOnlyMemory input, int width, int height, PixelFormat format, int mipLevel, CancellationToken token = default) + /// The input data to encode. + /// A containing the encoded texture data. + public Task EncodeAsync(ReadOnlyMemory2D input, CancellationToken token = default) { - return Task.Run(() => EncodeToRawInternal(ByteToColorMemory(input.Span, width, height, format), mipLevel, out _, out _, token), token); + var inputData = BCnTextureData.FromSingle(CompressionFormat.Rgba32, input.Width, input.Height, input.CopyAsBytes(), AlphaChannelHint.Unknown); + return Task.Run(() => EncodeInternal(inputData, token), token); } + // /// + // /// Encodes a single mip level of the input image to a byte buffer asynchronously. This data does not contain any file headers, just the raw encoded pixel data. + // /// + // /// The input to encode represented by a . + // /// The mipmap to encode. + // /// The cancellation token for this operation. Can be default if cancellation is not needed. + // /// The raw encoded input. + // /// To get the width and height of the encoded mip level, see . + // public Task EncodeToRawBytesAsync(ReadOnlyMemory2D input, int mipLevel, CancellationToken token = default) + // { + // return Task.Run(() => EncodeSingleLdrInternal(input.Flatten(), input.Width, input.Height, mipLevel, token), token); + // } + // + /// /// Encodes a single mip level of the input image to a byte buffer asynchronously. This data does not contain any file headers, just the raw encoded pixel data. + /// Note that even if the input data already contains mipLevels, new mips are generated from the first mip. /// - /// The input to encode represented by a . + /// The input to encode represented by a . /// The mipmap to encode. /// The cancellation token for this operation. Can be default if cancellation is not needed. /// The raw encoded input. /// To get the width and height of the encoded mip level, see . - public Task EncodeToRawBytesAsync(ReadOnlyMemory2D input, int mipLevel, CancellationToken token = default) + public async Task EncodeToRawBytesAsync(BCnTextureData input, int mipLevel, CancellationToken token = default) { - return Task.Run(() => EncodeToRawInternal(input, mipLevel, out _, out _, token), token); + CalculateMipMapSize(input.Width, input.Height, 1, mipLevel, out int mipWidth, out int mipHeight, out _); + + var output = AllocateOutputBuffer(mipWidth, mipHeight, 1); + + await Task.Run(() => EncodeSingleInternal(input.First.Data, output, input.Format, input.Width, input.Height, 1, mipLevel, token), token); + + return output; } /// - /// Encodes all mipMaps of a cubeMap image to a stream asynchronously either in ktx or dds format. - /// The format can be set in . - /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// Encode input data as raw bytes into a . /// - /// The positive X-axis face of the cubeMap - /// The negative X-axis face of the cubeMap - /// The positive Y-axis face of the cubeMap - /// The negative Y-axis face of the cubeMap - /// The positive Z-axis face of the cubeMap - /// The negative Z-axis face of the cubeMap - /// The stream to write the encoded image to. - /// The cancellation token for this operation. Can be default if cancellation is not needed. - /// A . - public Task EncodeCubeMapToStreamAsync(ReadOnlyMemory2D right, ReadOnlyMemory2D left, - ReadOnlyMemory2D top, ReadOnlyMemory2D down, - ReadOnlyMemory2D back, ReadOnlyMemory2D front, Stream outputStream, CancellationToken token = default) + /// The input data to encode. This can be in any supported format. + /// The width of the image. + /// The height of the image. + /// The pixel format the input data is in. + /// A containing the encoded texture data. + public Task EncodeBytesAsync(byte[] input, int width, int height, CompressionFormat inputFormat, CancellationToken token = default) { - return Task.Run(() => EncodeCubeMapToStreamInternal(right, left, top, down, back, front, outputStream, token), token); + var inputData = BCnTextureData.FromSingle(inputFormat, width, height, input, AlphaChannelHint.Unknown); + return Task.Run(() => EncodeInternal(inputData, token), token); } /// - /// Encodes all mipMaps of a cubeMap image to a asynchronously. - /// The format can be set in . - /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// Encode input data as raw bytes into a . /// - /// The positive X-axis face of the cubeMap - /// The negative X-axis face of the cubeMap - /// The positive Y-axis face of the cubeMap - /// The negative Y-axis face of the cubeMap - /// The positive Z-axis face of the cubeMap - /// The negative Z-axis face of the cubeMap - /// The cancellation token for this operation. Can be default if cancellation is not needed. - /// A of type . - public Task EncodeCubeMapToKtxAsync(ReadOnlyMemory2D right, ReadOnlyMemory2D left, - ReadOnlyMemory2D top, ReadOnlyMemory2D down, - ReadOnlyMemory2D back, ReadOnlyMemory2D front, CancellationToken token = default) + /// The input data to encode. This can be in any supported format. + /// The width of the image. + /// The height of the image. + /// The pixel format the input data is in. + /// A containing the encoded texture data. + public BCnTextureData EncodeBytes(byte[] input, int width, int height, CompressionFormat inputFormat) { - return Task.Run(() => EncodeCubeMapToKtxInternal(right, left, top, down, back, front, token), token); + var inputData = BCnTextureData.FromSingle(inputFormat, width, height, input, AlphaChannelHint.Unknown); + return EncodeInternal(inputData, default); } /// - /// Encodes all mipMaps of a cubeMap image to a asynchronously. - /// The format can be set in . - /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// Encodes a single mip level of the input image to a byte buffer. This data does not contain any file headers, just the raw encoded pixel data. /// - /// The positive X-axis face of the cubeMap - /// The negative X-axis face of the cubeMap - /// The positive Y-axis face of the cubeMap - /// The negative Y-axis face of the cubeMap - /// The positive Z-axis face of the cubeMap - /// The negative Z-axis face of the cubeMap - /// The cancellation token for this operation. Can be default if cancellation is not needed. - /// A of type . - public Task EncodeCubeMapToDdsAsync(ReadOnlyMemory2D right, ReadOnlyMemory2D left, - ReadOnlyMemory2D top, ReadOnlyMemory2D down, - ReadOnlyMemory2D back, ReadOnlyMemory2D front, CancellationToken token = default) + public byte[] EncodeToRawBytes(ReadOnlyMemory input, int width, int height, CompressionFormat inputFormat, int mipLevel, out int mipWidth, out int mipHeight) + where TIn : unmanaged { - return Task.Run(() => EncodeCubeMapToDdsInternal(right, left, top, down, back, front, token), token); - } + CalculateMipMapSize(width, height, 1, mipLevel, out mipWidth, out mipHeight, out int mipDepth); - #endregion + var inputBytes = PrepareInputBuffer(input, width, height, 1, inputFormat); + var output = AllocateOutputBuffer(mipWidth, mipHeight, mipDepth); - #region Sync Api + EncodeSingleInternal(inputBytes, output, inputFormat, width, height, 1, mipLevel, default); - /// - /// Encodes all mipmap levels into a ktx or a dds file and writes it to the output stream. - /// - /// The input to encode represented by a . - /// The width of the image. - /// The height of the image. - /// The pixel format the input data is in. - /// The stream to write the encoded image to. - public void EncodeToStream(ReadOnlySpan input, int width, int height, PixelFormat format, Stream outputStream) - { - EncodeToStream(ByteToColorMemory(input, width, height, format), outputStream); + return output; } /// - /// Encodes all mipmap levels into a ktx or a dds file and writes it to the output stream. + /// Encodes a single mip level of the input image to a byte buffer. This data does not contain any file headers, just the raw encoded pixel data. + /// Note that even if the input data already contains mipLevels, new mips are generated from the first mip. /// - /// The input to encode represented by a . - /// The stream to write the encoded image to. - public void EncodeToStream(ReadOnlyMemory2D input, Stream outputStream) + /// The input to encode represented by a . + /// The mipmap to encode. + /// The width of the mipmap. + /// The height of the mipmap. + /// The depth of the mipmap. + /// A byte buffer containing the encoded data of the requested mip-level. + public byte[] EncodeToRawBytes(BCnTextureData input, int mipLevel, out int mipWidth, out int mipHeight, out int mipDepth) { - EncodeToStreamInternal(input, outputStream, default); + CalculateMipMapSize(input.Width, input.Height, input.Depth, mipLevel, out mipWidth, out mipHeight, out mipDepth); + + var output = AllocateOutputBuffer(mipWidth, mipHeight, mipDepth); + + EncodeSingleInternal(input.First.Data, output, input.Format, input.Width, input.Height, input.Depth, mipLevel, default); + + return output; } + // /// + // /// Encodes a single 4x4 block to raw encoded bytes. Input Span length must be exactly 16. + // /// + // /// Input 4x4 color block + // /// Raw encoded data + // public byte[] EncodeBlock(ReadOnlySpan inputBlock) + // { + // if (inputBlock.Length != 16) + // { + // throw new ArgumentException($"Single block encoding can only encode blocks of 4x4"); + // } + // return EncodeBlockLdrInternal(inputBlock.AsSpan2D(4, 4)); + // } + // + // /// + // /// Encodes a single 4x4 block to raw encoded bytes. Input Span width and height must be exactly 4. + // /// + // /// Input 4x4 color block + // /// Raw encoded data + // public byte[] EncodeBlock(ReadOnlySpan2D inputBlock) + // { + // if (inputBlock.Width != 4 || inputBlock.Height != 4) + // { + // throw new ArgumentException($"Single block encoding can only encode blocks of 4x4"); + // } + // return EncodeBlockLdrInternal(inputBlock); + // } + // + // /// + // /// Encodes a single 4x4 block and writes the encoded block to a stream. Input Span length must be exactly 16. + // /// + // /// Input 4x4 color block + // /// Output stream where the encoded block will be written to. + // public void EncodeBlock(ReadOnlySpan inputBlock, Stream outputStream) + // { + // if (inputBlock.Length != 16) + // { + // throw new ArgumentException($"Single block encoding can only encode blocks of 4x4"); + // } + // EncodeBlockLdrInternal(inputBlock.AsSpan2D(4, 4), outputStream); + // } + // + // /// + // /// Encodes a single 4x4 block and writes the encoded block to a stream. Input Span width and height must be exactly 4. + // /// + // /// Input 4x4 color block + // /// Output stream where the encoded block will be written to. + // public void EncodeBlock(ReadOnlySpan2D inputBlock, Stream outputStream) + // { + // if (inputBlock.Width != 4 || inputBlock.Height != 4) + // { + // throw new ArgumentException($"Single block encoding can only encode blocks of 4x4"); + // } + // EncodeBlockLdrInternal(inputBlock, outputStream); + // } /// - /// Encodes all mipmap levels into a Ktx file. + /// Gets the block size of the currently selected compression format in bytes. /// - /// The input to encode represented by a . - /// The width of the image. - /// The height of the image. - /// The pixel format the input data is in. - /// The containing the encoded image. - public KtxFile EncodeToKtx(ReadOnlySpan input, int width, int height, PixelFormat format) + /// The size of a single 4x4 block in bytes + public int GetBlockSize() { - return EncodeToKtx(ByteToColorMemory(input, width, height, format)); + return GetBlockEncoder(OutputOptions.Format).GetBlockSize(); } /// - /// Encodes all mipmap levels into a Ktx file. + /// Gets the number of total blocks in an image with the given pixel width and height. /// - /// The input to encode represented by a . - /// The containing the encoded image. - public KtxFile EncodeToKtx(ReadOnlyMemory2D input) + /// The pixel width of the image + /// The pixel height of the image + /// The total number of blocks. + public int GetBlockCount(int pixelWidth, int pixelHeight) { - return EncodeToKtxInternal(input, default); + return ImageToBlocks.CalculateNumOfBlocks(OutputOptions.Format, pixelWidth, pixelHeight); } /// - /// Encodes all mipmap levels into a Dds file. + /// Gets the number of blocks in an image with the given pixel width and height. /// - /// The input to encode represented by a . - /// The width of the image. - /// The height of the image. - /// The pixel format the input data is in. - /// The containing the encoded image. - public DdsFile EncodeToDds(ReadOnlySpan input, int width, int height, PixelFormat format) + /// The pixel width of the image + /// The pixel height of the image + /// The amount of blocks in the x-axis + /// The amount of blocks in the y-axis + public void GetBlockCount(int pixelWidth, int pixelHeight, out int blocksWidth, out int blocksHeight) { - return EncodeToDds(ByteToColorMemory(input, width, height, format)); + ImageToBlocks.CalculateNumOfBlocks(OutputOptions.Format, pixelWidth, pixelHeight, 1, out blocksWidth, out blocksHeight, out _); } + #endregion + + #region HDR + /// - /// Encodes all mipmap levels into a Dds file. + /// Encode a cubemap as raw bytes into a . + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. /// - /// The input to encode represented by a . - /// The containing the encoded image. - public DdsFile EncodeToDds(ReadOnlyMemory2D input) + /// The input data to encode. + /// A containing the encoded texture data. + public BCnTextureData EncodeCubeMapHdr(ReadOnlyMemory2D right, ReadOnlyMemory2D left, + ReadOnlyMemory2D top, ReadOnlyMemory2D down, + ReadOnlyMemory2D back, ReadOnlyMemory2D front) { - return EncodeToDdsInternal(input, default); + var inputData = new BCnTextureData(CompressionFormat.RgbaFloat, right.Width, right.Height, 1, 1, 1, true, false); + + if ( + right.Width != left.Width || right.Height != left.Height || + right.Width != top.Width || right.Height != top.Height || + right.Width != down.Width || right.Height != down.Height || + right.Width != back.Width || right.Height != back.Height || + right.Width != front.Width || right.Height != front.Height + ) + { + throw new ArgumentException("All faces of a cubeMap must be of equal width and height!"); + } + + inputData.Mips[0][CubeMapFaceDirection.XPositive].Data = right.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.XNegative].Data = left.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.YPositive].Data = top.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.YNegative].Data = down.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.ZPositive].Data = back.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.ZNegative].Data = front.CopyAsBytes(); + + return EncodeInternal(inputData, default); } /// - /// Encodes all mipmap levels into an array of byte buffers. This data does not contain any file headers, just the raw encoded pixel data. + /// Encode a cubemap as raw bytes into a . + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. /// - /// The input to encode represented by a . - /// The width of the image. - /// The height of the image. - /// The pixel format the input data is in. - /// An array of byte buffers containing all mipmap levels. - /// To get the width and height of the encoded mip levels, see . - public byte[][] EncodeToRawBytes(ReadOnlySpan input, int width, int height, PixelFormat format) + /// The input data to encode. + /// A containing the encoded texture data. + public Task EncodeCubeMapHdrAsync(ReadOnlyMemory2D right, ReadOnlyMemory2D left, + ReadOnlyMemory2D top, ReadOnlyMemory2D down, + ReadOnlyMemory2D back, ReadOnlyMemory2D front, CancellationToken token = default) { - return EncodeToRawBytes(ByteToColorMemory(input, width, height, format)); + var inputData = new BCnTextureData(CompressionFormat.RgbaFloat, right.Width, right.Height, 1, 1, 1, true, false, AlphaChannelHint.Straight); + + if ( + right.Width != left.Width || right.Height != left.Height || + right.Width != top.Width || right.Height != top.Height || + right.Width != down.Width || right.Height != down.Height || + right.Width != back.Width || right.Height != back.Height || + right.Width != front.Width || right.Height != front.Height + ) + { + throw new ArgumentException("All faces of a cubeMap must be of equal width and height!"); + } + + inputData.Mips[0][CubeMapFaceDirection.XPositive].Data = right.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.XNegative].Data = left.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.YPositive].Data = top.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.YNegative].Data = down.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.ZPositive].Data = back.CopyAsBytes(); + inputData.Mips[0][CubeMapFaceDirection.ZNegative].Data = front.CopyAsBytes(); + + return Task.Run(() => EncodeInternal(inputData, token), token); } /// - /// Encodes all mipmap levels into a list of byte buffers. This data does not contain any file headers, just the raw encoded pixel data. + /// Encodes all mipmap levels of a HDR image into . This data does not contain any file headers, just the raw encoded pixel data. /// /// The input to encode represented by a . - /// An array of byte buffers containing all mipmap levels. - /// To get the width and height of the encoded mip levels, see . - public byte[][] EncodeToRawBytes(ReadOnlyMemory2D input) + public Task EncodeHdrAsync(ReadOnlyMemory2D input, CancellationToken token = default) { - return EncodeToRawInternal(input, default); + var inputData = BCnTextureData.FromSingle( + CompressionFormat.RgbaFloat, + input.Width, + input.Height, + input.CopyAsBytes(), + AlphaChannelHint.Straight); + return Task.Run(() => EncodeInternal(inputData, token), token); } - /// - /// Encodes a single mip level of the input image to a byte buffer. This data does not contain any file headers, just the raw encoded pixel data. - /// - /// The input to encode represented by a . - /// The width of the image. - /// The height of the image. - /// The pixel format the input data is in. - /// The mipmap to encode. - /// The width of the mipmap. - /// The height of the mipmap. - /// A byte buffer containing the encoded data of the requested mip-level. - public byte[] EncodeToRawBytes(ReadOnlySpan input, int width, int height, PixelFormat format, int mipLevel, out int mipWidth, out int mipHeight) - { - return EncodeToRawInternal(ByteToColorMemory(input, width, height, format), mipLevel, out mipWidth, out mipHeight, default); - } + // /// + // /// Encodes a single mip level of the input HDR image to a byte buffer asynchronously. This data does not contain any file headers, just the raw encoded pixel data. + // /// + // /// The input to encode represented by a . + // /// The mipmap to encode. + // /// The cancellation token for this operation. Can be default if cancellation is not needed. + // /// The raw encoded input. + // /// To get the width and height of the encoded mip level, see . + // public Task EncodeToRawBytesHdrAsync(ReadOnlyMemory2D input, int mipLevel, CancellationToken token = default) + // { + // return Task.Run(() => EncodeSingleHdrInternal(input.Flatten(), input.Width, input.Height, mipLevel, token), token); + // } /// - /// Encodes a single mip level of the input image to a byte buffer. This data does not contain any file headers, just the raw encoded pixel data. + /// Encodes all mipmap levels of a HDR image into . This data does not contain any file headers, just the raw encoded pixel data. /// /// The input to encode represented by a . - /// The mipmap to encode. - /// The width of the mipmap. - /// The height of the mipmap. - /// A byte buffer containing the encoded data of the requested mip-level. - public byte[] EncodeToRawBytes(ReadOnlyMemory2D input, int mipLevel, out int mipWidth, out int mipHeight) - { - return EncodeToRawInternal(input, mipLevel, out mipWidth, out mipHeight, default); - } + public BCnTextureData EncodeHdr(ReadOnlyMemory2D input) + { + var inputData = BCnTextureData.FromSingle( + CompressionFormat.RgbaFloat, + input.Width, + input.Height, + input.CopyAsBytes(), + AlphaChannelHint.Straight); + return EncodeInternal(inputData, default); + } + + // /// + // /// Encodes a single mip level of the HDR input image to a byte buffer. This data does not contain any file headers, just the raw encoded pixel data. + // /// + // /// The input to encode represented by a . + // /// The mipmap to encode. + // /// The width of the mipmap. + // /// The height of the mipmap. + // /// A byte buffer containing the encoded data of the requested mip-level. + // public byte[] EncodeToRawBytesHdr(ReadOnlyMemory2D input, int mipLevel, out int mipWidth, out int mipHeight) + // { + // CalculateMipMapSize(input.Width, input.Height, mipLevel, out mipWidth, out mipHeight); + // return EncodeSingleHdrInternal(input.Flatten(), input.Width, input.Height, mipLevel, default); + // } + + // /// + // /// Encodes a single 4x4 HDR block to raw encoded bytes. Input Span length must be exactly 16. + // /// + // /// Input 4x4 color block + // /// Raw encoded data + // public byte[] EncodeBlockHdr(ReadOnlySpan inputBlock) + // { + // if (inputBlock.Length != 16) + // { + // throw new ArgumentException($"Single block encoding can only encode blocks of 4x4"); + // } + // return EncodeBlockHdrInternal(inputBlock.AsSpan2D(4, 4)); + // } + // + // /// + // /// Encodes a single 4x4 HDR block to raw encoded bytes. Input Span width and height must be exactly 4. + // /// + // /// Input 4x4 color block + // /// Raw encoded data + // public byte[] EncodeBlockHdr(ReadOnlySpan2D inputBlock) + // { + // if (inputBlock.Width != 4 || inputBlock.Height != 4) + // { + // throw new ArgumentException($"Single block encoding can only encode blocks of 4x4"); + // } + // return EncodeBlockHdrInternal(inputBlock); + // } + // + // /// + // /// Encodes a single 4x4 HDR block and writes the encoded block to a stream. Input Span length must be exactly 16. + // /// + // /// Input 4x4 color block + // /// Output stream where the encoded block will be written to. + // public void EncodeBlockHdr(ReadOnlySpan inputBlock, Stream outputStream) + // { + // if (inputBlock.Length != 16) + // { + // throw new ArgumentException($"Single block encoding can only encode blocks of 4x4"); + // } + // EncodeBlockHdrInternal(inputBlock.AsSpan2D(4, 4), outputStream); + // } + // + // /// + // /// Encodes a single 4x4 HDR block and writes the encoded block to a stream. Input Span width and height must be exactly 4. + // /// + // /// Input 4x4 color block + // /// Output stream where the encoded block will be written to. + // public void EncodeBlockHdr(ReadOnlySpan2D inputBlock, Stream outputStream) + // { + // if (inputBlock.Width != 4 || inputBlock.Height != 4) + // { + // throw new ArgumentException($"Single block encoding can only encode blocks of 4x4"); + // } + // EncodeBlockHdrInternal(inputBlock, outputStream); + // } + + #endregion + #region MipMap operations /// - /// Encodes all mipMaps of a cubeMap image to a stream either in ktx or dds format. - /// The format can be set in . - /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// Calculates the number of mipmap levels that will be generated for the given input image. + /// This method takes into account and + /// . /// - /// The positive X-axis face of the cubeMap - /// The negative X-axis face of the cubeMap - /// The positive Y-axis face of the cubeMap - /// The negative Y-axis face of the cubeMap - /// The positive Z-axis face of the cubeMap - /// The negative Z-axis face of the cubeMap - /// The width of the faces. - /// The height of the faces. - /// The pixel format the input data is in. - /// The stream to write the encoded image to. - public void EncodeCubeMapToStream(ReadOnlySpan right, ReadOnlySpan left, - ReadOnlySpan top, ReadOnlySpan down, - ReadOnlySpan back, ReadOnlySpan front, - int width, int height, PixelFormat format, Stream outputStream) + /// The width of the input image in pixels + /// The height of the input image in pixels + /// The depth of the input image in pixels + /// The number of mipmap levels that will be generated for the input image. + public int CalculateNumberOfMipLevels(int imagePixelWidth, int imagePixelHeight, int imagePixelDepth) { - EncodeCubeMapToStreamInternal( - ByteToColorMemory(right, width, height, format), - ByteToColorMemory(left, width, height, format), - ByteToColorMemory(top, width, height, format), - ByteToColorMemory(down, width, height, format), - ByteToColorMemory(back, width, height, format), - ByteToColorMemory(front, width, height, format), - outputStream, default); + return MipMapper.CalculateMipChainLength(imagePixelWidth, imagePixelHeight, imagePixelDepth, + OutputOptions.GenerateMipMaps ? OutputOptions.MaxMipMapLevel : 1); } - /// - /// Encodes all mipMaps of a cubeMap image to a stream either in ktx or dds format. - /// The format can be set in . - /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. - /// - /// The positive X-axis face of the cubeMap - /// The negative X-axis face of the cubeMap - /// The positive Y-axis face of the cubeMap - /// The negative Y-axis face of the cubeMap - /// The positive Z-axis face of the cubeMap - /// The negative Z-axis face of the cubeMap - /// The stream to write the encoded image to. - public void EncodeCubeMapToStream(ReadOnlyMemory2D right, ReadOnlyMemory2D left, - ReadOnlyMemory2D top, ReadOnlyMemory2D down, - ReadOnlyMemory2D back, ReadOnlyMemory2D front, Stream outputStream) + public int CalculateNumberOfMipLevels(int imagePixelWidth, int imagePixelHeight) { - EncodeCubeMapToStreamInternal(right, left, top, down, back, front, outputStream, default); + return MipMapper.CalculateMipChainLength(imagePixelWidth, imagePixelHeight, 1, + OutputOptions.GenerateMipMaps ? OutputOptions.MaxMipMapLevel : 1); } /// - /// Encodes all mipMaps of a cubeMap image to a . - /// The format can be set in . - /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// Calculates the size of a given mipmap level. /// - /// The positive X-axis face of the cubeMap - /// The negative X-axis face of the cubeMap - /// The positive Y-axis face of the cubeMap - /// The negative Y-axis face of the cubeMap - /// The positive Z-axis face of the cubeMap - /// The negative Z-axis face of the cubeMap - /// The width of the faces. - /// The height of the faces. - /// The pixel format the input data is in. - /// The encoded image as a . - public KtxFile EncodeCubeMapToKtx(ReadOnlySpan right, ReadOnlySpan left, - ReadOnlySpan top, ReadOnlySpan down, - ReadOnlySpan back, ReadOnlySpan front, - int width, int height, PixelFormat format) + /// The width of the input image in pixels + /// The height of the input image in pixels + /// The depth of the input image in pixels + /// The mipLevel to calculate (0 is original image) + /// The mipmap width calculated + /// The mipmap height calculated + /// The mipmap depth calculated + public void CalculateMipMapSize(int imagePixelWidth, int imagePixelHeight, int imagePixelDepth, int mipLevel, out int mipWidth, out int mipHeight, out int mipDepth) { - return EncodeCubeMapToKtxInternal( - ByteToColorMemory(right, width, height, format), - ByteToColorMemory(left, width, height, format), - ByteToColorMemory(top, width, height, format), - ByteToColorMemory(down, width, height, format), - ByteToColorMemory(back, width, height, format), - ByteToColorMemory(front, width, height, format), default); + MipMapper.CalculateMipLevelSize(imagePixelWidth, imagePixelHeight, imagePixelDepth, mipLevel, out mipWidth, + out mipHeight, out mipDepth); } - /// - /// Encodes all mipMaps of a cubeMap image to a . - /// The format can be set in . - /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. - /// - /// The positive X-axis face of the cubeMap - /// The negative X-axis face of the cubeMap - /// The positive Y-axis face of the cubeMap - /// The negative Y-axis face of the cubeMap - /// The positive Z-axis face of the cubeMap - /// The negative Z-axis face of the cubeMap - /// The encoded image as a . - public KtxFile EncodeCubeMapToKtx(ReadOnlyMemory2D right, ReadOnlyMemory2D left, - ReadOnlyMemory2D top, ReadOnlyMemory2D down, - ReadOnlyMemory2D back, ReadOnlyMemory2D front) + public void CalculateMipMapSize(int imagePixelWidth, int imagePixelHeight, int mipLevel, out int mipWidth, out int mipHeight) { - return EncodeCubeMapToKtxInternal(right, left, top, down, back, front, default); + MipMapper.CalculateMipLevelSize(imagePixelWidth, imagePixelHeight, 1, mipLevel, out mipWidth, + out mipHeight, out _); } /// - /// Encodes all mipMaps of a cubeMap image to a . - /// The format can be set in . - /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// Calculates the byte size of a given mipmap level. + /// This takes into account the current /// - /// The positive X-axis face of the cubeMap - /// The negative X-axis face of the cubeMap - /// The positive Y-axis face of the cubeMap - /// The negative Y-axis face of the cubeMap - /// The positive Z-axis face of the cubeMap - /// The negative Z-axis face of the cubeMap - /// The width of the faces. - /// The height of the faces. - /// The pixel format the input data is in. - /// The encoded image as a . - public DdsFile EncodeCubeMapToDds(ReadOnlySpan right, ReadOnlySpan left, - ReadOnlySpan top, ReadOnlySpan down, - ReadOnlySpan back, ReadOnlySpan front, - int width, int height, PixelFormat format) + /// The width of the input image in pixels + /// The height of the input image in pixels + /// The depth of the input image in pixels + /// The mipLevel to calculate (0 is original image) + public long CalculateMipMapByteSize(int imagePixelWidth, int imagePixelHeight, int imagePixelDepth, int mipLevel) { - return EncodeCubeMapToDdsInternal( - ByteToColorMemory(right, width, height, format), - ByteToColorMemory(left, width, height, format), - ByteToColorMemory(top, width, height, format), - ByteToColorMemory(down, width, height, format), - ByteToColorMemory(back, width, height, format), - ByteToColorMemory(front, width, height, format), default); + MipMapper.CalculateMipLevelSize(imagePixelWidth, imagePixelHeight, imagePixelDepth, mipLevel, out var mipWidth, + out var mipHeight, out var mipDepth); + return OutputOptions.Format.CalculateMipByteSize(mipWidth, mipHeight, mipDepth); } - /// - /// Encodes all mipMaps of a cubeMap image to a . - /// The format can be set in . - /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. - /// - /// The positive X-axis face of the cubeMap - /// The negative X-axis face of the cubeMap - /// The positive Y-axis face of the cubeMap - /// The negative Y-axis face of the cubeMap - /// The positive Z-axis face of the cubeMap - /// The negative Z-axis face of the cubeMap - /// The encoded image as a . - public DdsFile EncodeCubeMapToDds(ReadOnlyMemory2D right, ReadOnlyMemory2D left, - ReadOnlyMemory2D top, ReadOnlyMemory2D down, - ReadOnlyMemory2D back, ReadOnlyMemory2D front) + public long CalculateMipMapByteSize(int imagePixelWidth, int imagePixelHeight, int mipLevel) { - return EncodeCubeMapToDdsInternal(right, left, top, down, back, front, default); + MipMapper.CalculateMipLevelSize(imagePixelWidth, imagePixelHeight, 1, mipLevel, out var mipWidth, + out var mipHeight, out var mipDepth); + return OutputOptions.Format.CalculateMipByteSize(mipWidth, mipHeight, mipDepth); } - /// - /// Encodes a single 4x4 block to raw encoded bytes. Input Span length must be exactly 16. - /// - /// Input 4x4 color block - /// Raw encoded data - public byte[] EncodeBlock(ReadOnlySpan inputBlock) + #endregion + + #region Private + + private void EncodeSingleInternal(ReadOnlyMemory input, Memory output, CompressionFormat inputFormat, int width, + int height, int depth, int mipLevel, CancellationToken token) { - if (inputBlock.Length != 16) + var outputFormat = OutputOptions.Format; + var encoder = GetEncoder(outputFormat); + + if (encoder == null) { - throw new ArgumentException($"Single block encoding can only encode blocks of 4x4"); + throw new NotSupportedException($"This format is not supported for this method: {OutputOptions.Format}"); } - return EncodeBlockInternal(inputBlock.AsSpan2D(4, 4)); - } - /// - /// Encodes a single 4x4 block to raw encoded bytes. Input Span width and height must be exactly 4. - /// - /// Input 4x4 color block - /// Raw encoded data - public byte[] EncodeBlock(ReadOnlySpan2D inputBlock) - { - if (inputBlock.Width != 4 || inputBlock.Height != 4) + if (inputFormat.IsBlockCompressedFormat()) { - throw new ArgumentException($"Single block encoding can only encode blocks of 4x4"); + throw new InvalidOperationException("Single mip encoding only supports raw formats as inputs!"); } - return EncodeBlockInternal(inputBlock); - } - /// - /// Encodes a single 4x4 block and writes the encoded block to a stream. Input Span length must be exactly 16. - /// - /// Input 4x4 color block - /// Output stream where the encoded block will be written to. - public void EncodeBlock(ReadOnlySpan inputBlock, Stream outputStream) - { - if (inputBlock.Length != 16) + MipMapper.CalculateMipLevelSize(width, height, depth, mipLevel, out var mipWidth, out var mipHeight, out var mipDepth); + + var totalBlocks = outputFormat.CalculateMipByteSize(mipWidth, mipHeight, mipDepth) / outputFormat.GetBytesPerBlock(); + + // Track whether the input is in sRGB space to respect AsIs option + var inputIsSrgb = inputFormat.IsSRGBFormat(); + var ignoreColorSpace = OutputOptions.ColorSpaceHandling == EncoderColorSpaceHandling.KeepAsIs; + + // 1. Convert to RgbaFloat format + Memory floatData; + + if (inputFormat != CompressionFormat.RgbaFloat) { - throw new ArgumentException($"Single block encoding can only encode blocks of 4x4"); + // For KeepAsIs, don't apply color space conversion + var initialConversion = ignoreColorSpace ? + ColorConversionMode.None : + (inputIsSrgb ? ColorConversionMode.SrgbToLinear : ColorConversionMode.None); + + floatData = ColorExtensions.InternalConvertToAsBytesFromBytes( + input, inputFormat, CompressionFormat.RgbaFloat, initialConversion) + .AsMemory().Cast(); + } + else + { + floatData = new ColorRgbaFloat[width * height * depth]; + input.Cast().CopyTo(floatData); } - EncodeBlockInternal(inputBlock.AsSpan2D(4, 4), outputStream); - } - /// - /// Encodes a single 4x4 block and writes the encoded block to a stream. Input Span width and height must be exactly 4. - /// - /// Input 4x4 color block - /// Output stream where the encoded block will be written to. - public void EncodeBlock(ReadOnlySpan2D inputBlock, Stream outputStream) - { - if (inputBlock.Width != 4 || inputBlock.Height != 4) + // 2. Process alpha based on AlphaHandling setting + if (OutputOptions.Format.SupportsAlpha() && !OutputOptions.Format.IsHdrFormat()) { - throw new ArgumentException($"Single block encoding can only encode blocks of 4x4"); + var alphaHint = AlphaChannelHint.Unknown; + var resultAlphaHint = AlphaHandlingHelper.ProcessAlpha(floatData, OutputOptions.AlphaHandling, alphaHint); } - EncodeBlockInternal(inputBlock, outputStream); - } - /// - /// Gets the block size of the currently selected compression format in bytes. - /// - /// The size of a single 4x4 block in bytes - public int GetBlockSize() - { - var compressedEncoder = GetRgba32BlockEncoder(OutputOptions.Format); - if (compressedEncoder == null) + // 3. Generate mipmap if needed + if (mipLevel > 0) + { + // Generate the mipmap (alpha handling already done above) + MipMapper.GenerateSingleMip(floatData.AsMemory2D(height, width), mipLevel).TryGetMemory(out floatData); + } + + // 4. Determine color conversion mode for final encoding + ColorConversionMode colorConversionMode = ColorConversionMode.None; + + if (!ignoreColorSpace) { - var hdrEncoder = GetFloatBlockEncoder(OutputOptions.Format); - - if (hdrEncoder == null) + if (OutputOptions.ColorSpaceHandling == EncoderColorSpaceHandling.ProcessLinearPreserveColorSpace) + { + if (inputIsSrgb) + colorConversionMode = ColorConversionMode.LinearToSrgb; + } + else if (OutputOptions.ColorSpaceHandling == EncoderColorSpaceHandling.Auto) { - throw new NotSupportedException($"This format is either not supported or does not use block compression: {OutputOptions.Format}"); + // Determine color conversion mode + colorConversionMode = DetermineColorConversionMode( + CompressionFormat.RgbaFloat, outputFormat); } + } + + // Setup encoding context + var context = new OperationContext + { + CancellationToken = token, + IsParallel = !Debugger.IsAttached && Options.IsParallel, + TaskCount = Options.TaskCount, + Progress = new OperationProgress(Options.Progress, totalBlocks), + ColorConversionMode = colorConversionMode, + Weights = new RgbWeights(OutputOptions.UsePerceptualMetrics), + Quality = OutputOptions.Quality + }; - return hdrEncoder.GetBlockSize(); + if (inputFormat.IsUNormFormat() && outputFormat.IsSNormFormat() && OutputOptions.RescaleUnormToSnorm) + { + UNormToSNorm(floatData.Span); } - return compressedEncoder.GetBlockSize(); + + // 5. Encode the data + var result = encoder.Encode(floatData, mipWidth, mipHeight, context); + + result.CopyTo(output); } - /// - /// Gets the number of total blocks in an image with the given pixel width and height. - /// - /// The pixel width of the image - /// The pixel height of the image - /// The total number of blocks. - public int GetBlockCount(int pixelWidth, int pixelHeight) - { - return ImageToBlocks.CalculateNumOfBlocks(pixelWidth, pixelHeight); - } - - /// - /// Gets the number of blocks in an image with the given pixel width and height. - /// - /// The pixel width of the image - /// The pixel height of the image - /// The amount of blocks in the x-axis - /// The amount of blocks in the y-axis - public void GetBlockCount(int pixelWidth, int pixelHeight, out int blocksWidth, out int blocksHeight) - { - ImageToBlocks.CalculateNumOfBlocks(pixelWidth, pixelHeight, out blocksWidth, out blocksHeight); - } - - #endregion - #endregion - - #region HDR - - #region Async Api - - /// - /// Encodes all mipmap levels into a ktx or a dds file and writes it to the output stream asynchronously. - /// - /// The input to encode represented by a . - /// The stream to write the encoded image to. - /// The cancellation token for this operation. Can be default if cancellation is not needed. - public Task EncodeToStreamHdrAsync(ReadOnlyMemory2D input, Stream outputStream, CancellationToken token = default) - { - return Task.Run(() => - { - EncodeToStreamInternalHdr(input, outputStream, default); - }, token); - } - - /// - /// Encodes all mipmap levels into a Ktx file asynchronously. - /// - /// The input to encode represented by a . - /// The cancellation token for this operation. Can be default if cancellation is not needed. - /// The Ktx file containing the encoded image. - public Task EncodeToKtxHdrAsync(ReadOnlyMemory2D input, CancellationToken token = default) - { - return Task.Run(() => EncodeToKtxInternalHdr(input, token), token); - } - - /// - /// Encodes all mipmap levels into a Dds file asynchronously. - /// - /// The input to encode represented by a . - /// The cancellation token for this operation. Can be default if cancellation is not needed. - /// The Dds file containing the encoded image. - public Task EncodeToDdsHdrAsync(ReadOnlyMemory2D input, CancellationToken token = default) - { - return Task.Run(() => EncodeToDdsInternalHdr(input, token), token); - } - - /// - /// Encodes all mipmap levels of an HDR image into an array of byte buffers asynchronously. This data does not contain any file headers, just the raw encoded pixel data. - /// - /// The input to encode represented by a . - /// The cancellation token for this operation. Can be default if cancellation is not needed. - /// A list of raw encoded mipmap input. - /// To get the width and height of the encoded mip levels, see . - public Task EncodeToRawBytesHdrAsync(ReadOnlyMemory2D input, CancellationToken token = default) - { - return Task.Run(() => EncodeToRawInternalHdr(input, token), token); - } - - /// - /// Encodes a single mip level of the input HDR image to a byte buffer asynchronously. This data does not contain any file headers, just the raw encoded pixel data. - /// - /// The input to encode represented by a . - /// The mipmap to encode. - /// The cancellation token for this operation. Can be default if cancellation is not needed. - /// The raw encoded input. - /// To get the width and height of the encoded mip level, see . - public Task EncodeToRawBytesHdrAsync(ReadOnlyMemory2D input, int mipLevel, CancellationToken token = default) - { - return Task.Run(() => EncodeToRawInternalHdr(input, mipLevel, out _, out _, token), token); - } - - /// - /// Encodes all mipMaps of a cubeMap HDR image to a stream asynchronously either in ktx or dds format. - /// The format can be set in . - /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. - /// - /// The positive X-axis face of the cubeMap - /// The negative X-axis face of the cubeMap - /// The positive Y-axis face of the cubeMap - /// The negative Y-axis face of the cubeMap - /// The positive Z-axis face of the cubeMap - /// The negative Z-axis face of the cubeMap - /// The stream to write the encoded image to. - /// The cancellation token for this operation. Can be default if cancellation is not needed. - /// A . - public Task EncodeCubeMapToStreamHdrAsync(ReadOnlyMemory2D right, ReadOnlyMemory2D left, - ReadOnlyMemory2D top, ReadOnlyMemory2D down, - ReadOnlyMemory2D back, ReadOnlyMemory2D front, Stream outputStream, CancellationToken token = default) - { - return Task.Run(() => EncodeCubeMapToStreamInternalHdr(right, left, top, down, back, front, outputStream, token), token); - } - - /// - /// Encodes all mipMaps of a cubeMap HDR image to a asynchronously. - /// The format can be set in . - /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. - /// - /// The positive X-axis face of the cubeMap - /// The negative X-axis face of the cubeMap - /// The positive Y-axis face of the cubeMap - /// The negative Y-axis face of the cubeMap - /// The positive Z-axis face of the cubeMap - /// The negative Z-axis face of the cubeMap - /// The cancellation token for this operation. Can be default if cancellation is not needed. - /// A of type . - public Task EncodeCubeMapToKtxHdrAsync(ReadOnlyMemory2D right, ReadOnlyMemory2D left, - ReadOnlyMemory2D top, ReadOnlyMemory2D down, - ReadOnlyMemory2D back, ReadOnlyMemory2D front, CancellationToken token = default) - { - return Task.Run(() => EncodeCubeMapToKtxInternalHdr(right, left, top, down, back, front, token), token); - } - - /// - /// Encodes all mipMaps of a cubeMap HDR image to a asynchronously. - /// The format can be set in . - /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. - /// - /// The positive X-axis face of the cubeMap - /// The negative X-axis face of the cubeMap - /// The positive Y-axis face of the cubeMap - /// The negative Y-axis face of the cubeMap - /// The positive Z-axis face of the cubeMap - /// The negative Z-axis face of the cubeMap - /// The cancellation token for this operation. Can be default if cancellation is not needed. - /// A of type . - public Task EncodeCubeMapToDdsHdrAsync(ReadOnlyMemory2D right, ReadOnlyMemory2D left, - ReadOnlyMemory2D top, ReadOnlyMemory2D down, - ReadOnlyMemory2D back, ReadOnlyMemory2D front, CancellationToken token = default) - { - return Task.Run(() => EncodeCubeMapToDdsInternalHdr(right, left, top, down, back, front, token), token); - } - - #endregion - - #region Sync Api - - /// - /// Encodes all mipmap levels into a ktx or a dds file and writes it to the output stream. - /// - /// The input to encode represented by a . - /// The stream to write the encoded image to. - public void EncodeToStreamHdr(ReadOnlyMemory2D input, Stream outputStream) - { - EncodeToStreamInternalHdr(input, outputStream, default); - } - - /// - /// Encodes all mipmap levels into a Ktx file. - /// - /// The input to encode represented by a . - /// The containing the encoded image. - public KtxFile EncodeToKtxHdr(ReadOnlyMemory2D input) - { - return EncodeToKtxInternalHdr(input, default); - } - - /// - /// Encodes all mipmap levels into a Dds file. - /// - /// The input to encode represented by a . - /// The containing the encoded image. - public DdsFile EncodeToDdsHdr(ReadOnlyMemory2D input) - { - return EncodeToDdsInternalHdr(input, default); - } - - /// - /// Encodes all mipmap levels of a HDR image into a list of byte buffers. This data does not contain any file headers, just the raw encoded pixel data. - /// - /// The input to encode represented by a . - /// An array of byte buffers containing all mipmap levels. - /// To get the width and height of the encoded mip levels, see . - public byte[][] EncodeToRawBytesHdr(ReadOnlyMemory2D input) - { - return EncodeToRawInternalHdr(input, default); - } - - /// - /// Encodes a single mip level of the HDR input image to a byte buffer. This data does not contain any file headers, just the raw encoded pixel data. - /// - /// The input to encode represented by a . - /// The mipmap to encode. - /// The width of the mipmap. - /// The height of the mipmap. - /// A byte buffer containing the encoded data of the requested mip-level. - public byte[] EncodeToRawBytesHdr(ReadOnlyMemory2D input, int mipLevel, out int mipWidth, out int mipHeight) - { - return EncodeToRawInternalHdr(input, mipLevel, out mipWidth, out mipHeight, default); - } - - /// - /// Encodes all mipMaps of a HDR cubeMap image to a stream either in ktx or dds format. - /// The format can be set in . - /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. - /// - /// The positive X-axis face of the cubeMap - /// The negative X-axis face of the cubeMap - /// The positive Y-axis face of the cubeMap - /// The negative Y-axis face of the cubeMap - /// The positive Z-axis face of the cubeMap - /// The negative Z-axis face of the cubeMap - /// The stream to write the encoded image to. - public void EncodeCubeMapToStreamHdr(ReadOnlyMemory2D right, ReadOnlyMemory2D left, - ReadOnlyMemory2D top, ReadOnlyMemory2D down, - ReadOnlyMemory2D back, ReadOnlyMemory2D front, Stream outputStream) - { - EncodeCubeMapToStreamInternalHdr(right, left, top, down, back, front, outputStream, default); - } - - /// - /// Encodes all mipMaps of a HDR cubeMap image to a . - /// The format can be set in . - /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. - /// - /// The positive X-axis face of the cubeMap - /// The negative X-axis face of the cubeMap - /// The positive Y-axis face of the cubeMap - /// The negative Y-axis face of the cubeMap - /// The positive Z-axis face of the cubeMap - /// The negative Z-axis face of the cubeMap - /// The encoded image as a . - public KtxFile EncodeCubeMapToKtxHdr(ReadOnlyMemory2D right, ReadOnlyMemory2D left, - ReadOnlyMemory2D top, ReadOnlyMemory2D down, - ReadOnlyMemory2D back, ReadOnlyMemory2D front) - { - return EncodeCubeMapToKtxInternalHdr(right, left, top, down, back, front, default); - } - - /// - /// Encodes all mipMaps of a HDR cubeMap image to a . - /// The format can be set in . - /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. - /// - /// The positive X-axis face of the cubeMap - /// The negative X-axis face of the cubeMap - /// The positive Y-axis face of the cubeMap - /// The negative Y-axis face of the cubeMap - /// The positive Z-axis face of the cubeMap - /// The negative Z-axis face of the cubeMap - /// The encoded image as a . - public DdsFile EncodeCubeMapToDdsHdr(ReadOnlyMemory2D right, ReadOnlyMemory2D left, - ReadOnlyMemory2D top, ReadOnlyMemory2D down, - ReadOnlyMemory2D back, ReadOnlyMemory2D front) - { - return EncodeCubeMapToDdsInternalHdr(right, left, top, down, back, front, default); - } - - /// - /// Encodes a single 4x4 HDR block to raw encoded bytes. Input Span length must be exactly 16. - /// - /// Input 4x4 color block - /// Raw encoded data - public byte[] EncodeBlockHdr(ReadOnlySpan inputBlock) - { - if (inputBlock.Length != 16) - { - throw new ArgumentException($"Single block encoding can only encode blocks of 4x4"); - } - return EncodeBlockInternalHdr(inputBlock.AsSpan2D(4, 4)); - } - - /// - /// Encodes a single 4x4 HDR block to raw encoded bytes. Input Span width and height must be exactly 4. - /// - /// Input 4x4 color block - /// Raw encoded data - public byte[] EncodeBlockHdr(ReadOnlySpan2D inputBlock) - { - if (inputBlock.Width != 4 || inputBlock.Height != 4) - { - throw new ArgumentException($"Single block encoding can only encode blocks of 4x4"); - } - return EncodeBlockInternalHdr(inputBlock); - } - - /// - /// Encodes a single 4x4 HDR block and writes the encoded block to a stream. Input Span length must be exactly 16. - /// - /// Input 4x4 color block - /// Output stream where the encoded block will be written to. - public void EncodeBlockHdr(ReadOnlySpan inputBlock, Stream outputStream) - { - if (inputBlock.Length != 16) - { - throw new ArgumentException($"Single block encoding can only encode blocks of 4x4"); - } - EncodeBlockInternalHdr(inputBlock.AsSpan2D(4, 4), outputStream); - } - - /// - /// Encodes a single 4x4 HDR block and writes the encoded block to a stream. Input Span width and height must be exactly 4. - /// - /// Input 4x4 color block - /// Output stream where the encoded block will be written to. - public void EncodeBlockHdr(ReadOnlySpan2D inputBlock, Stream outputStream) - { - if (inputBlock.Width != 4 || inputBlock.Height != 4) - { - throw new ArgumentException($"Single block encoding can only encode blocks of 4x4"); - } - EncodeBlockInternalHdr(inputBlock, outputStream); - } - - #endregion - - #endregion - #region MipMap operations - - /// - /// Calculates the number of mipmap levels that will be generated for the given input image. - /// - /// The width of the input image in pixels - /// The height of the input image in pixels - /// The number of mipmap levels that will be generated for the input image. - public int CalculateNumberOfMipLevels(int imagePixelWidth, int imagePixelHeight) - { - return MipMapper.CalculateMipChainLength(imagePixelWidth, imagePixelHeight, - OutputOptions.GenerateMipMaps ? OutputOptions.MaxMipMapLevel : 1); - } - - /// - /// Calculates the size of a given mipmap level. - /// - /// The width of the input image in pixels - /// The height of the input image in pixels - /// The mipLevel to calculate (0 is original image) - /// The mipmap width calculated - /// The mipmap height calculated - public void CalculateMipMapSize(int imagePixelWidth, int imagePixelHeight, int mipLevel, out int mipWidth, out int mipHeight) - { - MipMapper.CalculateMipLevelSize(imagePixelWidth, imagePixelHeight, mipLevel, out mipWidth, - out mipHeight); - } - - #endregion - - #region Private - - #region HDR - - private void EncodeToStreamInternalHdr(ReadOnlyMemory2D input, Stream outputStream, CancellationToken token) - { - switch (OutputOptions.FileFormat) - { - case OutputFileFormat.Dds: - var dds = EncodeToDdsInternalHdr(input, token); - dds.Write(outputStream); - break; - - case OutputFileFormat.Ktx: - var ktx = EncodeToKtxInternalHdr(input, token); - ktx.Write(outputStream); - break; - } - } - - private KtxFile EncodeToKtxInternalHdr(ReadOnlyMemory2D input, CancellationToken token) - { - KtxFile output; - IBcBlockEncoder compressedEncoder = null; - - var numMipMaps = OutputOptions.GenerateMipMaps ? OutputOptions.MaxMipMapLevel : 1; - var mipChain = MipMapper.GenerateMipChain(input, ref numMipMaps); - - // Setup encoders - if (!OutputOptions.Format.IsHdrFormat()) - { - throw new NotSupportedException($"This Format is not supported for hdr images: {OutputOptions.Format}"); - } - compressedEncoder = GetFloatBlockEncoder(OutputOptions.Format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This Format is not supported: {OutputOptions.Format}"); - } - - output = new KtxFile( - KtxHeader.InitializeCompressed(input.Width, input.Height, - compressedEncoder.GetInternalFormat(), - compressedEncoder.GetBaseInternalFormat())); - - var context = new OperationContext - { - CancellationToken = token, - IsParallel = !Debugger.IsAttached && Options.IsParallel, - TaskCount = Options.TaskCount - }; - - // Calculate total blocks - var totalBlocks = mipChain.Sum(m => ImageToBlocks.CalculateNumOfBlocks(m.Width, m.Height)); - context.Progress = new OperationProgress(Options.Progress, totalBlocks); - - // Encode mipmap levels - for (var mip = 0; mip < numMipMaps; mip++) - { - byte[] encoded; - var blocks = ImageToBlocks.ImageTo4X4(mipChain[mip], out var blocksWidth, - out var blocksHeight); - encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.Quality, - context); - - context.Progress.SetProcessedBlocks(mipChain.Take(mip + 1).Sum(x => ImageToBlocks.CalculateNumOfBlocks(x.Width, x.Height))); - - - output.MipMaps.Add(new KtxMipmap((uint)encoded.Length, - (uint)mipChain[mip].Width, - (uint)mipChain[mip].Height, 1)); - output.MipMaps[mip].Faces[0] = new KtxMipFace(encoded, - (uint)mipChain[mip].Width, - (uint)mipChain[mip].Height); - } - - output.header.NumberOfFaces = 1; - output.header.NumberOfMipmapLevels = (uint)numMipMaps; - - return output; - } - - private DdsFile EncodeToDdsInternalHdr(ReadOnlyMemory2D input, CancellationToken token) - { - DdsFile output; - IBcBlockEncoder compressedEncoder = null; - - var numMipMaps = OutputOptions.GenerateMipMaps ? OutputOptions.MaxMipMapLevel : 1; - var mipChain = MipMapper.GenerateMipChain(input, ref numMipMaps); - - // Setup encoder - if (!OutputOptions.Format.IsHdrFormat()) - { - throw new NotSupportedException($"This Format is not supported for hdr images: {OutputOptions.Format}"); - } - compressedEncoder = GetFloatBlockEncoder(OutputOptions.Format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This Format is not supported: {OutputOptions.Format}"); - } - - var (ddsHeader, dxt10Header) = DdsHeader.InitializeCompressed(input.Width, input.Height, - compressedEncoder.GetDxgiFormat(), OutputOptions.DdsPreferDxt10Header); - output = new DdsFile(ddsHeader, dxt10Header); - - var context = new OperationContext - { - CancellationToken = token, - IsParallel = !Debugger.IsAttached && Options.IsParallel, - TaskCount = Options.TaskCount - }; - - // Calculate total blocks - var totalBlocks = mipChain.Sum(m => ImageToBlocks.CalculateNumOfBlocks(m.Width, m.Height)); - context.Progress = new OperationProgress(Options.Progress, totalBlocks); - - // Encode mipmap levels - for (var mip = 0; mip < numMipMaps; mip++) - { - byte[] encoded; - - var blocks = ImageToBlocks.ImageTo4X4(mipChain[mip], out var blocksWidth, out var blocksHeight); - encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.Quality, context); - - context.Progress.SetProcessedBlocks(mipChain.Take(mip + 1).Sum(x => ImageToBlocks.CalculateNumOfBlocks(x.Width, x.Height))); - - - if (mip == 0) - { - output.Faces.Add(new DdsFace((uint)input.Width, (uint)input.Height, - (uint)encoded.Length, numMipMaps)); - } - - output.Faces[0].MipMaps[mip] = new DdsMipMap(encoded, - (uint)mipChain[mip].Width, - (uint)mipChain[mip].Height); - } - - - output.header.dwMipMapCount = (uint)numMipMaps; - if (numMipMaps > 1) - { - output.header.dwCaps |= HeaderCaps.DdscapsComplex | HeaderCaps.DdscapsMipmap; - } - - return output; - } - - private byte[][] EncodeToRawInternalHdr(ReadOnlyMemory2D input, CancellationToken token) - { - var numMipMaps = OutputOptions.GenerateMipMaps ? OutputOptions.MaxMipMapLevel : 1; - var mipChain = MipMapper.GenerateMipChain(input, ref numMipMaps); - - var output = new byte[numMipMaps][]; - IBcBlockEncoder compressedEncoder = null; - - // Setup encoder - - compressedEncoder = GetFloatBlockEncoder(OutputOptions.Format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This Format is not supported: {OutputOptions.Format}"); - } - - var context = new OperationContext - { - CancellationToken = token, - IsParallel = !Debugger.IsAttached && Options.IsParallel, - TaskCount = Options.TaskCount - }; - - // Calculate total blocks - var totalBlocks = mipChain.Sum(m => ImageToBlocks.CalculateNumOfBlocks(m.Width, m.Height)); - context.Progress = new OperationProgress(Options.Progress, totalBlocks); - - // Encode all mipmap levels - for (var mip = 0; mip < numMipMaps; mip++) - { - byte[] encoded; - - var blocks = ImageToBlocks.ImageTo4X4(mipChain[mip], out var blocksWidth, out var blocksHeight); - encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.Quality, context); - - context.Progress.SetProcessedBlocks(mipChain.Take(mip + 1).Sum(x => ImageToBlocks.CalculateNumOfBlocks(x.Width, x.Height))); - - output[mip] = encoded; - } - - return output; - } - - private byte[] EncodeToRawInternalHdr(ReadOnlyMemory2D input, int mipLevel, out int mipWidth, out int mipHeight, CancellationToken token) - { - mipLevel = Math.Max(0, mipLevel); - - IBcBlockEncoder compressedEncoder = null; - - var numMipMaps = OutputOptions.GenerateMipMaps ? OutputOptions.MaxMipMapLevel : 1; - var mipChain = MipMapper.GenerateMipChain(input, ref numMipMaps); - - // Setup encoder - - compressedEncoder = GetFloatBlockEncoder(OutputOptions.Format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This Format is not supported: {OutputOptions.Format}"); - } - - // Dispose all mipmap levels - if (mipLevel > numMipMaps - 1) - { - throw new ArgumentException($"{nameof(mipLevel)} cannot be more than number of mipmaps."); - } - - var context = new OperationContext - { - CancellationToken = token, - IsParallel = !Debugger.IsAttached && Options.IsParallel, - TaskCount = Options.TaskCount - }; - - // Calculate total blocks - var totalBlocks = mipChain.Sum(m => ImageToBlocks.CalculateNumOfBlocks(m.Width, m.Height)); - context.Progress = new OperationProgress(Options.Progress, totalBlocks); - - // Encode mipmap level - byte[] encoded; - - var blocks = ImageToBlocks.ImageTo4X4(mipChain[mipLevel], out var blocksWidth, out var blocksHeight); - encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.Quality, context); - - mipWidth = mipChain[mipLevel].Width; - mipHeight = mipChain[mipLevel].Height; - - return encoded; - } - - private void EncodeCubeMapToStreamInternalHdr(ReadOnlyMemory2D right, ReadOnlyMemory2D left, ReadOnlyMemory2D top, ReadOnlyMemory2D down, - ReadOnlyMemory2D back, ReadOnlyMemory2D front, Stream outputStream, CancellationToken token) - { - switch (OutputOptions.FileFormat) - { - case OutputFileFormat.Ktx: - var ktx = EncodeCubeMapToKtxInternalHdr(right, left, top, down, back, front, token); - ktx.Write(outputStream); - break; - - case OutputFileFormat.Dds: - var dds = EncodeCubeMapToDdsInternalHdr(right, left, top, down, back, front, token); - dds.Write(outputStream); - break; - } - } - - private KtxFile EncodeCubeMapToKtxInternalHdr(ReadOnlyMemory2D right, ReadOnlyMemory2D left, ReadOnlyMemory2D top, ReadOnlyMemory2D down, - ReadOnlyMemory2D back, ReadOnlyMemory2D front, CancellationToken token) - { - KtxFile output; - IBcBlockEncoder compressedEncoder = null; - - var faces = new[] { right, left, top, down, back, front }; - - var width = right.Width; - var height = right.Height; - - // Setup encoder - compressedEncoder = GetFloatBlockEncoder(OutputOptions.Format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This Format is not supported: {OutputOptions.Format}"); - } - - output = new KtxFile( - KtxHeader.InitializeCompressed(width, height, - compressedEncoder.GetInternalFormat(), - compressedEncoder.GetBaseInternalFormat())); - - var numMipMaps = OutputOptions.GenerateMipMaps ? OutputOptions.MaxMipMapLevel : 1; - var mipLength = MipMapper.CalculateMipChainLength(width, height, numMipMaps); - for (uint i = 0; i < mipLength; i++) - { - output.MipMaps.Add(new KtxMipmap(0, 0, 0, (uint)faces.Length)); - } - - var context = new OperationContext - { - CancellationToken = token, - IsParallel = !Debugger.IsAttached && Options.IsParallel, - TaskCount = Options.TaskCount - }; - - // Calculate total blocks - var totalBlocks = 0; - foreach (var face in faces) - { - for (var mip = 0; mip < numMipMaps; mip++) - { - MipMapper.CalculateMipLevelSize(width, height, mip, out var mipWidth, out var mipHeight); - totalBlocks += ImageToBlocks.CalculateNumOfBlocks(mipWidth, mipHeight); - } - } - context.Progress = new OperationProgress(Options.Progress, totalBlocks); - - // Encode all faces - var processedBlocks = 0; - for (var face = 0; face < faces.Length; face++) - { - var mipChain = MipMapper.GenerateMipChain(faces[face], ref numMipMaps); - - // Encode all mipmap levels per face - for (var mipLevel = 0; mipLevel < numMipMaps; mipLevel++) - { - byte[] encoded; - var blocks = ImageToBlocks.ImageTo4X4(mipChain[mipLevel], out var blocksWidth, out var blocksHeight); - encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.Quality, context); - - processedBlocks += blocks.Length; - context.Progress.SetProcessedBlocks(processedBlocks); - - if (face == 0) - { - output.MipMaps[mipLevel] = new KtxMipmap((uint)encoded.Length, - (uint)mipChain[mipLevel].Width, - (uint)mipChain[mipLevel].Height, (uint)faces.Length); - } - - output.MipMaps[mipLevel].Faces[face] = new KtxMipFace(encoded, - (uint)mipChain[mipLevel].Width, - (uint)mipChain[mipLevel].Height); - } - } - - output.header.NumberOfFaces = (uint)faces.Length; - output.header.NumberOfMipmapLevels = (uint)mipLength; - - return output; - } - - private DdsFile EncodeCubeMapToDdsInternalHdr(ReadOnlyMemory2D right, ReadOnlyMemory2D left, ReadOnlyMemory2D top, ReadOnlyMemory2D down, - ReadOnlyMemory2D back, ReadOnlyMemory2D front, CancellationToken token) - { - DdsFile output; - IBcBlockEncoder compressedEncoder = null; - - var faces = new[] { right, left, top, down, back, front }; - - var width = right.Width; - var height = right.Height; - - // Setup encoder - compressedEncoder = GetFloatBlockEncoder(OutputOptions.Format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This Format is not supported: {OutputOptions.Format}"); - } - - var (ddsHeader, dxt10Header) = DdsHeader.InitializeCompressed(width, height, - compressedEncoder.GetDxgiFormat(), OutputOptions.DdsPreferDxt10Header); - output = new DdsFile(ddsHeader, dxt10Header); - - if (OutputOptions.DdsBc1WriteAlphaFlag && - OutputOptions.Format == CompressionFormat.Bc1WithAlpha) - { - output.header.ddsPixelFormat.dwFlags |= PixelFormatFlags.DdpfAlphaPixels; - } - - var numMipMaps = OutputOptions.GenerateMipMaps ? OutputOptions.MaxMipMapLevel : 1; - - var context = new OperationContext - { - CancellationToken = token, - IsParallel = !Debugger.IsAttached && Options.IsParallel, - TaskCount = Options.TaskCount - }; - - // Calculate total blocks - var totalBlocks = 0; - foreach (var face in faces) - { - for (var mip = 0; mip < numMipMaps; mip++) - { - MipMapper.CalculateMipLevelSize(width, height, mip, out var mipWidth, out var mipHeight); - totalBlocks += ImageToBlocks.CalculateNumOfBlocks(mipWidth, mipHeight); - } - } - context.Progress = new OperationProgress(Options.Progress, totalBlocks); - - // EncodeBlock all faces - var processedBlocks = 0; - for (var face = 0; face < faces.Length; face++) - { - var mipChain = MipMapper.GenerateMipChain(faces[face], ref numMipMaps); - - // Encode all mipmap levels per face - for (var mip = 0; mip < numMipMaps; mip++) - { - byte[] encoded; - - var blocks = ImageToBlocks.ImageTo4X4(mipChain[mip], out var blocksWidth, out var blocksHeight); - encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.Quality, context); - - processedBlocks += blocks.Length; - context.Progress.SetProcessedBlocks(processedBlocks); - - if (mip == 0) - { - output.Faces.Add(new DdsFace((uint)mipChain[mip].Width, (uint)mipChain[mip].Height, - (uint)encoded.Length, mipChain.Length)); - } - - output.Faces[face].MipMaps[mip] = new DdsMipMap(encoded, - (uint)mipChain[mip].Width, - (uint)mipChain[mip].Height); - } - } - - output.header.dwCaps |= HeaderCaps.DdscapsComplex; - output.header.dwMipMapCount = (uint)numMipMaps; - if (numMipMaps > 1) - { - output.header.dwCaps |= HeaderCaps.DdscapsMipmap; - } - - output.header.dwCaps2 |= HeaderCaps2.Ddscaps2Cubemap | - HeaderCaps2.Ddscaps2CubemapPositivex | - HeaderCaps2.Ddscaps2CubemapNegativex | - HeaderCaps2.Ddscaps2CubemapPositivey | - HeaderCaps2.Ddscaps2CubemapNegativey | - HeaderCaps2.Ddscaps2CubemapPositivez | - HeaderCaps2.Ddscaps2CubemapNegativez; - - return output; - } - - private byte[] EncodeBlockInternalHdr(ReadOnlySpan2D input) - { - var compressedEncoder = GetFloatBlockEncoder(OutputOptions.Format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This Format is not supported for single block encoding: {OutputOptions.Format}"); - } - - var output = new byte[compressedEncoder.GetBlockSize()]; - - var rawBlock = new RawBlock4X4RgbFloat(); - - var pixels = rawBlock.AsSpan; - - input.GetRowSpan(0).CopyTo(pixels); - input.GetRowSpan(1).CopyTo(pixels.Slice(4)); - input.GetRowSpan(2).CopyTo(pixels.Slice(8)); - input.GetRowSpan(3).CopyTo(pixels.Slice(12)); - - compressedEncoder.EncodeBlock(rawBlock, OutputOptions.Quality, output); - - return output; - } - - private void EncodeBlockInternalHdr(ReadOnlySpan2D input, Stream outputStream) - { - var compressedEncoder = GetFloatBlockEncoder(OutputOptions.Format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This Format is not supported for single block encoding: {OutputOptions.Format}"); - } - if (input.Width != 4 || input.Height != 4) - { - throw new ArgumentException($"Single block encoding can only encode blocks of 4x4"); - } - - Span output = stackalloc byte[16]; - output = output.Slice(0, compressedEncoder.GetBlockSize()); - - var rawBlock = new RawBlock4X4RgbFloat(); - - var pixels = rawBlock.AsSpan; - - input.GetRowSpan(0).CopyTo(pixels); - input.GetRowSpan(1).CopyTo(pixels.Slice(4)); - input.GetRowSpan(2).CopyTo(pixels.Slice(8)); - input.GetRowSpan(3).CopyTo(pixels.Slice(12)); - - compressedEncoder.EncodeBlock(rawBlock, OutputOptions.Quality, output); - - outputStream.Write(output); - } - - #endregion - - #region LDR - private void EncodeToStreamInternal(ReadOnlyMemory2D input, Stream outputStream, CancellationToken token) - { - switch (OutputOptions.FileFormat) - { - case OutputFileFormat.Dds: - var dds = EncodeToDdsInternal(input, token); - dds.Write(outputStream); - break; - - case OutputFileFormat.Ktx: - var ktx = EncodeToKtxInternal(input, token); - ktx.Write(outputStream); - break; - } - } - - private KtxFile EncodeToKtxInternal(ReadOnlyMemory2D input, CancellationToken token) - { - KtxFile output; - IBcBlockEncoder compressedEncoder = null; - IRawEncoder uncompressedEncoder = null; - - var numMipMaps = OutputOptions.GenerateMipMaps ? OutputOptions.MaxMipMapLevel : 1; - var mipChain = MipMapper.GenerateMipChain(input, ref numMipMaps); - - // Setup encoders - var isCompressedFormat = OutputOptions.Format.IsCompressedFormat(); - if (isCompressedFormat) - { - compressedEncoder = GetRgba32BlockEncoder(OutputOptions.Format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This Format is not supported: {OutputOptions.Format}"); - } - - output = new KtxFile( - KtxHeader.InitializeCompressed(input.Width, input.Height, - compressedEncoder.GetInternalFormat(), - compressedEncoder.GetBaseInternalFormat())); - } - else - { - uncompressedEncoder = GetRawEncoder(OutputOptions.Format); - output = new KtxFile( - KtxHeader.InitializeUncompressed(input.Width, input.Height, - uncompressedEncoder.GetGlType(), - uncompressedEncoder.GetGlFormat(), - uncompressedEncoder.GetGlTypeSize(), - uncompressedEncoder.GetInternalFormat(), - uncompressedEncoder.GetBaseInternalFormat())); - } - - var context = new OperationContext - { - CancellationToken = token, - IsParallel = !Debugger.IsAttached && Options.IsParallel, - TaskCount = Options.TaskCount - }; - - // Calculate total blocks - var totalBlocks = isCompressedFormat ? mipChain.Sum(m => ImageToBlocks.CalculateNumOfBlocks(m.Width, m.Height)) : mipChain.Sum(m => m.Width * m.Height); - context.Progress = new OperationProgress(Options.Progress, totalBlocks); - - // Encode mipmap levels - for (var mip = 0; mip < numMipMaps; mip++) - { - byte[] encoded; - if (isCompressedFormat) - { - var blocks = ImageToBlocks.ImageTo4X4(mipChain[mip], out var blocksWidth, - out var blocksHeight); - encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.Quality, - context); - - context.Progress.SetProcessedBlocks(mipChain.Take(mip + 1).Sum(x => ImageToBlocks.CalculateNumOfBlocks(x.Width, x.Height))); - } - else - { - if (!mipChain[mip].TryGetMemory(out var mipMemory)) - { - throw new InvalidOperationException("Could not get Memory from Memory2D."); - } - - encoded = uncompressedEncoder.Encode(mipMemory); - - context.Progress.SetProcessedBlocks(mipChain.Take(mip + 1).Sum(x => x.Width * x.Height)); - } - - output.MipMaps.Add(new KtxMipmap((uint)encoded.Length, - (uint)mipChain[mip].Width, - (uint)mipChain[mip].Height, 1)); - output.MipMaps[mip].Faces[0] = new KtxMipFace(encoded, - (uint)mipChain[mip].Width, - (uint)mipChain[mip].Height); - } - - output.header.NumberOfFaces = 1; - output.header.NumberOfMipmapLevels = (uint)numMipMaps; - - return output; - } - - private DdsFile EncodeToDdsInternal(ReadOnlyMemory2D input, CancellationToken token) - { - DdsFile output; - IBcBlockEncoder compressedEncoder = null; - IRawEncoder uncompressedEncoder = null; - - var numMipMaps = OutputOptions.GenerateMipMaps ? OutputOptions.MaxMipMapLevel : 1; - var mipChain = MipMapper.GenerateMipChain(input, ref numMipMaps); - - // Setup encoder - var isCompressedFormat = OutputOptions.Format.IsCompressedFormat(); - if (isCompressedFormat) - { - compressedEncoder = GetRgba32BlockEncoder(OutputOptions.Format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This Format is not supported: {OutputOptions.Format}"); - } - - var (ddsHeader, dxt10Header) = DdsHeader.InitializeCompressed(input.Width, input.Height, - compressedEncoder.GetDxgiFormat(), OutputOptions.DdsPreferDxt10Header); - output = new DdsFile(ddsHeader, dxt10Header); - - if (OutputOptions.DdsBc1WriteAlphaFlag && - OutputOptions.Format == CompressionFormat.Bc1WithAlpha) - { - output.header.ddsPixelFormat.dwFlags |= PixelFormatFlags.DdpfAlphaPixels; - } - } - else - { - uncompressedEncoder = GetRawEncoder(OutputOptions.Format); - var ddsHeader = DdsHeader.InitializeUncompressed(input.Width, input.Height, - uncompressedEncoder.GetDxgiFormat()); - output = new DdsFile(ddsHeader); - } - - var context = new OperationContext - { - CancellationToken = token, - IsParallel = !Debugger.IsAttached && Options.IsParallel, - TaskCount = Options.TaskCount - }; - - // Calculate total blocks - var totalBlocks = isCompressedFormat ? mipChain.Sum(m => ImageToBlocks.CalculateNumOfBlocks(m.Width, m.Height)) : mipChain.Sum(m => m.Width * m.Height); - context.Progress = new OperationProgress(Options.Progress, totalBlocks); - - // Encode mipmap levels - for (var mip = 0; mip < numMipMaps; mip++) - { - byte[] encoded; - if (isCompressedFormat) - { - var blocks = ImageToBlocks.ImageTo4X4(mipChain[mip], out var blocksWidth, out var blocksHeight); - encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.Quality, context); - - context.Progress.SetProcessedBlocks(mipChain.Take(mip + 1).Sum(x => ImageToBlocks.CalculateNumOfBlocks(x.Width, x.Height))); - } - else - { - if (!mipChain[mip].TryGetMemory(out var mipMemory)) - { - throw new InvalidOperationException("Could not get Memory from Memory2D."); - } - - encoded = uncompressedEncoder.Encode(mipMemory); - - context.Progress.SetProcessedBlocks(mipChain.Take(mip + 1).Sum(x => x.Width * x.Height)); - } - - if (mip == 0) - { - output.Faces.Add(new DdsFace((uint)input.Width, (uint)input.Height, - (uint)encoded.Length, numMipMaps)); - } - - output.Faces[0].MipMaps[mip] = new DdsMipMap(encoded, - (uint)mipChain[mip].Width, - (uint)mipChain[mip].Height); - } - - - output.header.dwMipMapCount = (uint)numMipMaps; - if (numMipMaps > 1) - { - output.header.dwCaps |= HeaderCaps.DdscapsComplex | HeaderCaps.DdscapsMipmap; - } - - return output; - } - - private byte[][] EncodeToRawInternal(ReadOnlyMemory2D input, CancellationToken token) + private BCnTextureData EncodeInternal(BCnTextureData textureData, CancellationToken token) { + var encoder = GetEncoder(OutputOptions.Format); var numMipMaps = OutputOptions.GenerateMipMaps ? OutputOptions.MaxMipMapLevel : 1; - var mipChain = MipMapper.GenerateMipChain(input, ref numMipMaps); - - var output = new byte[numMipMaps][]; - IBcBlockEncoder compressedEncoder = null; - IRawEncoder uncompressedEncoder = null; - - // Setup encoder - var isCompressedFormat = OutputOptions.Format.IsCompressedFormat(); - - if (isCompressedFormat) - { - compressedEncoder = GetRgba32BlockEncoder(OutputOptions.Format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This Format is not supported: {OutputOptions.Format}"); - } - } - else - { - uncompressedEncoder = GetRawEncoder(OutputOptions.Format); - } - - var context = new OperationContext - { - CancellationToken = token, - IsParallel = !Debugger.IsAttached && Options.IsParallel, - TaskCount = Options.TaskCount - }; - // Calculate total blocks - var totalBlocks = isCompressedFormat ? mipChain.Sum(m => ImageToBlocks.CalculateNumOfBlocks(m.Width, m.Height)) : mipChain.Sum(m => m.Width * m.Height); - context.Progress = new OperationProgress(Options.Progress, totalBlocks); + // Track whether the input is in sRGB space to respect AsIs option + var inputFormat = textureData.Format; + var inputIsSrgb = textureData.Format.IsSRGBFormat(); + var ignoreColorSpace = OutputOptions.ColorSpaceHandling == EncoderColorSpaceHandling.KeepAsIs; - // Encode all mipmap levels - for (var mip = 0; mip < numMipMaps; mip++) - { - byte[] encoded; - if (isCompressedFormat) - { - var blocks = ImageToBlocks.ImageTo4X4(mipChain[mip], out var blocksWidth, out var blocksHeight); - encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.Quality, context); - - context.Progress.SetProcessedBlocks(mipChain.Take(mip + 1).Sum(x => ImageToBlocks.CalculateNumOfBlocks(x.Width, x.Height))); - } - else - { - if (!mipChain[mip].TryGetMemory(out var mipMemory)) - { - throw new InvalidOperationException("Could not get Memory from Memory2D."); - } - - encoded = uncompressedEncoder.Encode(mipMemory); - - context.Progress.SetProcessedBlocks(mipChain.Take(mip + 1).Sum(x => x.Width * x.Height)); - } - - output[mip] = encoded; - } - - return output; - } - - private byte[] EncodeToRawInternal(ReadOnlyMemory2D input, int mipLevel, out int mipWidth, out int mipHeight, CancellationToken token) - { - mipLevel = Math.Max(0, mipLevel); - - IBcBlockEncoder compressedEncoder = null; - IRawEncoder uncompressedEncoder = null; - - var numMipMaps = OutputOptions.GenerateMipMaps ? OutputOptions.MaxMipMapLevel : 1; - var mipChain = MipMapper.GenerateMipChain(input, ref numMipMaps); + // 1. Convert texture data to RgbaFloat format for consistent processing, also ensuring we're in linear space + textureData = textureData.ConvertTo(CompressionFormat.RgbaFloat, convertColorspace: !ignoreColorSpace); - // Setup encoder - var isCompressedFormat = OutputOptions.Format.IsCompressedFormat(); - if (isCompressedFormat) - { - compressedEncoder = GetRgba32BlockEncoder(OutputOptions.Format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This Format is not supported: {OutputOptions.Format}"); - } - } - else - { - uncompressedEncoder = GetRawEncoder(OutputOptions.Format); - } + // 2. Process alpha according to the chosen handling method + if (OutputOptions.Format.SupportsAlpha() && !OutputOptions.Format.IsHdrFormat()) + AlphaHandlingHelper.ProcessAlpha(textureData, OutputOptions.AlphaHandling); - // Dispose all mipmap levels - if (mipLevel > numMipMaps - 1) + // 3. Generate mipmaps if needed (now that we're in linear space with correct alpha) + if (OutputOptions.GenerateMipMaps) { - throw new ArgumentException($"{nameof(mipLevel)} cannot be more than number of mipmaps."); + textureData = MipMapper.GenerateMipChain(textureData, ref numMipMaps); } - var context = new OperationContext - { - CancellationToken = token, - IsParallel = !Debugger.IsAttached && Options.IsParallel, - TaskCount = Options.TaskCount - }; + // 4. Create output texture data with the correct format + var newData = new BCnTextureData( + OutputOptions.Format, + textureData.Width, + textureData.Height, + textureData.Depth, + numMipMaps, + textureData.NumArrayElements, + textureData.IsCubeMap, false, + textureData.AlphaChannelHint); - // Calculate total blocks - var totalBlocks = isCompressedFormat ? ImageToBlocks.CalculateNumOfBlocks(mipChain[mipLevel].Width, mipChain[mipLevel].Height) : mipChain[mipLevel].Width * mipChain[mipLevel].Height; - context.Progress = new OperationProgress(Options.Progress, totalBlocks); + ColorConversionMode colorConversionMode = ColorConversionMode.None; - // Encode mipmap level - byte[] encoded; - if (isCompressedFormat) + // 5. Determine final color conversion mode for encoding + if (!ignoreColorSpace) { - var blocks = ImageToBlocks.ImageTo4X4(mipChain[mipLevel], out var blocksWidth, out var blocksHeight); - encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.Quality, context); - } - else - { - if (!mipChain[mipLevel].TryGetMemory(out var mipMemory)) + if (OutputOptions.ColorSpaceHandling == EncoderColorSpaceHandling.ProcessLinearPreserveColorSpace) { - throw new InvalidOperationException("Could not get Memory from Memory2D."); + if (inputIsSrgb) + colorConversionMode = ColorConversionMode.LinearToSrgb; } - - encoded = uncompressedEncoder.Encode(mipMemory); - } - - mipWidth = mipChain[mipLevel].Width; - mipHeight = mipChain[mipLevel].Height; - - return encoded; - } - - private void EncodeCubeMapToStreamInternal(ReadOnlyMemory2D right, ReadOnlyMemory2D left, ReadOnlyMemory2D top, ReadOnlyMemory2D down, - ReadOnlyMemory2D back, ReadOnlyMemory2D front, Stream outputStream, CancellationToken token) - { - switch (OutputOptions.FileFormat) - { - case OutputFileFormat.Ktx: - var ktx = EncodeCubeMapToKtxInternal(right, left, top, down, back, front, token); - ktx.Write(outputStream); - break; - - case OutputFileFormat.Dds: - var dds = EncodeCubeMapToDdsInternal(right, left, top, down, back, front, token); - dds.Write(outputStream); - break; - } - } - - private KtxFile EncodeCubeMapToKtxInternal(ReadOnlyMemory2D right, ReadOnlyMemory2D left, ReadOnlyMemory2D top, ReadOnlyMemory2D down, - ReadOnlyMemory2D back, ReadOnlyMemory2D front, CancellationToken token) - { - KtxFile output; - IBcBlockEncoder compressedEncoder = null; - IRawEncoder uncompressedEncoder = null; - - var faces = new[] { right, left, top, down, back, front }; - - var width = right.Width; - var height = right.Height; - - // Setup encoder - var isCompressedFormat = OutputOptions.Format.IsCompressedFormat(); - if (isCompressedFormat) - { - compressedEncoder = GetRgba32BlockEncoder(OutputOptions.Format); - if (compressedEncoder == null) + else if (OutputOptions.ColorSpaceHandling == EncoderColorSpaceHandling.Auto) { - throw new NotSupportedException($"This Format is not supported: {OutputOptions.Format}"); + colorConversionMode = DetermineColorConversionMode( + CompressionFormat.RgbaFloat, OutputOptions.Format); } - - output = new KtxFile( - KtxHeader.InitializeCompressed(width, height, - compressedEncoder.GetInternalFormat(), - compressedEncoder.GetBaseInternalFormat())); - } - else - { - uncompressedEncoder = GetRawEncoder(OutputOptions.Format); - output = new KtxFile( - KtxHeader.InitializeUncompressed(width, height, - uncompressedEncoder.GetGlType(), - uncompressedEncoder.GetGlFormat(), - uncompressedEncoder.GetGlTypeSize(), - uncompressedEncoder.GetInternalFormat(), - uncompressedEncoder.GetBaseInternalFormat())); - - } - - var numMipMaps = OutputOptions.GenerateMipMaps ? OutputOptions.MaxMipMapLevel : 1; - var mipLength = MipMapper.CalculateMipChainLength(width, height, numMipMaps); - for (uint i = 0; i < mipLength; i++) - { - output.MipMaps.Add(new KtxMipmap(0, 0, 0, (uint)faces.Length)); } + // Setup encoding context + var totalBlocks = newData.TotalByteSize / OutputOptions.Format.GetBytesPerBlock(); var context = new OperationContext { CancellationToken = token, IsParallel = !Debugger.IsAttached && Options.IsParallel, - TaskCount = Options.TaskCount + TaskCount = Options.TaskCount, + Progress = new OperationProgress(Options.Progress, totalBlocks), + ColorConversionMode = colorConversionMode, + Weights = new RgbWeights(OutputOptions.UsePerceptualMetrics), + Quality = OutputOptions.Quality }; - // Calculate total blocks - var totalBlocks = 0; - foreach (var face in faces) - { - for (var mip = 0; mip < numMipMaps; mip++) - { - MipMapper.CalculateMipLevelSize(width, height, mip, out var mipWidth, out var mipHeight); - totalBlocks += isCompressedFormat ? ImageToBlocks.CalculateNumOfBlocks(mipWidth, mipHeight) : mipWidth * mipHeight; - } - } - context.Progress = new OperationProgress(Options.Progress, totalBlocks); - - // Encode all faces - var processedBlocks = 0; - for (var face = 0; face < faces.Length; face++) + // 6. Encode each mip level + for (var m = 0; m < newData.NumMips; m++) { - var mipChain = MipMapper.GenerateMipChain(faces[face], ref numMipMaps); - - // Encode all mipmap levels per face - for (var mipLevel = 0; mipLevel < numMipMaps; mipLevel++) + for (var f = 0; f < newData.NumFaces; f++) { - byte[] encoded; - if (isCompressedFormat) + for (var a = 0; a < newData.NumArrayElements; a++) { - var blocks = ImageToBlocks.ImageTo4X4(mipChain[mipLevel], out var blocksWidth, out var blocksHeight); - encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.Quality, context); + var mipWidth = textureData.Mips[m].Width; + var mipHeight = textureData.Mips[m].Height; + var colorMemory = textureData.Mips[m][(CubeMapFaceDirection)f, a].AsMemory(); - processedBlocks += blocks.Length; - context.Progress.SetProcessedBlocks(processedBlocks); - } - else - { - if (!mipChain[mipLevel].TryGetMemory(out var mipMemory)) + if (inputFormat.IsUNormFormat() && OutputOptions.Format.IsSNormFormat() && + OutputOptions.RescaleUnormToSnorm) { - throw new InvalidOperationException("Could not get Memory from Memory2D."); + UNormToSNorm(colorMemory.Span); } - encoded = uncompressedEncoder.Encode(mipMemory); - - processedBlocks += mipMemory.Length; - context.Progress.SetProcessedBlocks(processedBlocks); - } - - if (face == 0) - { - output.MipMaps[mipLevel] = new KtxMipmap((uint)encoded.Length, - (uint)mipChain[mipLevel].Width, - (uint)mipChain[mipLevel].Height, (uint)faces.Length); - } - - output.MipMaps[mipLevel].Faces[face] = new KtxMipFace(encoded, - (uint)mipChain[mipLevel].Width, - (uint)mipChain[mipLevel].Height); - } - } - - output.header.NumberOfFaces = (uint)faces.Length; - output.header.NumberOfMipmapLevels = (uint)mipLength; - - return output; - } - - private DdsFile EncodeCubeMapToDdsInternal(ReadOnlyMemory2D right, ReadOnlyMemory2D left, ReadOnlyMemory2D top, ReadOnlyMemory2D down, - ReadOnlyMemory2D back, ReadOnlyMemory2D front, CancellationToken token) - { - DdsFile output; - IBcBlockEncoder compressedEncoder = null; - IRawEncoder uncompressedEncoder = null; - - var faces = new[] { right, left, top, down, back, front }; - - var width = right.Width; - var height = right.Height; - - // Setup encoder - var isCompressedFormat = OutputOptions.Format.IsCompressedFormat(); - if (isCompressedFormat) - { - compressedEncoder = GetRgba32BlockEncoder(OutputOptions.Format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This Format is not supported: {OutputOptions.Format}"); - } - - var (ddsHeader, dxt10Header) = DdsHeader.InitializeCompressed(width, height, - compressedEncoder.GetDxgiFormat(), OutputOptions.DdsPreferDxt10Header); - output = new DdsFile(ddsHeader, dxt10Header); - - if (OutputOptions.DdsBc1WriteAlphaFlag && - OutputOptions.Format == CompressionFormat.Bc1WithAlpha) - { - output.header.ddsPixelFormat.dwFlags |= PixelFormatFlags.DdpfAlphaPixels; - } - } - else - { - uncompressedEncoder = GetRawEncoder(OutputOptions.Format); - var ddsHeader = DdsHeader.InitializeUncompressed(width, height, - uncompressedEncoder.GetDxgiFormat()); - - output = new DdsFile(ddsHeader); - } - - var numMipMaps = OutputOptions.GenerateMipMaps ? OutputOptions.MaxMipMapLevel : 1; - - var context = new OperationContext - { - CancellationToken = token, - IsParallel = !Debugger.IsAttached && Options.IsParallel, - TaskCount = Options.TaskCount - }; - - // Calculate total blocks - var totalBlocks = 0; - foreach (var face in faces) - { - for (var mip = 0; mip < numMipMaps; mip++) - { - MipMapper.CalculateMipLevelSize(width, height, mip, out var mipWidth, out var mipHeight); - totalBlocks += isCompressedFormat ? ImageToBlocks.CalculateNumOfBlocks(mipWidth, mipHeight) : mipWidth * mipHeight; - } - } - context.Progress = new OperationProgress(Options.Progress, totalBlocks); - - // EncodeBlock all faces - var processedBlocks = 0; - for (var face = 0; face < faces.Length; face++) - { - var mipChain = MipMapper.GenerateMipChain(faces[face], ref numMipMaps); + var encoded = encoder.Encode(colorMemory, mipWidth, mipHeight, context); - // Encode all mipmap levels per face - for (var mip = 0; mip < numMipMaps; mip++) - { - byte[] encoded; - if (isCompressedFormat) - { - var blocks = ImageToBlocks.ImageTo4X4(mipChain[mip], out var blocksWidth, out var blocksHeight); - encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.Quality, context); - - processedBlocks += blocks.Length; - context.Progress.SetProcessedBlocks(processedBlocks); - } - else - { - if (!mipChain[mip].TryGetMemory(out var mipMemory)) + if (newData.Mips[m].SizeInBytes != encoded.Length) { - throw new InvalidOperationException("Could not get Memory from Memory2D."); + throw new InvalidOperationException("Encoded size does not match expected!"); } - encoded = uncompressedEncoder.Encode(mipMemory); - - processedBlocks += mipMemory.Length; - context.Progress.SetProcessedBlocks(processedBlocks); - } - - if (mip == 0) - { - output.Faces.Add(new DdsFace((uint)mipChain[mip].Width, (uint)mipChain[mip].Height, - (uint)encoded.Length, mipChain.Length)); + newData.Mips[m][(CubeMapFaceDirection)f, a].Data = encoded; } - - output.Faces[face].MipMaps[mip] = new DdsMipMap(encoded, - (uint)mipChain[mip].Width, - (uint)mipChain[mip].Height); } } - output.header.dwCaps |= HeaderCaps.DdscapsComplex; - output.header.dwMipMapCount = (uint)numMipMaps; - if (numMipMaps > 1) - { - output.header.dwCaps |= HeaderCaps.DdscapsMipmap; - } - - output.header.dwCaps2 |= HeaderCaps2.Ddscaps2Cubemap | - HeaderCaps2.Ddscaps2CubemapPositivex | - HeaderCaps2.Ddscaps2CubemapNegativex | - HeaderCaps2.Ddscaps2CubemapPositivey | - HeaderCaps2.Ddscaps2CubemapNegativey | - HeaderCaps2.Ddscaps2CubemapPositivez | - HeaderCaps2.Ddscaps2CubemapNegativez; - - return output; + return newData; } - private byte[] EncodeBlockInternal(ReadOnlySpan2D input) + /// + /// Determines the color conversion mode based on the source and destination formats + /// + private ColorConversionMode DetermineColorConversionMode(CompressionFormat sourceFormat, CompressionFormat destFormat) { - var compressedEncoder = GetRgba32BlockEncoder(OutputOptions.Format); - if (compressedEncoder == null) + ColorConversionMode colorConversionMode = ColorConversionMode.None; + if (OutputOptions.ColorSpaceHandling != EncoderColorSpaceHandling.KeepAsIs) { - throw new NotSupportedException($"This Format is not supported for single block encoding: {OutputOptions.Format}"); + // Auto-detect appropriate colorspace conversion based on formats + colorConversionMode = sourceFormat.GetColorConversionMode(destFormat); } + return colorConversionMode; + } + #endregion - var output = new byte[compressedEncoder.GetBlockSize()]; - - var rawBlock = new RawBlock4X4Rgba32(); - - var pixels = rawBlock.AsSpan; - - input.GetRowSpan(0).CopyTo(pixels); - input.GetRowSpan(1).CopyTo(pixels.Slice(4)); - input.GetRowSpan(2).CopyTo(pixels.Slice(8)); - input.GetRowSpan(3).CopyTo(pixels.Slice(12)); - - compressedEncoder.EncodeBlock(rawBlock, OutputOptions.Quality, output); + #region Support - return output; + private IBcBlockEncoder GetBlockEncoder(CompressionFormat format) + { + return GetEncoder(format) as IBcBlockEncoder; } - private void EncodeBlockInternal(ReadOnlySpan2D input, Stream outputStream) + private IBcEncoder GetEncoder(CompressionFormat format) { - var compressedEncoder = GetRgba32BlockEncoder(OutputOptions.Format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This Format is not supported for single block encoding: {OutputOptions.Format}"); - } - if (input.Width != 4 || input.Height != 4) + if (format.IsRawPixelFormat()) { - throw new ArgumentException($"Single block encoding can only encode blocks of 4x4"); + return Activator.CreateInstance(typeof(RawPixelEncoder<>).MakeGenericType(format.GetPixelType())) as IBcEncoder; } - - Span output = stackalloc byte[16]; - output = output.Slice(0, compressedEncoder.GetBlockSize()); - - var rawBlock = new RawBlock4X4Rgba32(); - - var pixels = rawBlock.AsSpan; - - input.GetRowSpan(0).CopyTo(pixels); - input.GetRowSpan(1).CopyTo(pixels.Slice(4)); - input.GetRowSpan(2).CopyTo(pixels.Slice(8)); - input.GetRowSpan(3).CopyTo(pixels.Slice(12)); - - compressedEncoder.EncodeBlock(rawBlock, OutputOptions.Quality, output); - - outputStream.Write(output); - } - - #endregion - - #endregion - - #region Support - - private IBcBlockEncoder GetRgba32BlockEncoder(CompressionFormat format) - { switch (format) { case CompressionFormat.Bc1: - return new Bc1BlockEncoder(); - + case CompressionFormat.Bc1_sRGB: + return new Bc1BlockEncoder(true, false); case CompressionFormat.Bc1WithAlpha: - return new Bc1AlphaBlockEncoder(); - + case CompressionFormat.Bc1WithAlpha_sRGB: + return new Bc1BlockEncoder(true, true); case CompressionFormat.Bc2: + case CompressionFormat.Bc2_sRGB: return new Bc2BlockEncoder(); - case CompressionFormat.Bc3: + case CompressionFormat.Bc3_sRGB: return new Bc3BlockEncoder(); - case CompressionFormat.Bc4: return new Bc4BlockEncoder(InputOptions.Bc4Component); - case CompressionFormat.Bc5: return new Bc5BlockEncoder(InputOptions.Bc5Component1, InputOptions.Bc5Component2); - + case CompressionFormat.Bc6U: + return new Bc6Encoder(false); + case CompressionFormat.Bc6S: + return new Bc6Encoder(true); case CompressionFormat.Bc7: + case CompressionFormat.Bc7_sRGB: return new Bc7Encoder(); - case CompressionFormat.Atc: return new AtcBlockEncoder(); - case CompressionFormat.AtcExplicitAlpha: return new AtcExplicitAlphaBlockEncoder(); - case CompressionFormat.AtcInterpolatedAlpha: return new AtcInterpolatedAlphaBlockEncoder(); - default: - return null; + throw new ArgumentOutOfRangeException(nameof(format), format, null); } } - private IBcBlockEncoder GetFloatBlockEncoder(CompressionFormat format) + private byte[] AllocateOutputBuffer(int pixelWidth, int pixelHeight, int pixelDepth) { - switch (format) - { - case CompressionFormat.Bc6S: - return new Bc6Encoder(true); - case CompressionFormat.Bc6U: - return new Bc6Encoder(false); - default: - return null; - } + var output = new byte[OutputOptions.Format.CalculateMipByteSize(pixelWidth, pixelHeight, pixelDepth)]; + return output; } - private IRawEncoder GetRawEncoder(CompressionFormat format) + private ReadOnlyMemory PrepareInputBuffer(ReadOnlyMemory input, int pixelWidth, int pixelHeight, int pixelDepth, CompressionFormat inputFormat) + where TIn : unmanaged { - switch (format) - { - case CompressionFormat.R: - return new RawLuminanceEncoder(InputOptions.LuminanceAsRed); - - case CompressionFormat.Rg: - return new RawRgEncoder(); - - case CompressionFormat.Rgb: - return new RawRgbEncoder(); - - case CompressionFormat.Rgba: - return new RawRgbaEncoder(); + var bytes = input.Cast(); - case CompressionFormat.Bgra: - return new RawBgraEncoder(); - - default: - throw new ArgumentOutOfRangeException(nameof(format), format, null); + if (bytes.Length != inputFormat.CalculateMipByteSize(pixelWidth, pixelHeight, pixelDepth)) + { + throw new ArgumentException($"Invalid input size. Expected {inputFormat.CalculateMipByteSize(pixelWidth, pixelHeight, pixelDepth)} bytes, but got {bytes.Length} bytes.", nameof(input)); } + + return bytes; } - private ReadOnlyMemory2D ByteToColorMemory(ReadOnlySpan span, int width, int height, PixelFormat format) + private static void UNormToSNorm(Span colors) { - var pixels = new ColorRgba32[width * height]; - - switch (format) + for (int i = 0; i < colors.Length; i++) { - case PixelFormat.Rgba32: - for (var i = 0; i < width * height * 4; i += 4) - pixels[i / 4] = new ColorRgba32(span[i], span[i + 1], span[i + 2], span[i + 3]); - break; - - case PixelFormat.Rgb24: - for (var i = 0; i < width * height * 3; i += 3) - pixels[i / 3] = new ColorRgba32(span[i], span[i + 1], span[i + 2], 255); - break; - - case PixelFormat.Bgra32: - for (var i = 0; i < width * height * 4; i += 4) - pixels[i / 4] = new ColorRgba32(span[i + 2], span[i + 1], span[i], span[i + 3]); - break; - - case PixelFormat.Bgr24: - for (var i = 0; i < width * height * 3; i += 3) - pixels[i / 3] = new ColorRgba32(span[i + 2], span[i + 1], span[i], 255); - break; - - case PixelFormat.Argb32: - for (var i = 0; i < width * height * 4; i += 4) - pixels[i / 4] = new ColorRgba32(span[i + 1], span[i + 2], span[i + 3], span[i]); - break; + colors[i] = new ColorRgbaFloat( + (colors[i].r - 0.5f) * 2, + (colors[i].g - 0.5f) * 2, + (colors[i].b - 0.5f) * 2 + ); } - - return new ReadOnlyMemory2D(pixels, height, width); } #endregion diff --git a/BCnEnc.Net/Encoder/Bptc/Bc6Encoder.cs b/BCnEnc.Net/Encoder/Bptc/Bc6Encoder.cs index 6212760..a253085 100644 --- a/BCnEnc.Net/Encoder/Bptc/Bc6Encoder.cs +++ b/BCnEnc.Net/Encoder/Bptc/Bc6Encoder.cs @@ -1,12 +1,11 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder.Bptc { - internal class Bc6Encoder : BaseBcBlockEncoder + internal class Bc6Encoder : BaseBcBlockEncoder { private readonly bool signed; @@ -15,38 +14,22 @@ public Bc6Encoder(bool signed) this.signed = signed; } - - public override GlInternalFormat GetInternalFormat() - { - return signed ? GlInternalFormat.GlCompressedRgbBptcSignedFloatArb : GlInternalFormat.GlCompressedRgbBptcUnsignedFloatArb; - } - - public override GlFormat GetBaseInternalFormat() + public override Bc6Block EncodeBlock(in RgbEncodingContext context) { - return GlFormat.GlRgb; - } - - public override DxgiFormat GetDxgiFormat() - { - return signed ? DxgiFormat.DxgiFormatBc6HSf16 : DxgiFormat.DxgiFormatBc6HUf16; - } - - public override Bc6Block EncodeBlock(RawBlock4X4RgbFloat block, CompressionQuality quality) - { - switch (quality) + switch (context.Quality) { case CompressionQuality.Fast: - return Bc6EncoderFast.EncodeBlock(block, signed); + return Bc6EncoderFast.EncodeBlock(context, signed); case CompressionQuality.Balanced: - return Bc6EncoderBalanced.EncodeBlock(block, signed); + return Bc6EncoderBalanced.EncodeBlock(context.RawBlock, context, signed); case CompressionQuality.BestQuality: - return Bc6EncoderBestQuality.EncodeBlock(block, signed); + return Bc6EncoderBestQuality.EncodeBlock(context.RawBlock, context, signed); default: - throw new ArgumentOutOfRangeException(nameof(quality), quality, null); + throw new ArgumentOutOfRangeException(nameof(context.Quality), context.Quality, null); } } - internal static ClusterIndices4X4 CreateClusterIndexBlock(RawBlock4X4RgbFloat raw, out int outputNumClusters, + internal static ClusterIndices4X4 CreateClusterIndexBlock(RawBlock4X4RgbaFloat raw, out int outputNumClusters, int numClusters = 2) { @@ -73,13 +56,14 @@ internal static ClusterIndices4X4 CreateClusterIndexBlock(RawBlock4X4RgbFloat ra internal static class Bc6EncoderFast { - internal static Bc6Block EncodeBlock(RawBlock4X4RgbFloat block, bool signed) + internal static Bc6Block EncodeBlock(in RgbEncodingContext context, bool signed) { - RgbBoundingBox.CreateFloat(block.AsSpan, out var min, out var max); + RgbBoundingBox.CreateFloat(context.RawBlock.AsSpan, out var min, out var max); + ColorRgbFloat minRgb = min, maxRgb = max; - LeastSquares.OptimizeEndpoints1Sub(block, ref min, ref max); + LeastSquares.OptimizeEndpoints1Sub(context.RawBlock, ref minRgb, ref maxRgb); - return Bc6ModeEncoder.EncodeBlock1Sub(Bc6BlockType.Type3, block, min, max, signed, out _); + return Bc6ModeEncoder.EncodeBlock1Sub(Bc6BlockType.Type3, context.RawBlock, min, max, signed, out _); } } @@ -89,10 +73,10 @@ internal static class Bc6EncoderBalanced private const float TargetError = 0.001f; private const int MaxTries = 10; - private static IEnumerable GenerateCandidates(RawBlock4X4RgbFloat block, bool signed) + private static IEnumerable GenerateCandidates(RawBlock4X4RgbaFloat block, RgbEncodingContext context, bool signed) { var candidates = 0; - Bc6EncodingHelpers.GetInitialUnscaledEndpoints(block, out var ep0Sub1, out var ep1Sub1); + Bc6EncodingHelpers.GetInitialUnscaledEndpoints(block, out var ep0Sub1, out var ep1Sub1, context.Weights); if (!signed) { @@ -107,7 +91,7 @@ private static IEnumerable GenerateCandidates(RawBlock4X4RgbFloat bloc ep0Sub1.ClampToPositive(); ep1Sub1.ClampToPositive(); } - + //Type3 Always ok! yield return Bc6ModeEncoder.EncodeBlock1Sub(Bc6BlockType.Type3, block, ep0Sub1, ep1Sub1, signed, out _); @@ -127,8 +111,8 @@ private static IEnumerable GenerateCandidates(RawBlock4X4RgbFloat bloc foreach (var subsetPartition in best2SubsetPartitions) { - Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(block, out var ep0, out var ep1, subsetPartition, 0); - Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(block, out var ep2, out var ep3, subsetPartition, 1); + Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(block, out var ep0, out var ep1, subsetPartition, 0, context.Weights); + Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(block, out var ep2, out var ep3, subsetPartition, 1, context.Weights); if (!signed) { @@ -148,7 +132,7 @@ private static IEnumerable GenerateCandidates(RawBlock4X4RgbFloat bloc ep2.ClampToPositive(); ep3.ClampToPositive(); } - + { var type1Block = Bc6ModeEncoder.EncodeBlock2Sub(Bc6BlockType.Type1, block, ep0, ep1, ep2, ep3, subsetPartition, signed, out var badType1); @@ -184,12 +168,12 @@ private static IEnumerable GenerateCandidates(RawBlock4X4RgbFloat bloc } } - internal static Bc6Block EncodeBlock(RawBlock4X4RgbFloat block, bool signed) + internal static Bc6Block EncodeBlock(RawBlock4X4RgbaFloat block, in RgbEncodingContext context, bool signed) { var result = new Bc6Block(); var bestError = 9999999f; - foreach (var candidate in GenerateCandidates(block, signed)) + foreach (var candidate in GenerateCandidates(block, context, signed)) { var error = block.CalculateError(candidate.Decode(signed)); @@ -214,10 +198,10 @@ internal static class Bc6EncoderBestQuality private const float TargetError = 0.0005f; private const int MaxTries = 500; - private static IEnumerable GenerateCandidates(RawBlock4X4RgbFloat block, bool signed) + private static IEnumerable GenerateCandidates(RawBlock4X4RgbaFloat block, RgbEncodingContext context, bool signed) { var candidates = 0; - Bc6EncodingHelpers.GetInitialUnscaledEndpoints(block, out var ep0Sub1, out var ep1Sub1); + Bc6EncodingHelpers.GetInitialUnscaledEndpoints(block, out var ep0Sub1, out var ep1Sub1, context.Weights); if (!signed) { @@ -273,8 +257,8 @@ private static IEnumerable GenerateCandidates(RawBlock4X4RgbFloat bloc foreach (var subsetPartition in best2SubsetPartitions) { - Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(block, out var ep0, out var ep1, subsetPartition, 0); - Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(block, out var ep2, out var ep3, subsetPartition, 1); + Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(block, out var ep0, out var ep1, subsetPartition, 0, context.Weights); + Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(block, out var ep2, out var ep3, subsetPartition, 1, context.Weights); if (!signed) { @@ -300,7 +284,7 @@ private static IEnumerable GenerateCandidates(RawBlock4X4RgbFloat bloc var sub2Block = Bc6ModeEncoder.EncodeBlock2Sub(type, block, ep0, ep1, ep2, ep3, subsetPartition, signed, out var badTransform); candidates++; - + if (!badTransform) { yield return sub2Block; @@ -313,13 +297,13 @@ private static IEnumerable GenerateCandidates(RawBlock4X4RgbFloat bloc } } } - - internal static Bc6Block EncodeBlock(RawBlock4X4RgbFloat block, bool signed) + + internal static Bc6Block EncodeBlock(RawBlock4X4RgbaFloat block, in RgbEncodingContext context, bool signed) { var result = new Bc6Block(); float bestError = 9999999; - - foreach (var candidate in GenerateCandidates(block, signed)) + + foreach (var candidate in GenerateCandidates(block, context, signed)) { var error = block.CalculateError(candidate.Decode(signed)); diff --git a/BCnEnc.Net/Encoder/Bptc/Bc6EncodingHelpers.cs b/BCnEnc.Net/Encoder/Bptc/Bc6EncodingHelpers.cs index a972ca2..6cbc011 100644 --- a/BCnEnc.Net/Encoder/Bptc/Bc6EncodingHelpers.cs +++ b/BCnEnc.Net/Encoder/Bptc/Bc6EncodingHelpers.cs @@ -5,6 +5,7 @@ using System.Numerics; using System.Text; using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder.Bptc { @@ -16,8 +17,8 @@ internal static class Bc6EncodingHelpers /// internal static int PreQuantize(float value, bool signed) { - var half = new Half(value); - var bits = (int)Half.GetBits(half); + var half = (Half)value; + var bits = (int)BitConverter.HalfToUInt16Bits(half); if (!signed) { @@ -27,7 +28,7 @@ internal static int PreQuantize(float value, bool signed) { const int signMask = ~0x8000; - if (half < new Half(0)) + if (half < (Half)0) { var component = -(bits & signMask); @@ -98,6 +99,10 @@ public static (int, int, int) PreQuantizeRawEndpoint(ColorRgbFloat endpoint, boo ); } + public static (int, int, int) PreQuantizeRawEndpoint(ColorRgbaFloat endpoint, bool signed) + => PreQuantizeRawEndpoint(new ColorRgbFloat(endpoint.r, endpoint.g, endpoint.b), signed); + + public static (int, int, int) FinishQuantizeEndpoint((int, int, int) endpoint, int endpointBits, bool signed) { return ( @@ -154,7 +159,7 @@ public static void GeneratePalette(Span palette, (int, int, int) { var interpolated = Bc6Block.InterpolateColor(unQuantizedEp0, unQuantizedEp1, i, indexPrecision); var (r, g, b) = Bc6Block.FinishUnQuantize(interpolated, signed); - palette[i] = new ColorRgbFloat(r, g, b); + palette[i] = new ColorRgbFloat((float)r, (float)g, (float)b); } } @@ -214,7 +219,7 @@ private static int FindClosestColorIndex(ColorRgbFloat color, ReadOnlySpan indices, bool signed) { const int paletteSize = 1 << 4; @@ -233,7 +238,7 @@ public static float FindOptimalIndicesInt1Sub(RawBlock4X4RgbFloat block, (int, i } - public static float FindOptimalIndices1Sub(RawBlock4X4RgbFloat block, (int, int, int) unQuantizedEp0, (int, int, int) unQuantizedEp1, + public static float FindOptimalIndices1Sub(RawBlock4X4RgbaFloat block, (int, int, int) unQuantizedEp0, (int, int, int) unQuantizedEp1, Span indices, bool signed) { const int paletteSize = 1 << 4; @@ -251,7 +256,7 @@ public static float FindOptimalIndices1Sub(RawBlock4X4RgbFloat block, (int, int, return error; } - public static float FindOptimalIndicesInt2Sub(RawBlock4X4RgbFloat block, (int, int, int) unQuantizedEp0, (int, int, int) unQuantizedEp1, + public static float FindOptimalIndicesInt2Sub(RawBlock4X4RgbaFloat block, (int, int, int) unQuantizedEp0, (int, int, int) unQuantizedEp1, Span indices, int partitionSetId, int subsetIndex, bool signed) { const int paletteSize = 1 << 3; @@ -272,7 +277,7 @@ public static float FindOptimalIndicesInt2Sub(RawBlock4X4RgbFloat block, (int, i return error; } - public static float FindOptimalIndices2Sub(RawBlock4X4RgbFloat block, (int, int, int) unQuantizedEp0, (int, int, int) unQuantizedEp1, + public static float FindOptimalIndices2Sub(RawBlock4X4RgbaFloat block, (int, int, int) unQuantizedEp0, (int, int, int) unQuantizedEp1, Span indices, int partitionSetId, int subsetIndex, bool signed) { const int paletteSize = 1 << 3; @@ -292,7 +297,7 @@ public static float FindOptimalIndices2Sub(RawBlock4X4RgbFloat block, (int, int, return error; } - public static void SwapIndicesIfNecessary1Sub(RawBlock4X4RgbFloat block, ref (int, int, int) unQuantizedEp0, ref (int, int, int) unQuantizedEp1, + public static void SwapIndicesIfNecessary1Sub(RawBlock4X4RgbaFloat block, ref (int, int, int) unQuantizedEp0, ref (int, int, int) unQuantizedEp1, Span indices, bool signed) { const int msb = 1 << (3); @@ -306,7 +311,7 @@ public static void SwapIndicesIfNecessary1Sub(RawBlock4X4RgbFloat block, ref (in FindOptimalIndicesInt1Sub(block, unQuantizedEp0, unQuantizedEp1, indices, signed); } - public static void SwapIndicesIfNecessary2Sub(RawBlock4X4RgbFloat block, ref (int, int, int) unQuantizedEp0, ref (int, int, int) unQuantizedEp1, + public static void SwapIndicesIfNecessary2Sub(RawBlock4X4RgbaFloat block, ref (int, int, int) unQuantizedEp0, ref (int, int, int) unQuantizedEp1, Span indices, int partitionSetId, int subsetIndex, bool signed) { const int msb = 1 << (2); @@ -321,9 +326,9 @@ public static void SwapIndicesIfNecessary2Sub(RawBlock4X4RgbFloat block, ref (in InternalUtils.Swap(ref unQuantizedEp0, ref unQuantizedEp1); FindOptimalIndicesInt2Sub(block, unQuantizedEp0, unQuantizedEp1, indices, partitionSetId, subsetIndex, signed); } - - public static void GetInitialUnscaledEndpointsForSubset(RawBlock4X4RgbFloat block, out ColorRgbFloat ep0, - out ColorRgbFloat ep1, int partitionSetId, int subsetIndex) + + public static void GetInitialUnscaledEndpointsForSubset(RawBlock4X4RgbaFloat block, out ColorRgbFloat ep0, + out ColorRgbFloat ep1, int partitionSetId, int subsetIndex, RgbWeights weights) { var originalPixels = block.AsSpan; @@ -337,7 +342,7 @@ public static void GetInitialUnscaledEndpointsForSubset(RawBlock4X4RgbFloat bloc } } - Span subsetColors = stackalloc ColorRgbFloat[count]; + Span subsetColors = stackalloc ColorRgbaFloat[count]; var next = 0; for (var i = 0; i < 16; i++) { @@ -347,24 +352,22 @@ public static void GetInitialUnscaledEndpointsForSubset(RawBlock4X4RgbFloat bloc } } - PcaVectors.Create(subsetColors, out var mean, out var pa); - PcaVectors.GetExtremePoints(subsetColors, mean, pa, out var min, out var max); + PcaVectors.Create(originalPixels, out var mean, out var pa, out var min, out var max); - ep0 = new ColorRgbFloat(min); - ep1 = new ColorRgbFloat(max); + ep0 = new ColorRgbaFloat(min); + ep1 = new ColorRgbaFloat(max); } - public static void GetInitialUnscaledEndpoints(RawBlock4X4RgbFloat block, out ColorRgbFloat ep0, - out ColorRgbFloat ep1) + public static void GetInitialUnscaledEndpoints(RawBlock4X4RgbaFloat block, out ColorRgbFloat ep0, + out ColorRgbFloat ep1, RgbWeights weights) { var originalPixels = block.AsSpan; - PcaVectors.Create(originalPixels, out var mean, out var pa); - PcaVectors.GetExtremePoints(originalPixels, mean, pa, out var min, out var max); + PcaVectors.Create(originalPixels, out var mean, out var pa, out var min, out var max); - ep0 = new ColorRgbFloat(min); - ep1 = new ColorRgbFloat(max); + ep0 = new ColorRgbaFloat(min); + ep1 = new ColorRgbaFloat(max); } diff --git a/BCnEnc.Net/Encoder/Bptc/Bc6ModeEncoder.cs b/BCnEnc.Net/Encoder/Bptc/Bc6ModeEncoder.cs index 987ab1d..38d245f 100644 --- a/BCnEnc.Net/Encoder/Bptc/Bc6ModeEncoder.cs +++ b/BCnEnc.Net/Encoder/Bptc/Bc6ModeEncoder.cs @@ -3,13 +3,14 @@ using System.Diagnostics; using System.Text; using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder.Bptc { internal static class Bc6ModeEncoder { - public static Bc6Block EncodeBlock1Sub(Bc6BlockType type, RawBlock4X4RgbFloat block, ColorRgbFloat initialEndpoint0, + public static Bc6Block EncodeBlock1Sub(Bc6BlockType type, RawBlock4X4RgbaFloat block, ColorRgbFloat initialEndpoint0, ColorRgbFloat initialEndpoint1, bool signed, out bool badTransform) { var endpointBits = type.EndpointBits(); @@ -74,7 +75,7 @@ public static Bc6Block EncodeBlock1Sub(Bc6BlockType type, RawBlock4X4RgbFloat bl } } - public static Bc6Block EncodeBlock2Sub(Bc6BlockType type, RawBlock4X4RgbFloat block, ColorRgbFloat initialEndpoint0, + public static Bc6Block EncodeBlock2Sub(Bc6BlockType type, RawBlock4X4RgbaFloat block, ColorRgbFloat initialEndpoint0, ColorRgbFloat initialEndpoint1, ColorRgbFloat initialEndpoint2, ColorRgbFloat initialEndpoint3, int partitionSetId, bool signed, out bool badTransform) { diff --git a/BCnEnc.Net/Encoder/Bptc/Bc7Encoder.cs b/BCnEnc.Net/Encoder/Bptc/Bc7Encoder.cs index 2a7f280..9f37cb9 100644 --- a/BCnEnc.Net/Encoder/Bptc/Bc7Encoder.cs +++ b/BCnEnc.Net/Encoder/Bptc/Bc7Encoder.cs @@ -1,43 +1,33 @@ using System; using System.Collections.Generic; using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder.Bptc { - internal class Bc7Encoder : BaseBcBlockEncoder + internal class Bc7Encoder : BaseBcBlockEncoder { - public override Bc7Block EncodeBlock(RawBlock4X4Rgba32 rawBlock, CompressionQuality quality) + public override Bc7Block EncodeBlock(in RgbEncodingContext context) { - switch (quality) + // TODO: Do better. + RawBlock4X4RgbaFloat rawBlock = context.RawBlock; + rawBlock.ColorConvert(context.ColorConversionMode); + + switch (context.Quality) { case CompressionQuality.Fast: - return Bc7EncoderFast.EncodeBlock(rawBlock); + return Bc7EncoderFast.EncodeBlock(rawBlock, context); case CompressionQuality.Balanced: - return Bc7EncoderBalanced.EncodeBlock(rawBlock); + return Bc7EncoderBalanced.EncodeBlock(rawBlock, context); case CompressionQuality.BestQuality: - return Bc7EncoderBestQuality.EncodeBlock(rawBlock); + return Bc7EncoderBestQuality.EncodeBlock(rawBlock, context); default: - throw new ArgumentOutOfRangeException(nameof(quality), quality, null); + throw new ArgumentOutOfRangeException(nameof(context.Quality), context.Quality, null); } } - public override GlInternalFormat GetInternalFormat() - { - return GlInternalFormat.GlCompressedRgbaBptcUnormArb; - } - - public override GlFormat GetBaseInternalFormat() - { - return GlFormat.GlRgba; - } - - public override DxgiFormat GetDxgiFormat() { - return DxgiFormat.DxgiFormatBc7Unorm; - } - - private static ClusterIndices4X4 CreateClusterIndexBlock(RawBlock4X4Rgba32 raw, out int outputNumClusters, + private static ClusterIndices4X4 CreateClusterIndexBlock(RawBlock4X4RgbaFloat raw, out int outputNumClusters, int numClusters = 3) { @@ -67,28 +57,28 @@ private static class Bc7EncoderFast private const float ErrorThreshold = 0.005f; private const int MaxTries = 5; - private static IEnumerable TryMethods(RawBlock4X4Rgba32 rawBlock, int[] best2SubsetPartitions, int[] best3SubsetPartitions, bool alpha) + private static IEnumerable TryMethods(RawBlock4X4RgbaFloat rawBlock, int[] best2SubsetPartitions, int[] best3SubsetPartitions, bool alpha, RgbEncodingContext context) { if (alpha) { - yield return Bc7Mode6Encoder.EncodeBlock(rawBlock, 5); - yield return Bc7Mode5Encoder.EncodeBlock(rawBlock, 3); + yield return Bc7Mode6Encoder.EncodeBlock(rawBlock, 5, context); + yield return Bc7Mode5Encoder.EncodeBlock(rawBlock, 3, context); } else { - yield return Bc7Mode6Encoder.EncodeBlock(rawBlock, 6); + yield return Bc7Mode6Encoder.EncodeBlock(rawBlock, 6, context); for (var i = 0; i < 64; i++) { if(best3SubsetPartitions[i] < 16) { - yield return Bc7Mode0Encoder.EncodeBlock(rawBlock, 3, best3SubsetPartitions[i]); + yield return Bc7Mode0Encoder.EncodeBlock(rawBlock, 3, best3SubsetPartitions[i], context); } - - yield return Bc7Mode1Encoder.EncodeBlock(rawBlock, 4, best2SubsetPartitions[i]); - + + yield return Bc7Mode1Encoder.EncodeBlock(rawBlock, 4, best2SubsetPartitions[i], context); + } } } - public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) + public static Bc7Block EncodeBlock(RawBlock4X4RgbaFloat rawBlock, in RgbEncodingContext context) { var hasAlpha = rawBlock.HasTransparentPixels(); @@ -106,7 +96,7 @@ public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) float bestError = 99999; var best = new Bc7Block(); var tries = 0; - foreach (var block in TryMethods(rawBlock, best2SubsetPartitions, best3SubsetPartitions, hasAlpha)) { + foreach (var block in TryMethods(rawBlock, best2SubsetPartitions, best3SubsetPartitions, hasAlpha, context)) { var decoded = block.Decode(); var error = rawBlock.CalculateYCbCrAlphaError(decoded); tries++; @@ -131,37 +121,37 @@ private static class Bc7EncoderBalanced private const float ErrorThreshold = 0.005f; private const int MaxTries = 25; - private static IEnumerable TryMethods(RawBlock4X4Rgba32 rawBlock, int[] best2SubsetPartitions, int[] best3SubsetPartitions, bool alpha) + private static IEnumerable TryMethods(RawBlock4X4RgbaFloat rawBlock, int[] best2SubsetPartitions, int[] best3SubsetPartitions, bool alpha, RgbEncodingContext context) { if (alpha) { - yield return Bc7Mode6Encoder.EncodeBlock(rawBlock, 6); - yield return Bc7Mode5Encoder.EncodeBlock(rawBlock, 4); - yield return Bc7Mode4Encoder.EncodeBlock(rawBlock, 4); + yield return Bc7Mode6Encoder.EncodeBlock(rawBlock, 6, context); + yield return Bc7Mode5Encoder.EncodeBlock(rawBlock, 4, context); + yield return Bc7Mode4Encoder.EncodeBlock(rawBlock, 4, context); for (var i = 0; i < 64; i++) { - yield return Bc7Mode7Encoder.EncodeBlock(rawBlock, 3, best2SubsetPartitions[i]); + yield return Bc7Mode7Encoder.EncodeBlock(rawBlock, 3, best2SubsetPartitions[i], context); } } else { - yield return Bc7Mode6Encoder.EncodeBlock(rawBlock, 6); - yield return Bc7Mode5Encoder.EncodeBlock(rawBlock, 4); - yield return Bc7Mode4Encoder.EncodeBlock(rawBlock, 4); + yield return Bc7Mode6Encoder.EncodeBlock(rawBlock, 6, context); + yield return Bc7Mode5Encoder.EncodeBlock(rawBlock, 4, context); + yield return Bc7Mode4Encoder.EncodeBlock(rawBlock, 4, context); for (var i = 0; i < 64; i++) { if(best3SubsetPartitions[i] < 16) { - yield return Bc7Mode0Encoder.EncodeBlock(rawBlock, 3, best3SubsetPartitions[i]); + yield return Bc7Mode0Encoder.EncodeBlock(rawBlock, 3, best3SubsetPartitions[i], context); } else { - yield return Bc7Mode2Encoder.EncodeBlock(rawBlock, 5, best3SubsetPartitions[i]); + yield return Bc7Mode2Encoder.EncodeBlock(rawBlock, 5, best3SubsetPartitions[i], context); } - yield return Bc7Mode1Encoder.EncodeBlock(rawBlock, 4, best2SubsetPartitions[i]); + yield return Bc7Mode1Encoder.EncodeBlock(rawBlock, 4, best2SubsetPartitions[i], context); } } } - public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) + public static Bc7Block EncodeBlock(RawBlock4X4RgbaFloat rawBlock, in RgbEncodingContext context) { var hasAlpha = rawBlock.HasTransparentPixels(); @@ -179,7 +169,7 @@ public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) float bestError = 99999; var best = new Bc7Block(); var tries = 0; - foreach (var block in TryMethods(rawBlock, best2SubsetPartitions, best3SubsetPartitions, hasAlpha)) { + foreach (var block in TryMethods(rawBlock, best2SubsetPartitions, best3SubsetPartitions, hasAlpha, context)) { var decoded = block.Decode(); var error = rawBlock.CalculateYCbCrAlphaError(decoded); tries++; @@ -205,38 +195,38 @@ private static class Bc7EncoderBestQuality private const float ErrorThreshold = 0.001f; private const int MaxTries = 40; - private static IEnumerable TryMethods(RawBlock4X4Rgba32 rawBlock, int[] best2SubsetPartitions, int[] best3SubsetPartitions, bool alpha) + private static IEnumerable TryMethods(RawBlock4X4RgbaFloat rawBlock, int[] best2SubsetPartitions, int[] best3SubsetPartitions, bool alpha, RgbEncodingContext context) { if (alpha) { - yield return Bc7Mode6Encoder.EncodeBlock(rawBlock, 8); - yield return Bc7Mode5Encoder.EncodeBlock(rawBlock, 5); - yield return Bc7Mode4Encoder.EncodeBlock(rawBlock, 5); + yield return Bc7Mode6Encoder.EncodeBlock(rawBlock, 8, context); + yield return Bc7Mode5Encoder.EncodeBlock(rawBlock, 5, context); + yield return Bc7Mode4Encoder.EncodeBlock(rawBlock, 5, context); for (var i = 0; i < 64; i++) { - yield return Bc7Mode7Encoder.EncodeBlock(rawBlock, 4, best2SubsetPartitions[i]); + yield return Bc7Mode7Encoder.EncodeBlock(rawBlock, 4, best2SubsetPartitions[i], context); } } else { - yield return Bc7Mode6Encoder.EncodeBlock(rawBlock, 8); - yield return Bc7Mode5Encoder.EncodeBlock(rawBlock, 5); - yield return Bc7Mode4Encoder.EncodeBlock(rawBlock, 5); + yield return Bc7Mode6Encoder.EncodeBlock(rawBlock, 8, context); + yield return Bc7Mode5Encoder.EncodeBlock(rawBlock, 5, context); + yield return Bc7Mode4Encoder.EncodeBlock(rawBlock, 5, context); for (var i = 0; i < 64; i++) { if(best3SubsetPartitions[i] < 16) { - yield return Bc7Mode0Encoder.EncodeBlock(rawBlock, 4, best3SubsetPartitions[i]); + yield return Bc7Mode0Encoder.EncodeBlock(rawBlock, 4, best3SubsetPartitions[i], context); } - yield return Bc7Mode2Encoder.EncodeBlock(rawBlock, 5, best3SubsetPartitions[i]); + yield return Bc7Mode2Encoder.EncodeBlock(rawBlock, 5, best3SubsetPartitions[i], context); - yield return Bc7Mode1Encoder.EncodeBlock(rawBlock, 4, best2SubsetPartitions[i]); - yield return Bc7Mode3Encoder.EncodeBlock(rawBlock, 5, best2SubsetPartitions[i]); + yield return Bc7Mode1Encoder.EncodeBlock(rawBlock, 4, best2SubsetPartitions[i], context); + yield return Bc7Mode3Encoder.EncodeBlock(rawBlock, 5, best2SubsetPartitions[i], context); } } } - public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) + public static Bc7Block EncodeBlock(RawBlock4X4RgbaFloat rawBlock, in RgbEncodingContext context) { var hasAlpha = rawBlock.HasTransparentPixels(); @@ -255,7 +245,7 @@ public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) float bestError = 99999; var best = new Bc7Block(); var tries = 0; - foreach (var block in TryMethods(rawBlock, best2SubsetPartitions, best3SubsetPartitions, hasAlpha)) { + foreach (var block in TryMethods(rawBlock, best2SubsetPartitions, best3SubsetPartitions, hasAlpha, context)) { var decoded = block.Decode(); var error = rawBlock.CalculateYCbCrAlphaError(decoded); tries++; diff --git a/BCnEnc.Net/Encoder/Bptc/Bc7EncodingHelpers.cs b/BCnEnc.Net/Encoder/Bptc/Bc7EncodingHelpers.cs index 5b39c06..61d9a2e 100644 --- a/BCnEnc.Net/Encoder/Bptc/Bc7EncodingHelpers.cs +++ b/BCnEnc.Net/Encoder/Bptc/Bc7EncodingHelpers.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Runtime.InteropServices; using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder.Bptc { @@ -10,13 +11,13 @@ internal static class Bc7EncodingHelpers { private static readonly int[] varPatternRAlpha = new int[] { 1, -1, 1, 0, 0, -1, 0, 0, 0, 0 }; private static readonly int[] varPatternRNoAlpha = new int[] { 1, -1, 1, 0, 0, -1, 0, 0 }; - + private static readonly int[] varPatternGAlpha = new int[] { 1, -1, 0, 1, 0, 0, -1, 0, 0, 0 }; private static readonly int[] varPatternGNoAlpha = new int[] { 1, -1, 0, 1, 0, 0, -1, 0 }; - + private static readonly int[] varPatternBAlpha = new int[] { 1, -1, 0, 0, 1, 0, 0, -1, 0, 0 }; private static readonly int[] varPatternBNoAlpha = new int[] { 1, -1, 0, 0, 1, 0, 0, -1 }; - + private static readonly int[] varPatternAAlpha = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 1, -1 }; private static readonly int[] varPatternANoAlpha = new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }; @@ -206,20 +207,20 @@ public static ColorRgba32 ExpandEndpoint(Bc7BlockType type, ColorRgba32 endpoint } - public static void GetInitialUnscaledEndpoints(RawBlock4X4Rgba32 block, out ColorRgba32 ep0, - out ColorRgba32 ep1) + public static void GetInitialUnscaledEndpoints(RawBlock4X4RgbaFloat block, out ColorRgba32 ep0, + out ColorRgba32 ep1, RgbWeights weights) { var originalPixels = block.AsSpan; - PcaVectors.CreateWithAlpha(originalPixels, out var mean, out var pa); - PcaVectors.GetExtremePointsWithAlpha(block.AsSpan, mean, pa, out var min, out var max); + + PcaVectors.Create(originalPixels, out var mean, out var pa, out var min, out var max); ep0 = new ColorRgba32((byte)(min.X * 255), (byte)(min.Y * 255), (byte)(min.Z * 255), (byte)(min.W * 255)); ep1 = new ColorRgba32((byte)(max.X * 255), (byte)(max.Y * 255), (byte)(max.Z * 255), (byte)(max.W * 255)); } - public static void GetInitialUnscaledEndpointsForSubset(RawBlock4X4Rgba32 block, out ColorRgba32 ep0, - out ColorRgba32 ep1, ReadOnlySpan partitionTable, int subsetIndex) + public static void GetInitialUnscaledEndpointsForSubset(RawBlock4X4RgbaFloat block, out ColorRgba32 ep0, + out ColorRgba32 ep1, ReadOnlySpan partitionTable, int subsetIndex, RgbWeights weights) { var originalPixels = block.AsSpan; @@ -233,7 +234,7 @@ public static void GetInitialUnscaledEndpointsForSubset(RawBlock4X4Rgba32 block, } } - Span subsetColors = stackalloc ColorRgba32[count]; + Span subsetColors = stackalloc ColorRgbaFloat[count]; var next = 0; for (var i = 0; i < 16; i++) { @@ -243,8 +244,7 @@ public static void GetInitialUnscaledEndpointsForSubset(RawBlock4X4Rgba32 block, } } - PcaVectors.CreateWithAlpha(subsetColors, out var mean, out var pa); - PcaVectors.GetExtremePointsWithAlpha(block.AsSpan, mean, pa, out var min, out var max); + PcaVectors.Create(originalPixels, out var mean, out var pa, out var min, out var max); ep0 = new ColorRgba32((byte)(min.X * 255), (byte)(min.Y * 255), (byte)(min.Z * 255), (byte)(min.W * 255)); ep1 = new ColorRgba32((byte)(max.X * 255), (byte)(max.Y * 255), (byte)(max.Z * 255), (byte)(max.W * 255)); @@ -378,7 +378,7 @@ private static int FindClosestAlphaIndex(byte alpha, ReadOnlySpan alphas, } - private static float TrySubsetEndpoints(Bc7BlockType type, RawBlock4X4Rgba32 raw, ColorRgba32 ep0, ColorRgba32 ep1, + private static float TrySubsetEndpoints(Bc7BlockType type, RawBlock4X4RgbaFloat raw, ColorRgba32 ep0, ColorRgba32 ep1, ReadOnlySpan partitionTable, int subsetIndex, int type4IdxMode) { var colorIndexPrecision = GetColorIndexBitCount(type, type4IdxMode); @@ -391,8 +391,8 @@ private static float TrySubsetEndpoints(Bc7BlockType type, RawBlock4X4Rgba32 raw for (var i = 0; i < colors.Length; i++) { - colors[i] = new ColorYCbCr(InterpolateColor(ep0, ep1, i, - 0, colorIndexPrecision, 0)); + colors[i] = InterpolateColor(ep0, ep1, i, + 0, colorIndexPrecision, 0).As(); } for (var i = 0; i < alphas.Length; i++) @@ -406,10 +406,10 @@ private static float TrySubsetEndpoints(Bc7BlockType type, RawBlock4X4Rgba32 raw for (var i = 0; i < 16; i++) { - var pixelColor = new ColorYCbCr(pixels[i]); + var pixelColor = pixels[i].As(); FindClosestColorIndex(pixelColor, colors, out var ce); - FindClosestAlphaIndex(pixels[i].a, alphas, out var ae); + FindClosestAlphaIndex(ByteHelper.FloatToByte(pixels[i].a), alphas, out var ae); error += ce + ae; } @@ -421,8 +421,8 @@ private static float TrySubsetEndpoints(Bc7BlockType type, RawBlock4X4Rgba32 raw Span colors = stackalloc ColorYCbCrAlpha[1 << colorIndexPrecision]; for (var i = 0; i < colors.Length; i++) { - colors[i] = new ColorYCbCrAlpha(InterpolateColor(ep0, ep1, i, - i, colorIndexPrecision, alphaIndexPrecision)); + colors[i] = InterpolateColor(ep0, ep1, i, + i, colorIndexPrecision, alphaIndexPrecision).As(); } var pixels = raw.AsSpan; @@ -433,7 +433,7 @@ private static float TrySubsetEndpoints(Bc7BlockType type, RawBlock4X4Rgba32 raw { if (partitionTable[i] == subsetIndex) { - var pixelColor = new ColorYCbCrAlpha(pixels[i]); + var pixelColor = pixels[i].As(); FindClosestColorIndex(pixelColor, colors, out var e); error += e * e; @@ -447,7 +447,7 @@ private static float TrySubsetEndpoints(Bc7BlockType type, RawBlock4X4Rgba32 raw } - public static void FillSubsetIndices(Bc7BlockType type, RawBlock4X4Rgba32 raw, ColorRgba32 ep0, ColorRgba32 ep1, ReadOnlySpan partitionTable, int subsetIndex, + public static void FillSubsetIndices(Bc7BlockType type, RawBlock4X4RgbaFloat raw, ColorRgba32 ep0, ColorRgba32 ep1, ReadOnlySpan partitionTable, int subsetIndex, Span indicesToFill) { var colorIndexPrecision = GetColorIndexBitCount(type); @@ -462,8 +462,8 @@ public static void FillSubsetIndices(Bc7BlockType type, RawBlock4X4Rgba32 raw, C Span colors = stackalloc ColorYCbCrAlpha[1 << colorIndexPrecision]; for (var i = 0; i < colors.Length; i++) { - colors[i] = new ColorYCbCrAlpha(InterpolateColor(ep0, ep1, i, - i, colorIndexPrecision, alphaIndexPrecision)); + colors[i] = InterpolateColor(ep0, ep1, i, + i, colorIndexPrecision, alphaIndexPrecision).As(); } var pixels = raw.AsSpan; @@ -472,7 +472,7 @@ public static void FillSubsetIndices(Bc7BlockType type, RawBlock4X4Rgba32 raw, C { if (partitionTable[i] == subsetIndex) { - var pixelColor = new ColorYCbCrAlpha(pixels[i]); + var pixelColor = pixels[i].As(); var index = FindClosestColorIndex(pixelColor, colors, out var e); indicesToFill[i] = (byte)index; @@ -484,7 +484,7 @@ public static void FillSubsetIndices(Bc7BlockType type, RawBlock4X4Rgba32 raw, C /// /// Used for Modes 4 and 5 /// - public static void FillAlphaColorIndices(Bc7BlockType type, RawBlock4X4Rgba32 raw, ColorRgba32 ep0, ColorRgba32 ep1, + public static void FillAlphaColorIndices(Bc7BlockType type, RawBlock4X4RgbaFloat raw, ColorRgba32 ep0, ColorRgba32 ep1, Span colorIndicesToFill, Span alphaIndicesToFill, int idxMode = 0) { var colorIndexPrecision = GetColorIndexBitCount(type, idxMode); @@ -497,8 +497,8 @@ public static void FillAlphaColorIndices(Bc7BlockType type, RawBlock4X4Rgba32 ra for (var i = 0; i < colors.Length; i++) { - colors[i] = new ColorYCbCr(InterpolateColor(ep0, ep1, i, - 0, colorIndexPrecision, 0)); + colors[i] = InterpolateColor(ep0, ep1, i, + 0, colorIndexPrecision, 0).As(); } for (var i = 0; i < alphas.Length; i++) @@ -511,12 +511,12 @@ public static void FillAlphaColorIndices(Bc7BlockType type, RawBlock4X4Rgba32 ra for (var i = 0; i < 16; i++) { - var pixelColor = new ColorYCbCr(pixels[i]); + var pixelColor = pixels[i].As(); var index = FindClosestColorIndex(pixelColor, colors, out _); colorIndicesToFill[i] = (byte)index; - index = FindClosestAlphaIndex(pixels[i].a, alphas, out _); + index = FindClosestAlphaIndex(ByteHelper.FloatToByte(pixels[i].a), alphas, out _); alphaIndicesToFill[i] = (byte)index; } } @@ -526,7 +526,7 @@ public static void FillAlphaColorIndices(Bc7BlockType type, RawBlock4X4Rgba32 ra } } - public static void OptimizeSubsetEndpointsWithPBit(Bc7BlockType type, RawBlock4X4Rgba32 raw, ref ColorRgba32 ep0, ref ColorRgba32 ep1, ref byte pBit0, ref byte pBit1, + public static void OptimizeSubsetEndpointsWithPBit(Bc7BlockType type, RawBlock4X4RgbaFloat raw, ref ColorRgba32 ep0, ref ColorRgba32 ep1, ref byte pBit0, ref byte pBit1, int variation, ReadOnlySpan partitionTable, int subsetIndex, bool variatePBits, bool variateAlpha, int type4IdxMode = 0) { @@ -667,14 +667,14 @@ public static void OptimizeSubsetEndpointsWithPBit(Bc7BlockType type, RawBlock4X } } - public static RawBlock4X4Rgba32 RotateBlockColors(RawBlock4X4Rgba32 block, int rotation) + public static RawBlock4X4RgbaFloat RotateBlockColors(RawBlock4X4RgbaFloat block, int rotation) { if (rotation == 0) { return block; } - var rotated = new RawBlock4X4Rgba32(); + var rotated = new RawBlock4X4RgbaFloat(); var pixels = block.AsSpan; var output = rotated.AsSpan; for (var i = 0; i < 16; i++) @@ -683,13 +683,13 @@ public static RawBlock4X4Rgba32 RotateBlockColors(RawBlock4X4Rgba32 block, int r switch (rotation) { case 1: - output[i] = new ColorRgba32(c.a, c.g, c.b, c.r); + output[i] = new ColorRgbaFloat(c.a, c.g, c.b, c.r); break; case 2: - output[i] = new ColorRgba32(c.r, c.a, c.b, c.g); + output[i] = new ColorRgbaFloat(c.r, c.a, c.b, c.g); break; case 3: - output[i] = new ColorRgba32(c.r, c.g, c.a, c.b); + output[i] = new ColorRgbaFloat(c.r, c.g, c.a, c.b); break; } } diff --git a/BCnEnc.Net/Encoder/Bptc/Bc7Mode0Encoder.cs b/BCnEnc.Net/Encoder/Bptc/Bc7Mode0Encoder.cs index 4b51330..f4dad9a 100644 --- a/BCnEnc.Net/Encoder/Bptc/Bc7Mode0Encoder.cs +++ b/BCnEnc.Net/Encoder/Bptc/Bc7Mode0Encoder.cs @@ -1,12 +1,13 @@ using System; using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder.Bptc { internal static class Bc7Mode0Encoder { - public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariation, int bestPartition) + public static Bc7Block EncodeBlock(RawBlock4X4RgbaFloat block, int startingVariation, int bestPartition, in RgbEncodingContext context) { var output = new Bc7Block(); const Bc7BlockType type = Bc7BlockType.Type0; @@ -29,9 +30,9 @@ public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariatio }; for (var subset = 0; subset < 3; subset++) { - + Bc7EncodingHelpers.GetInitialUnscaledEndpointsForSubset(block, out var ep0, out var ep1, - partitionTable, subset); + partitionTable, subset, context.Weights); var scaledEp0 = Bc7EncodingHelpers.ScaleDownEndpoint(ep0, type, true, out var pBit0); var scaledEp1 = diff --git a/BCnEnc.Net/Encoder/Bptc/Bc7Mode1Encoder.cs b/BCnEnc.Net/Encoder/Bptc/Bc7Mode1Encoder.cs index c51c0a0..7173019 100644 --- a/BCnEnc.Net/Encoder/Bptc/Bc7Mode1Encoder.cs +++ b/BCnEnc.Net/Encoder/Bptc/Bc7Mode1Encoder.cs @@ -1,12 +1,13 @@ using System; using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder.Bptc { internal static class Bc7Mode1Encoder { - public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariation, int bestPartition) + public static Bc7Block EncodeBlock(RawBlock4X4RgbaFloat block, int startingVariation, int bestPartition, in RgbEncodingContext context) { var output = new Bc7Block(); const Bc7BlockType type = Bc7BlockType.Type1; @@ -24,9 +25,9 @@ public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariatio }; for (var subset = 0; subset < 2; subset++) { - + Bc7EncodingHelpers.GetInitialUnscaledEndpointsForSubset(block, out var ep0, out var ep1, - partitionTable, subset); + partitionTable, subset, context.Weights); var scaledEp0 = Bc7EncodingHelpers.ScaleDownEndpoint(ep0, type, true, out var pBit); var scaledEp1 = diff --git a/BCnEnc.Net/Encoder/Bptc/Bc7Mode2Encoder.cs b/BCnEnc.Net/Encoder/Bptc/Bc7Mode2Encoder.cs index 24cfbcf..6fd0928 100644 --- a/BCnEnc.Net/Encoder/Bptc/Bc7Mode2Encoder.cs +++ b/BCnEnc.Net/Encoder/Bptc/Bc7Mode2Encoder.cs @@ -1,12 +1,13 @@ using System; using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder.Bptc { internal static class Bc7Mode2Encoder { - public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariation, int bestPartition) + public static Bc7Block EncodeBlock(RawBlock4X4RgbaFloat block, int startingVariation, int bestPartition, in RgbEncodingContext context) { var output = new Bc7Block(); const Bc7BlockType type = Bc7BlockType.Type2; @@ -23,9 +24,9 @@ public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariatio }; for (var subset = 0; subset < 3; subset++) { - + Bc7EncodingHelpers.GetInitialUnscaledEndpointsForSubset(block, out var ep0, out var ep1, - partitionTable, subset); + partitionTable, subset, context.Weights); var scaledEp0 = Bc7EncodingHelpers.ScaleDownEndpoint(ep0, type, true, out var _); var scaledEp1 = diff --git a/BCnEnc.Net/Encoder/Bptc/Bc7Mode3Encoder.cs b/BCnEnc.Net/Encoder/Bptc/Bc7Mode3Encoder.cs index 5bbed25..982df25 100644 --- a/BCnEnc.Net/Encoder/Bptc/Bc7Mode3Encoder.cs +++ b/BCnEnc.Net/Encoder/Bptc/Bc7Mode3Encoder.cs @@ -1,12 +1,13 @@ using System; using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder.Bptc { internal static class Bc7Mode3Encoder { - public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariation, int bestPartition) + public static Bc7Block EncodeBlock(RawBlock4X4RgbaFloat block, int startingVariation, int bestPartition, in RgbEncodingContext context) { var output = new Bc7Block(); const Bc7BlockType type = Bc7BlockType.Type3; @@ -23,9 +24,9 @@ public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariatio }; for (var subset = 0; subset < 2; subset++) { - + Bc7EncodingHelpers.GetInitialUnscaledEndpointsForSubset(block, out var ep0, out var ep1, - partitionTable, subset); + partitionTable, subset, context.Weights); var scaledEp0 = Bc7EncodingHelpers.ScaleDownEndpoint(ep0, type, true, out var pBit0); var scaledEp1 = diff --git a/BCnEnc.Net/Encoder/Bptc/Bc7Mode4Encoder.cs b/BCnEnc.Net/Encoder/Bptc/Bc7Mode4Encoder.cs index 19b8718..f860b30 100644 --- a/BCnEnc.Net/Encoder/Bptc/Bc7Mode4Encoder.cs +++ b/BCnEnc.Net/Encoder/Bptc/Bc7Mode4Encoder.cs @@ -9,7 +9,7 @@ internal static class Bc7Mode4Encoder private static ReadOnlySpan PartitionTable => new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; const int Subset = 0; - public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariation) + public static Bc7Block EncodeBlock(RawBlock4X4RgbaFloat block, int startingVariation, in RgbEncodingContext context) { var type = Bc7BlockType.Type4; @@ -22,7 +22,7 @@ public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariatio var rotatedBlock = Bc7EncodingHelpers.RotateBlockColors(block, rotation); var output = new Bc7Block(); - Bc7EncodingHelpers.GetInitialUnscaledEndpoints(rotatedBlock, out var ep0, out var ep1); + Bc7EncodingHelpers.GetInitialUnscaledEndpoints(rotatedBlock, out var ep0, out var ep1, context.Weights); var scaledEp0 = Bc7EncodingHelpers.ScaleDownEndpoint(ep0, type, false, out var _); diff --git a/BCnEnc.Net/Encoder/Bptc/Bc7Mode5Encoder.cs b/BCnEnc.Net/Encoder/Bptc/Bc7Mode5Encoder.cs index 8ee9456..ce71fff 100644 --- a/BCnEnc.Net/Encoder/Bptc/Bc7Mode5Encoder.cs +++ b/BCnEnc.Net/Encoder/Bptc/Bc7Mode5Encoder.cs @@ -7,8 +7,8 @@ internal static class Bc7Mode5Encoder { private static ReadOnlySpan PartitionTable => new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; const int Subset = 0; - - public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariation) + + public static Bc7Block EncodeBlock(RawBlock4X4RgbaFloat block, int startingVariation, in RgbEncodingContext context) { var type = Bc7BlockType.Type5; @@ -18,7 +18,7 @@ public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariatio var rotatedBlock = Bc7EncodingHelpers.RotateBlockColors(block, rotation); var output = new Bc7Block(); - Bc7EncodingHelpers.GetInitialUnscaledEndpoints(rotatedBlock, out var ep0, out var ep1); + Bc7EncodingHelpers.GetInitialUnscaledEndpoints(rotatedBlock, out var ep0, out var ep1, context.Weights); var scaledEp0 = Bc7EncodingHelpers.ScaleDownEndpoint(ep0, type, false, out var _); @@ -100,7 +100,7 @@ public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariatio bestIndex = i; } } - + return outputs[bestIndex]; } } diff --git a/BCnEnc.Net/Encoder/Bptc/Bc7Mode6Encoder.cs b/BCnEnc.Net/Encoder/Bptc/Bc7Mode6Encoder.cs index 4765482..c5f38b4 100644 --- a/BCnEnc.Net/Encoder/Bptc/Bc7Mode6Encoder.cs +++ b/BCnEnc.Net/Encoder/Bptc/Bc7Mode6Encoder.cs @@ -6,12 +6,12 @@ namespace BCnEncoder.Encoder.Bptc internal static class Bc7Mode6Encoder { - public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariation) + public static Bc7Block EncodeBlock(RawBlock4X4RgbaFloat block, int startingVariation, in RgbEncodingContext context) { var hasAlpha = block.HasTransparentPixels(); var output = new Bc7Block(); - Bc7EncodingHelpers.GetInitialUnscaledEndpoints(block, out var ep0, out var ep1); + Bc7EncodingHelpers.GetInitialUnscaledEndpoints(block, out var ep0, out var ep1, context.Weights); var scaledEp0 = Bc7EncodingHelpers.ScaleDownEndpoint(ep0, Bc7BlockType.Type6, false, out var pBit0); @@ -39,7 +39,7 @@ public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariatio ep1, partitionTable, subset, indices); - + if ((indices[0] & 0b1000) > 0) //If anchor index most significant bit is 1, switch endpoints { diff --git a/BCnEnc.Net/Encoder/Bptc/Bc7Mode7Encoder.cs b/BCnEnc.Net/Encoder/Bptc/Bc7Mode7Encoder.cs index d3d1e90..ad6723f 100644 --- a/BCnEnc.Net/Encoder/Bptc/Bc7Mode7Encoder.cs +++ b/BCnEnc.Net/Encoder/Bptc/Bc7Mode7Encoder.cs @@ -1,12 +1,13 @@ using System; using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder.Bptc { internal static class Bc7Mode7Encoder { - public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariation, int bestPartition) + public static Bc7Block EncodeBlock(RawBlock4X4RgbaFloat block, int startingVariation, int bestPartition, in RgbEncodingContext context) { var output = new Bc7Block(); const Bc7BlockType type = Bc7BlockType.Type7; @@ -23,9 +24,9 @@ public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariatio }; for (var subset = 0; subset < 2; subset++) { - + Bc7EncodingHelpers.GetInitialUnscaledEndpointsForSubset(block, out var ep0, out var ep1, - partitionTable, subset); + partitionTable, subset, context.Weights); var scaledEp0 = Bc7EncodingHelpers.ScaleDownEndpoint(ep0, type, false, out var pBit0); var scaledEp1 = diff --git a/BCnEnc.Net/Encoder/ColorChooser.cs b/BCnEnc.Net/Encoder/ColorChooser.cs index 0fbf6ef..9c91324 100644 --- a/BCnEnc.Net/Encoder/ColorChooser.cs +++ b/BCnEnc.Net/Encoder/ColorChooser.cs @@ -1,187 +1,52 @@ using System; +using System.Drawing; +using System.Numerics; using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; +using CommunityToolkit.HighPerformance; namespace BCnEncoder.Encoder { internal static class ColorChooser { - - public static int ChooseClosestColor4(ReadOnlySpan colors, ColorRgba32 color, float rWeight, float gWeight, float bWeight, out float error) - { - ReadOnlySpan d = stackalloc float[4] { - MathF.Abs(colors[0].r - color.r) * rWeight - + MathF.Abs(colors[0].g - color.g) * gWeight - + MathF.Abs(colors[0].b - color.b) * bWeight, - MathF.Abs(colors[1].r - color.r) * rWeight - + MathF.Abs(colors[1].g - color.g) * gWeight - + MathF.Abs(colors[1].b - color.b) * bWeight, - MathF.Abs(colors[2].r - color.r) * rWeight - + MathF.Abs(colors[2].g - color.g) * gWeight - + MathF.Abs(colors[2].b - color.b) * bWeight, - MathF.Abs(colors[3].r - color.r) * rWeight - + MathF.Abs(colors[3].g - color.g) * gWeight - + MathF.Abs(colors[3].b - color.b) * bWeight, - }; - - var b0 = d[0] > d[3] ? 1 : 0; - var b1 = d[1] > d[2] ? 1 : 0; - var b2 = d[0] > d[2] ? 1 : 0; - var b3 = d[1] > d[3] ? 1 : 0; - var b4 = d[2] > d[3] ? 1 : 0; - - var x0 = b1 & b2; - var x1 = b0 & b3; - var x2 = b0 & b4; - - var idx = x2 | ((x0 | x1) << 1); - error = d[idx]; - return idx; - } - - - public static int ChooseClosestColor4AlphaCutoff(ReadOnlySpan colors, ColorRgba32 color, float rWeight, float gWeight, float bWeight, int alphaCutoff, bool hasAlpha, out float error) + public static int ChooseClosestRgbColor4(ReadOnlySpan colors, Vector4 color, RgbWeights weights, out float error) { - - if (hasAlpha && color.a < alphaCutoff) + int idx = 0; + error = weights.CalculateColorDiff(colors[0], color); + for (int i = 1; i < 4; i++) { - error = 0; - return 3; - } - - ReadOnlySpan d = stackalloc float[4] { - MathF.Abs(colors[0].r - color.r) * rWeight - + MathF.Abs(colors[0].g - color.g) * gWeight - + MathF.Abs(colors[0].b - color.b) * bWeight, - MathF.Abs(colors[1].r - color.r) * rWeight - + MathF.Abs(colors[1].g - color.g) * gWeight - + MathF.Abs(colors[1].b - color.b) * bWeight, - MathF.Abs(colors[2].r - color.r) * rWeight - + MathF.Abs(colors[2].g - color.g) * gWeight - + MathF.Abs(colors[2].b - color.b) * bWeight, - - hasAlpha ? 999 : - MathF.Abs(colors[3].r - color.r) * rWeight - + MathF.Abs(colors[3].g - color.g) * gWeight - + MathF.Abs(colors[3].b - color.b) * bWeight, - }; - - var b0 = d[0] > d[2] ? 1 : 0; - var b1 = d[1] > d[3] ? 1 : 0; - var b2 = d[0] > d[3] ? 1 : 0; - var b3 = d[1] > d[2] ? 1 : 0; - var nb3 = d[1] > d[2] ? 0 : 1; - var b4 = d[0] > d[1] ? 1 : 0; - var b5 = d[2] > d[3] ? 1 : 0; - - var idx = (nb3 & b4) | (b2 & b5) | (((b0 & b3) | (b1 & b2)) << 1); - - error = d[idx]; - return idx; - } - - public static int ChooseClosestColor(Span colors, ColorRgba32 color) - { - var closest = 0; - var closestError = - Math.Abs(colors[0].r - color.r) - + Math.Abs(colors[0].g - color.g) - + Math.Abs(colors[0].b - color.b); - - for (var i = 1; i < colors.Length; i++) - { - var error = - Math.Abs(colors[i].r - color.r) - + Math.Abs(colors[i].g - color.g) - + Math.Abs(colors[i].b - color.b); - if (error < closestError) + float d = weights.CalculateColorDiff(colors[i], color); + if (d < error) { - closest = i; - closestError = error; + idx = i; + error = d; } } - return closest; + return idx; } - public static int ChooseClosestColor(Span colors, ColorRgba32 color) - { - var closest = 0; - var closestError = - Math.Abs(colors[0].r - color.r) - + Math.Abs(colors[0].g - color.g) - + Math.Abs(colors[0].b - color.b) - + Math.Abs(colors[0].a - color.a); - for (var i = 1; i < colors.Length; i++) - { - var error = - Math.Abs(colors[i].r - color.r) - + Math.Abs(colors[i].g - color.g) - + Math.Abs(colors[i].b - color.b) - + Math.Abs(colors[i].a - color.a); - if (error < closestError) - { - closest = i; - closestError = error; - } - } - return closest; - } - - public static int ChooseClosestColorAlphaCutOff(Span colors, ColorRgba32 color, byte alphaCutOff = 255 / 2) + public static int ChooseClosestRgbColor4AlphaCutoff(ReadOnlySpan colors, Vector4 color, RgbWeights weights, float alphaCutoff, bool hasAlpha, out float error) { - if (color.a <= alphaCutOff) + if (hasAlpha && color.W < alphaCutoff) { + error = 0; return 3; } - var closest = 0; - var closestError = - Math.Abs(colors[0].r - color.r) - + Math.Abs(colors[0].g - color.g) - + Math.Abs(colors[0].b - color.b); - - for (var i = 1; i < colors.Length; i++) + int idx = 0; + error = weights.CalculateColorDiff(colors[0], color); + for (int i = 1; i < 4; i++) { - if (i == 3) continue; // Skip transparent - var error = - Math.Abs(colors[i].r - color.r) - + Math.Abs(colors[i].g - color.g) - + Math.Abs(colors[i].b - color.b); - if (error < closestError) + float d = weights.CalculateColorDiff(colors[i], color); + if (d < error) { - closest = i; - closestError = error; + idx = i; + error = d; } } - return closest; - } - - public static int ChooseClosestColor(Span colors, ColorYCbCr color, float luminanceMultiplier = 4) - { - var closest = 0; - float closestError = 0; - var first = true; - for (var i = 0; i < colors.Length; i++) - { - var error = MathF.Abs(colors[i].y - color.y) * luminanceMultiplier - + MathF.Abs(colors[i].cb - color.cb) - + MathF.Abs(colors[i].cr - color.cr); - if (first) - { - closestError = error; - first = false; - } - else if (error < closestError) - { - closest = i; - closestError = error; - } - } - return closest; + return idx; } - - public static int ChooseClosestColor(Span colors, ColorRgba32 color, float luminanceMultiplier = 4) - => ChooseClosestColor(colors, new ColorYCbCr(color), luminanceMultiplier); } } diff --git a/BCnEnc.Net/Encoder/ColorVariationGenerator.cs b/BCnEnc.Net/Encoder/ColorVariationGenerator.cs index 2f179e3..bad950d 100644 --- a/BCnEnc.Net/Encoder/ColorVariationGenerator.cs +++ b/BCnEnc.Net/Encoder/ColorVariationGenerator.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder { @@ -14,11 +15,11 @@ internal static class ColorVariationGenerator private static readonly int[] variatePatternEp1B = new int[] { -1, 0, 0, -1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 0, 0, 1, 0, 0, -1 }; public static int VarPatternCount => variatePatternEp0R.Length; - public static (ColorRgb565, ColorRgb565) Variate565(ColorRgb565 c0, ColorRgb565 c1, int i) + public static (ColorB5G6R5Packed, ColorB5G6R5Packed) Variate565(ColorB5G6R5Packed c0, ColorB5G6R5Packed c1, int i) { var idx = i % variatePatternEp0R.Length; - var newEp0 = new ColorRgb565(); - var newEp1 = new ColorRgb565(); + var newEp0 = new ColorB5G6R5Packed(); + var newEp1 = new ColorB5G6R5Packed(); newEp0.RawR = ByteHelper.ClampToByte(c0.RawR + variatePatternEp0R[idx]); newEp0.RawG = ByteHelper.ClampToByte(c0.RawG + variatePatternEp0G[idx]); diff --git a/BCnEnc.Net/Encoder/IBcBlockEncoder.cs b/BCnEnc.Net/Encoder/IBcBlockEncoder.cs index 3fd4182..4c84b7a 100644 --- a/BCnEnc.Net/Encoder/IBcBlockEncoder.cs +++ b/BCnEnc.Net/Encoder/IBcBlockEncoder.cs @@ -1,18 +1,12 @@ using System; using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder { - internal interface IBcBlockEncoder where T : unmanaged + internal interface IBcBlockEncoder : IBcEncoder { - byte[] Encode(T[] blocks, int blockWidth, int blockHeight, CompressionQuality quality, OperationContext context); - void EncodeBlock(T block, CompressionQuality quality, Span output); - GlInternalFormat GetInternalFormat(); - GlFormat GetBaseInternalFormat(); - DxgiFormat GetDxgiFormat(); + void EncodeBlocks(ReadOnlySpan blocks, Span output, OperationContext context); int GetBlockSize(); } - - } diff --git a/BCnEnc.Net/Encoder/IBlockEncodingContext.cs b/BCnEnc.Net/Encoder/IBlockEncodingContext.cs new file mode 100644 index 0000000..c9fed04 --- /dev/null +++ b/BCnEnc.Net/Encoder/IBlockEncodingContext.cs @@ -0,0 +1,115 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; +using CommunityToolkit.HighPerformance; + +namespace BCnEncoder.Encoder; + +internal interface IBlockEncodingContext +{ + /// + /// Raw block in linear rgba color space + /// + public RawBlock4X4RgbaFloat RawBlock { get; } + + /// + /// Raw block in linear rgba color space as Vector4s + /// + public RawBlock4X4Vector4 RawBlockVec { get; } + + /// + /// The compression quality to use for the operation. + /// + public CompressionQuality Quality { get; } +} + +internal static class BlockEncodingContextHelpers +{ + public static TContext Create(RawBlock4X4RgbaFloat rawBlock, OperationContext context) + where TContext : struct, IBlockEncodingContext + { + if (typeof(TContext) == typeof(RgbEncodingContext)) + { + var rgbContext = new RgbEncodingContext(rawBlock, context.Quality, context.Weights, context.ColorConversionMode, context.AlphaThreshold); + return (TContext)(object)rgbContext; + } + if (typeof(TContext) == typeof(RgEncodingContext)) + { + var rgContext = new RgEncodingContext(rawBlock, context.Quality); + return (TContext)(object)rgContext; + } + throw new NotSupportedException($"The type {typeof(TContext).Name} is not supported."); + } +} + +internal struct RgbEncodingContext : IBlockEncodingContext +{ + /// + public RawBlock4X4RgbaFloat RawBlock { get; init; } + + /// + public RawBlock4X4Vector4 RawBlockVec { get; init; } + + /// + public CompressionQuality Quality { get; init; } + + /// + /// RawBlock converted to perceptual space + /// + public RawBlock4X4Vector4 PerceptualBlock { get; init; } + + /// + /// Color weights. + /// + public RgbWeights Weights { get; init; } + + /// + /// Color conversion mode. Used for final conversion before quantizing endpoints. + /// + public ColorConversionMode ColorConversionMode { get; init; } + + /// + /// For 1-bit alpha compression, the alpha threshold value. + /// + public float AlphaThreshold { get; init; } + + public RgbEncodingContext(RawBlock4X4RgbaFloat rawBlock, CompressionQuality quality, RgbWeights weights, ColorConversionMode colorConversionMode, float alphaThreshold) + { + RawBlock = rawBlock; + RawBlockVec = rawBlock.AsVector4(); + Quality = quality; + Weights = weights; + ColorConversionMode = colorConversionMode; + AlphaThreshold = alphaThreshold; + + // Precompute perceptual block + RawBlock4X4Vector4 perceptualBlock = new RawBlock4X4Vector4(); + Span perceptualPixels = perceptualBlock.AsSpan; + for (var i = 0; i < perceptualPixels.Length; i++) + { + perceptualPixels[i] = weights.TransformToPerceptual(RawBlockVec[i]); + } + + PerceptualBlock = perceptualBlock; + } +} + +internal struct RgEncodingContext : IBlockEncodingContext +{ + /// + public RawBlock4X4RgbaFloat RawBlock { get; } + /// + public RawBlock4X4Vector4 RawBlockVec { get; } + /// + public CompressionQuality Quality { get; } + + public RgEncodingContext(RawBlock4X4RgbaFloat rawBlock, CompressionQuality quality) + { + RawBlock = rawBlock; + RawBlockVec = rawBlock.AsVector4(); + Quality = quality; + } +} diff --git a/BCnEnc.Net/Encoder/LeastSquares.cs b/BCnEnc.Net/Encoder/LeastSquares.cs index 1c9bf68..c887139 100644 --- a/BCnEnc.Net/Encoder/LeastSquares.cs +++ b/BCnEnc.Net/Encoder/LeastSquares.cs @@ -4,6 +4,7 @@ using System.Text; using BCnEncoder.Encoder.Bptc; using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder { @@ -13,6 +14,7 @@ namespace BCnEncoder.Encoder /// internal static class LeastSquares { + private static readonly ushort HalfMaxUint16 = BitConverter.HalfToUInt16Bits(Half.MaxValue); private static int ComputeIndex4(float texelPos, float endPoint0Pos, float endPoint1Pos) { @@ -28,7 +30,7 @@ private static int ComputeIndex3(float texelPos, float endPoint0Pos, float endPo private static uint F32ToF16(float f32) { - return Half.GetBits(new Half(f32)); + return BitConverter.HalfToUInt16Bits((Half)f32); } private static Vector3 F32ToF16(Vector3 f32) @@ -42,7 +44,7 @@ private static Vector3 F32ToF16(Vector3 f32) private static float F16ToF32(uint f16) { - return Half.ToHalf((ushort)f16); + return (float)BitConverter.UInt16BitsToHalf((ushort)f16); } private static Vector3 F16ToF32(Vector3 f16) @@ -54,7 +56,7 @@ private static Vector3 F16ToF32(Vector3 f16) ); } - public static void OptimizeEndpoints1Sub(RawBlock4X4RgbFloat block, ref ColorRgbFloat ep0, ref ColorRgbFloat ep1) + public static void OptimizeEndpoints1Sub(RawBlock4X4RgbaFloat block, ref ColorRgbFloat ep0, ref ColorRgbFloat ep1) { var ep0v = ep0.ToVector3(); var ep1v = ep1.ToVector3(); @@ -98,14 +100,14 @@ public static void OptimizeEndpoints1Sub(RawBlock4X4RgbFloat block, ref ColorRgb var detRcp = 1f / (det); var ep0f16 = detRcp * (alphaTexelSum * betaSqSum - betaTexelSum * alphaBetaSum); var ep1f16 = detRcp * (betaTexelSum * alphaSqSum - alphaTexelSum * alphaBetaSum); - ep0f16 = Vector3.Clamp(ep0f16, Vector3.Zero, new Vector3(Half.MaxValue.Value)); - ep1f16 = Vector3.Clamp(ep1f16, Vector3.Zero, new Vector3(Half.MaxValue.Value)); + ep0f16 = Vector3.Clamp(ep0f16, Vector3.Zero, new Vector3(HalfMaxUint16)); + ep1f16 = Vector3.Clamp(ep1f16, Vector3.Zero, new Vector3(HalfMaxUint16)); ep0 = new ColorRgbFloat(F16ToF32(ep0f16)); ep1 = new ColorRgbFloat(F16ToF32(ep1f16)); } } - public static void OptimizeEndpoints2Sub(RawBlock4X4RgbFloat block, ref ColorRgbFloat ep0, ref ColorRgbFloat ep1, int partitionSetId, int subsetIndex) + public static void OptimizeEndpoints2Sub(RawBlock4X4RgbaFloat block, ref ColorRgbFloat ep0, ref ColorRgbFloat ep1, int partitionSetId, int subsetIndex) { var ep0v = ep0.ToVector3(); var ep1v = ep1.ToVector3(); @@ -152,8 +154,8 @@ public static void OptimizeEndpoints2Sub(RawBlock4X4RgbFloat block, ref ColorRgb var detRcp = 1f / (det); var ep0f16 = detRcp * (alphaTexelSum * betaSqSum - betaTexelSum * alphaBetaSum); var ep1f16 = detRcp * (betaTexelSum * alphaSqSum - alphaTexelSum * alphaBetaSum); - ep0f16 = Vector3.Clamp(ep0f16, Vector3.Zero, new Vector3(Half.MaxValue.Value)); - ep1f16 = Vector3.Clamp(ep1f16, Vector3.Zero, new Vector3(Half.MaxValue.Value)); + ep0f16 = Vector3.Clamp(ep0f16, Vector3.Zero, new Vector3(HalfMaxUint16)); + ep1f16 = Vector3.Clamp(ep1f16, Vector3.Zero, new Vector3(HalfMaxUint16)); ep0 = new ColorRgbFloat(F16ToF32(ep0f16)); ep1 = new ColorRgbFloat(F16ToF32(ep1f16)); } diff --git a/BCnEnc.Net/Encoder/OptimizeEndpoints.cs b/BCnEnc.Net/Encoder/OptimizeEndpoints.cs new file mode 100644 index 0000000..98a0cf4 --- /dev/null +++ b/BCnEnc.Net/Encoder/OptimizeEndpoints.cs @@ -0,0 +1,150 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; +using CommunityToolkit.HighPerformance; + +namespace BCnEncoder.Encoder; + +internal static class EndpointOptimizer +{ + /// + /// Quantize the given color vector to the 565 color range. + /// + /// The color vector to quantize. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void Quantize(ref Vector4 color) + { + color.X = MathF.Round(color.X * 31) / 31f; + color.Y = MathF.Round(color.Y * 63) / 63f; + color.Z = MathF.Round(color.Z * 31) / 31f; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void Saturate(ref Vector4 color) + { + color = Vector4.Clamp(color, Vector4.Zero, Vector4.One); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static int GetColorIndex(ReadOnlySpan colors, Vector4 color) + { + int bestIndex = 0; + float bestDistance = Vector4.DistanceSquared(colors[0], color); + for (int i = 1; i < colors.Length; i++) + { + float distance = Vector4.DistanceSquared(colors[i], color); + if (distance < bestDistance) + { + bestDistance = distance; + bestIndex = i; + } + } + + return bestIndex; + } + + + public static void Optimize565Endpoints(Span colors, Vector4 mean, Vector4 principalAxis, + ref ColorB5G6R5Packed ep0, ref ColorB5G6R5Packed ep1, RgbWeights weights, bool useBc1Blacks) + { + ReadOnlySpan paletteWeights = useBc1Blacks + ? stackalloc float[3] + { + 0f, + 1f, + .5f + } + : stackalloc float[4] + { + 0f, + 1f, + 1f / 3f, + 2f / 3f + }; + + Vector4 ep0Vec = ep0.ToColorRgbaFloat().ToVector4(); + Vector4 ep1Vec = ep1.ToColorRgbaFloat().ToVector4(); + + Span palette = stackalloc Vector4[4]; + + for (int it = 0; it < 3; it++) + { + palette[0] = ep0Vec; + palette[1] = ep1Vec; + + if (useBc1Blacks) // Bc1 palette[3] is black when c0 <= c1 + { + palette[2] = ep0Vec + (ep1Vec - ep0Vec) * .5f; + palette[3] = Vector4.Zero; + } + else + { + palette[2] = ep0Vec + (ep1Vec - ep0Vec) * (1f / 3f); + palette[3] = ep0Vec + (ep1Vec - ep0Vec) * (2f / 3f); + } + + float sum_w0w0 = 0f, // Sum of squares of weights from endpoint 0 + sum_w0w1 = 0f, // Sum of cross terms (w0 * w1) + sum_w1w1 = 0f; // Sum of squares of weights from endpoint 1 + float sum_w0_proj = 0f, // Sum of (w0 * projected color) + sum_w1_proj = 0f; // Sum of (w1 * projected color) + + for (int i = 0; i < 16; i++) + { + // Convert color to vector + Vector4 colorVec = colors[i].ToVector4(); + // colorVec *= weightsVec; + + // int colorIndex = GetColorIndex(palette, colorVec); + int colorIndex = ColorChooser.ChooseClosestRgbColor4(palette, colorVec, weights, out _); + + // Ignore black when optimizing + if (useBc1Blacks && colorIndex == 3) continue; + + // Take vector from mean to color + Vector4 delta = colorVec - mean; + // Project onto principal axis + float projected = Vector4.Dot(delta, principalAxis); + + // Assign weights + float w0 = paletteWeights[colorIndex]; + float w1 = 1f - w0; + + // AT: Matrix2x2 = transpose(A) + // ATA: Matrix2x2 = matmul(AT, A) + // ATb: Vector = matmul(AT, b) + sum_w0w0 += w0 * w0; + sum_w0w1 += w0 * w1; + sum_w1w1 += w1 * w1; + + sum_w0_proj += w0 * projected; + sum_w1_proj += w1 * projected; + } + + // Solve 2x2 linear system + // a, b_scalar = solve_2x2_linear_system(ATA, ATb) + + float determinant = sum_w0w0 * sum_w1w1 - sum_w0w1 * sum_w0w1; + float endpoint0_scalar = 0f, endpoint1_scalar = 0f; + + if (Math.Abs(determinant) < 1e-6f) + { + determinant = 1e-6f; + } + + float invDet = 1f / determinant; + endpoint0_scalar = (sum_w0_proj * sum_w1w1 - sum_w1_proj * sum_w0w1) * invDet; + endpoint1_scalar = (-sum_w0_proj * sum_w0w1 + sum_w1_proj * sum_w0w0) * invDet; + + // Un-project a and b back into RGB space + ep0Vec = mean + principalAxis * endpoint0_scalar; + ep1Vec = mean + principalAxis * endpoint1_scalar; + } + + // Convert to 565 + ep0 = new ColorB5G6R5Packed(ep0Vec.X, ep0Vec.Y, ep0Vec.Z); + ep1 = new ColorB5G6R5Packed(ep1Vec.X, ep1Vec.Y, ep1Vec.Z); + } +} diff --git a/BCnEnc.Net/Encoder/Options/EncoderInputOptions.cs b/BCnEnc.Net/Encoder/Options/EncoderInputOptions.cs index 57a2d72..3bd8715 100644 --- a/BCnEnc.Net/Encoder/Options/EncoderInputOptions.cs +++ b/BCnEnc.Net/Encoder/Options/EncoderInputOptions.cs @@ -7,12 +7,6 @@ namespace BCnEncoder.Encoder.Options /// public class EncoderInputOptions { - /// - /// If true, when encoding to R8 raw format, - /// use the pixel luminance instead of just the red channel. Default is false. (Does not apply to BC4 format) - /// - public bool LuminanceAsRed { get; set; } = false; - /// /// The color channel to take for the values of a BC4 block. Default is red. /// diff --git a/BCnEnc.Net/Encoder/Options/EncoderOptions.cs b/BCnEnc.Net/Encoder/Options/EncoderOptions.cs index 5d74d2e..45c0f80 100644 --- a/BCnEnc.Net/Encoder/Options/EncoderOptions.cs +++ b/BCnEnc.Net/Encoder/Options/EncoderOptions.cs @@ -18,6 +18,7 @@ public class EncoderOptions /// /// Determines how many tasks should be used for parallel processing. + /// Default is the number of available processors. /// public int TaskCount { get; set; } = Environment.ProcessorCount; diff --git a/BCnEnc.Net/Encoder/Options/EncoderOutputOptions.cs b/BCnEnc.Net/Encoder/Options/EncoderOutputOptions.cs index 90161c6..fa1c95f 100644 --- a/BCnEnc.Net/Encoder/Options/EncoderOutputOptions.cs +++ b/BCnEnc.Net/Encoder/Options/EncoderOutputOptions.cs @@ -26,29 +26,87 @@ public class EncoderOutputOptions /// /// The Quality of the compression. Use either fast or balanced for testing. /// Fast can be used for near real-time encoding for most algorithms. - /// Use bestQuality when needed. Default is balanced. + /// Use bestQuality if time is not an issue. Default is balanced. /// public CompressionQuality Quality { get; set; } = CompressionQuality.Balanced; /// - /// The output file Format of the data. Either Ktx or Dds. - /// Default is Ktx. + /// The colorspace to use for encoding and mipmap generation. /// - public OutputFileFormat FileFormat { get; set; } = OutputFileFormat.Ktx; + public EncoderColorSpaceHandling ColorSpaceHandling { get; set; } = EncoderColorSpaceHandling.ProcessLinearPreserveColorSpace; /// - /// The DDS file Format doesn't seem to have a standard for indicating whether a BC1 texture - /// includes 1bit of alpha. This option will write DDPF_ALPHAPIXELS flag to the header - /// to indicate the presence of an alpha channel. Some programs read and write this flag, - /// but some programs don't like it and get confused. Your mileage may vary. - /// Default is false. + /// How to handle alpha channel during processing. /// - public bool DdsBc1WriteAlphaFlag { get; set; } = false; + public EncoderAlphaHandling AlphaHandling { get; set; } = EncoderAlphaHandling.Auto; - /// - /// When writing a dds file, always prefer using the Dxt10 header - /// to write the compression format instead of DwFourCC. - /// - public bool DdsPreferDxt10Header { get; set; } = false; + /// + /// + /// Whether to rescale unsigned normalized values [0, 1] to signed normalized values [-1, 1], when applicable. + /// + /// + /// Default is true. + /// + /// + public bool RescaleUnormToSnorm { get; set; } = true; + + /// + /// + /// Whether to use perceptual metrics for encoding. + /// This makes the encoder calculate the error based on human perception of luma, instead of absolute color difference. + /// This option has no effect on R and RG formats. + /// + /// + /// Default is true. + /// + /// + public bool UsePerceptualMetrics { get; set; } = true; + } + + /// + /// Defines how colors should be interpreted and processed during encoding. + /// + public enum EncoderColorSpaceHandling + { + /// + /// Colors are converted to linear colorspace for processing. Output will match the input colorspace regardless of chosen output format. + /// + ProcessLinearPreserveColorSpace, + + /// + /// Do not convert colors to linear colorspace for processing. Output will match the input colorspace. + /// + KeepAsIs, + + /// + /// Detect appropriate colorspace based on input/output formats (e.g., use sRGB for sRGB formats, linear for HDR). + /// The RGB colorspace will be automatically determined based on the selected input and output formats. + /// Conversion will be automatically applied if necessary. + /// + Auto + } + + /// + /// Defines how the alpha channel should be handled during processing. + /// + public enum EncoderAlphaHandling + { + /// + /// Automatically detect if the source data is already using premultiplied alpha by checking + /// if any color channel value is higher than its corresponding alpha value. + /// If not premultiplied, convert to premultiplied for processing. + /// + Auto, + + /// + /// Use the source alpha representation as-is without any conversion. + /// + AsIs, + + /// + /// Expect input data in linear format with straight alpha and convert to premultiplied + /// for processing and output. + /// + LinearToPremultiplied } } diff --git a/BCnEnc.Net/Encoder/RawEncoders.cs b/BCnEnc.Net/Encoder/RawEncoders.cs index 139014b..99d5790 100644 --- a/BCnEnc.Net/Encoder/RawEncoders.cs +++ b/BCnEnc.Net/Encoder/RawEncoders.cs @@ -1,266 +1,28 @@ using System; using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Encoder { - internal interface IRawEncoder + internal interface IBcEncoder { - byte[] Encode(ReadOnlyMemory pixels); - GlInternalFormat GetInternalFormat(); - GlFormat GetBaseInternalFormat(); - GlFormat GetGlFormat(); - GlType GetGlType(); - uint GetGlTypeSize(); - DxgiFormat GetDxgiFormat(); + byte[] Encode(ReadOnlyMemory pixels, int width, int height, OperationContext context); } - internal class RawLuminanceEncoder : IRawEncoder + internal class RawPixelEncoder : IBcEncoder + where TPixelFormat : unmanaged, IColor { - private readonly bool useLuminance; + public RawPixelEncoder() { } - public RawLuminanceEncoder(bool useLuminance) + /// + public byte[] Encode(ReadOnlyMemory pixels, int width, int height, OperationContext context) { - this.useLuminance = useLuminance; - } - - public byte[] Encode(ReadOnlyMemory pixels) - { - var span = pixels.Span; + var output = pixels.ConvertToAsBytes(context.ColorConversionMode); - var output = new byte[pixels.Length]; - for (var i = 0; i < pixels.Length; i++) - { - if (useLuminance) - { - output[i] = (byte)(new ColorYCbCr(span[i]).y * 255); - } - else - { - output[i] = span[i].r; - } + context.CancellationToken.ThrowIfCancellationRequested(); + context.Progress?.Report(width * height); - } return output; } - - public GlInternalFormat GetInternalFormat() - { - return GlInternalFormat.GlR8; - } - - public GlFormat GetBaseInternalFormat() - { - return GlFormat.GlRed; - } - - public GlFormat GetGlFormat() - { - return GlFormat.GlRed; - } - - public GlType GetGlType() - { - return GlType.GlByte; - } - - public uint GetGlTypeSize() - { - return 1; - } - - public DxgiFormat GetDxgiFormat() - { - return DxgiFormat.DxgiFormatR8Unorm; - } - } - - internal class RawRgEncoder : IRawEncoder - { - public byte[] Encode(ReadOnlyMemory pixels) - { - var span = pixels.Span; - - var output = new byte[pixels.Length * 2]; - for (var i = 0; i < pixels.Length; i++) - { - output[i * 2] = span[i].r; - output[i * 2 + 1] = span[i].g; - } - return output; - } - - public GlInternalFormat GetInternalFormat() - { - return GlInternalFormat.GlRg8; - } - - public GlFormat GetBaseInternalFormat() - { - return GlFormat.GlRg; - } - - public GlFormat GetGlFormat() - { - return GlFormat.GlRg; - } - - public GlType GetGlType() - { - return GlType.GlByte; - } - - public uint GetGlTypeSize() - { - return 1; - } - - public DxgiFormat GetDxgiFormat() - { - return DxgiFormat.DxgiFormatR8G8Unorm; - } - } - - internal class RawRgbEncoder : IRawEncoder - { - public byte[] Encode(ReadOnlyMemory pixels) - { - var span = pixels.Span; - - var output = new byte[pixels.Length * 3]; - for (var i = 0; i < pixels.Length; i++) - { - output[i * 3] = span[i].r; - output[i * 3 + 1] = span[i].g; - output[i * 3 + 2] = span[i].b; - } - return output; - } - - public GlInternalFormat GetInternalFormat() - { - return GlInternalFormat.GlRgb8; - } - - public GlFormat GetBaseInternalFormat() - { - return GlFormat.GlRgb; - } - - public GlFormat GetGlFormat() - { - return GlFormat.GlRgb; - } - - public GlType GetGlType() - { - return GlType.GlByte; - } - - public uint GetGlTypeSize() - { - return 1; - } - - public DxgiFormat GetDxgiFormat() - { - throw new NotSupportedException("RGB Format is not supported for dds files."); - } - } - - internal class RawRgbaEncoder : IRawEncoder - { - public byte[] Encode(ReadOnlyMemory pixels) - { - var span = pixels.Span; - - var output = new byte[pixels.Length * 4]; - for (var i = 0; i < pixels.Length; i++) - { - output[i * 4] = span[i].r; - output[i * 4 + 1] = span[i].g; - output[i * 4 + 2] = span[i].b; - output[i * 4 + 3] = span[i].a; - } - return output; - } - - public GlInternalFormat GetInternalFormat() - { - return GlInternalFormat.GlRgba8; - } - - public GlFormat GetBaseInternalFormat() - { - return GlFormat.GlRgba; - } - - public GlFormat GetGlFormat() - { - return GlFormat.GlRgba; - } - - public GlType GetGlType() - { - return GlType.GlByte; - } - - public uint GetGlTypeSize() - { - return 1; - } - - public DxgiFormat GetDxgiFormat() - { - return DxgiFormat.DxgiFormatR8G8B8A8Unorm; - } - } - - internal class RawBgraEncoder : IRawEncoder - { - public byte[] Encode(ReadOnlyMemory pixels) - { - var span = pixels.Span; - - var output = new byte[pixels.Length * 4]; - for (var i = 0; i < pixels.Length; i++) - { - output[i * 4] = span[i].b; - output[i * 4 + 1] = span[i].g; - output[i * 4 + 2] = span[i].r; - output[i * 4 + 3] = span[i].a; - } - return output; - } - - public GlInternalFormat GetInternalFormat() - { - return GlInternalFormat.GlBgra8Extension; - } - - public GlFormat GetBaseInternalFormat() - { - return GlFormat.GlBgra; - } - - public GlFormat GetGlFormat() - { - return GlFormat.GlBgra; - } - - public GlType GetGlType() - { - return GlType.GlByte; - } - - public uint GetGlTypeSize() - { - return 1; - } - - public DxgiFormat GetDxgiFormat() - { - return DxgiFormat.DxgiFormatB8G8R8A8Unorm; - } } } diff --git a/BCnEnc.Net/Encoder/RgbWeights.cs b/BCnEnc.Net/Encoder/RgbWeights.cs new file mode 100644 index 0000000..e4ea132 --- /dev/null +++ b/BCnEnc.Net/Encoder/RgbWeights.cs @@ -0,0 +1,59 @@ +using System; +using System.Numerics; +using BCnEncoder.Shared.Colors; + +namespace BCnEncoder.Encoder; + +internal class RgbWeights +{ + public Vector4 PcaWeights = new Vector4(2f, 1f, 1f, 1f); + + public bool UsePerceptual { get; init; } + + + /// + /// Transform from perceptual space to PCA space + /// + public Vector4 TransformPca(Vector4 color) => UsePerceptual ? color * PcaWeights : color; + + /// + /// Transform from PCA space to perceptual space + /// + public Vector4 InverseTransformPca(Vector4 color) => UsePerceptual ? color / PcaWeights : color; + + public Vector4 TransformToPerceptual(Vector4 color) + { + if (!UsePerceptual) return color; + + // Convert to lRGB + // if (InputSrgb) color = ColorSpace.Srgb.ToLrgb(color); + + return ColorSpace.Oklab.LrgbToOklab(color); + } + + public Vector4 TransformFromPerceptual(Vector4 color) + { + if (!UsePerceptual) return color; + var lrgb = ColorSpace.Oklab.OklabToLrgb(color); + + // Convert back to sRGB + // if (InputSrgb) lrgb = ColorSpace.Srgb.ToSrgb(lrgb); + + return lrgb; + } + + /// + /// Calculate color difference in perceptual space + /// + public float CalculateColorDiff(Vector4 color1, Vector4 color2) + { + if (!UsePerceptual) return Vector4.Distance(color1, color2); + + return ColorSpace.Oklab.DeltaE(color1, color2); + } + + public RgbWeights(bool usePerceptual) + { + UsePerceptual = usePerceptual; + } +} diff --git a/BCnEnc.Net/Shared/AlphaHandlingHelper.cs b/BCnEnc.Net/Shared/AlphaHandlingHelper.cs new file mode 100644 index 0000000..f193948 --- /dev/null +++ b/BCnEnc.Net/Shared/AlphaHandlingHelper.cs @@ -0,0 +1,209 @@ +using System; +using BCnEncoder.Encoder.Options; +using BCnEncoder.Shared.Colors; +using CommunityToolkit.HighPerformance; + +namespace BCnEncoder.Shared +{ + /// + /// Helper class for handling alpha premultiplication detection and conversion. + /// + internal static class AlphaHandlingHelper + { + private const float AlphaEpsilon = 0.0001f; + + /// + /// Detects if an array of ColorRgbaFloat values is already using premultiplied alpha. + /// + /// The array of colors to check. + /// or + public static AlphaChannelHint GuessAlphaChannel(ReadOnlySpan colors) + { + // Start by assuming it's premultiplied until we find evidence otherwise + for (int i = 0; i < colors.Length; i++) + { + var color = colors[i]; + // If any color channel exceeds alpha value (plus a small epsilon for float precision), + // then it's using straight alpha, not premultiplied + if (color.a > AlphaEpsilon && (color.r > color.a + AlphaEpsilon || + color.g > color.a + AlphaEpsilon || + color.b > color.a + AlphaEpsilon)) + { + return AlphaChannelHint.Straight; + } + } + + // If we didn't find any evidence to the contrary, consider it premultiplied + return AlphaChannelHint.Premultiplied; + } + + public static AlphaChannelHint GuessAlphaChannel(BCnTextureData texture) + { + if (texture.Format != CompressionFormat.RgbaFloat) + throw new ArgumentException("Format must be RgbaFloat before calling this method."); + + bool isPremultiplied = true; + + for (var m = 0; m < texture.NumMips && isPremultiplied; m++) + { + for (var f = 0; f < texture.NumFaces && isPremultiplied; f++) + { + for (var a = 0; a < texture.NumArrayElements && isPremultiplied; a++) + { + var face = (CubeMapFaceDirection)f; + var channelHint = GuessAlphaChannel(texture.Mips[m][face, a].AsMemory().Span); + + if (channelHint == AlphaChannelHint.Straight) + isPremultiplied = false; + } + } + } + + return isPremultiplied ? AlphaChannelHint.Premultiplied : AlphaChannelHint.Straight; + } + + /// + /// Premultiplies the alpha of an array of ColorRgbaFloat values. + /// + /// The colors to premultiply. + public static void PremultiplyAlpha(Span colors) + { + for (int i = 0; i < colors.Length; i++) + { + var color = colors[i]; + colors[i] = new ColorRgbaFloat( + color.r * color.a, + color.g * color.a, + color.b * color.a, + color.a); + } + } + + /// + /// Unpremultiplies the alpha of an array of ColorRgbaFloat values. + /// + /// The colors to unpremultiply. + public static void UnpremultiplyAlpha(Span colors) + { + static float Unpremultiply(float c, float a) => a > 0 ? Math.Min(1.0f, c / a): 0; + + for (int i = 0; i < colors.Length; i++) + { + var color = colors[i]; + if (color.a > AlphaEpsilon) + { + colors[i] = new ColorRgbaFloat( + Unpremultiply(color.r, color.a), + Unpremultiply(color.g, color.a), + Unpremultiply(color.b, color.a), + color.a); + } + else + { + colors[i] = new ColorRgbaFloat(0, 0, 0, 0); + } + } + } + + /// + /// Handles alpha processing for a TextureData based on the specified AlphaHandling mode. + /// + /// The texture data to process. + /// The alpha handling mode to apply. + /// True if the data was modified, false otherwise. + public static bool ProcessAlpha(BCnTextureData textureData, EncoderAlphaHandling alphaHandling) + { + if (textureData.Mips.Length == 0) + { + return false; + } + + if (textureData.AlphaChannelHint == AlphaChannelHint.Unknown) + { + textureData.AlphaChannelHint = GuessAlphaChannel(textureData); + } + + bool modified = false; + + if (alphaHandling == EncoderAlphaHandling.Auto) + { + // If not premultiplied, convert to premultiplied + if (textureData.AlphaChannelHint == AlphaChannelHint.Straight) + { + for (var m = 0; m < textureData.NumMips; m++) + { + for (var f = 0; f < textureData.NumFaces; f++) + { + for (var a = 0; a < textureData.NumArrayElements; a++) + { + var face = (CubeMapFaceDirection)f; + var pixels = textureData.Mips[m][face, a].AsMemory(); + PremultiplyAlpha(pixels.Span); + } + } + } + modified = true; + } + } + else if (alphaHandling == EncoderAlphaHandling.LinearToPremultiplied) + { + // Always convert to premultiplied + for (var f = 0; f < textureData.NumFaces; f++) + { + for (var a = 0; a < textureData.NumArrayElements; a++) + { + var face = (CubeMapFaceDirection)f; + var pixels = textureData.Mips[0][face, a].AsMemory(); + PremultiplyAlpha(pixels.Span); + } + } + + textureData.AlphaChannelHint = AlphaChannelHint.Premultiplied; + modified = true; + } + + return modified; + } + + /// + /// Handles alpha processing for a single memory buffer based on the specified AlphaHandling mode. + /// + /// The color data to process. + /// The alpha handling mode to apply. + /// Optional hint about how the alpha is currently encoded. If Unknown, will attempt to detect. + /// A tuple containing the processed data (which may be the same as the input if no processing was needed) and the resulting alpha channel hint. + public static AlphaChannelHint ProcessAlpha( + Memory floatData, EncoderAlphaHandling alphaHandling, AlphaChannelHint alphaChannelHint = AlphaChannelHint.Unknown) + { + if (floatData.Length == 0) + { + return alphaChannelHint; + } + + // Determine alpha channel type if unknown + if (alphaChannelHint == AlphaChannelHint.Unknown) + { + alphaChannelHint = GuessAlphaChannel(floatData.Span); + } + + if (alphaHandling == EncoderAlphaHandling.Auto) + { + // Convert to premultiplied if it's straight alpha + if (alphaChannelHint == AlphaChannelHint.Straight) + { + PremultiplyAlpha(floatData.Span); + return AlphaChannelHint.Premultiplied; + } + } + else if (alphaHandling == EncoderAlphaHandling.LinearToPremultiplied) + { + // Always convert to premultiplied + PremultiplyAlpha(floatData.Span); + return AlphaChannelHint.Premultiplied; + } + + // For AsIs or already premultiplied data, return the original + return alphaChannelHint; + } + } +} diff --git a/BCnEnc.Net/Shared/BinaryReaderWriterExtensions.cs b/BCnEnc.Net/Shared/BinaryReaderWriterExtensions.cs index eccf7e6..9edbb49 100644 --- a/BCnEnc.Net/Shared/BinaryReaderWriterExtensions.cs +++ b/BCnEnc.Net/Shared/BinaryReaderWriterExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Runtime.CompilerServices; @@ -42,4 +42,4 @@ public static void SkipPadding(this BinaryReader br, uint padding) public static void SkipPadding(this BinaryReader br, int padding) => SkipPadding(br, (uint) padding); } -} \ No newline at end of file +} diff --git a/BCnEnc.Net/Shared/Bc6Block.cs b/BCnEnc.Net/Shared/BlockCompression/Bc6Block.cs similarity index 98% rename from BCnEnc.Net/Shared/Bc6Block.cs rename to BCnEnc.Net/Shared/BlockCompression/Bc6Block.cs index ab99421..9a89801 100644 --- a/BCnEnc.Net/Shared/Bc6Block.cs +++ b/BCnEnc.Net/Shared/BlockCompression/Bc6Block.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using BCnEncoder.Encoder.Bptc; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Shared { @@ -132,7 +133,7 @@ internal struct Bc6Block 15, 15, 15, 15, 15, 2, 2, 15 }; - public static readonly RawBlock4X4RgbFloat ErrorBlock = new RawBlock4X4RgbFloat(new ColorRgbFloat(1, 0, 1)); + public static readonly RawBlock4X4RgbaFloat ErrorBlock = new RawBlock4X4RgbaFloat(new ColorRgbaFloat(1, 0, 1)); public static readonly Bc6BlockType[] Subsets1Types = { @@ -209,36 +210,36 @@ internal void StoreEp0((int, int, int) endpoint) var r0 = (ulong)endpoint.Item1; var g0 = (ulong)endpoint.Item2; var b0 = (ulong)endpoint.Item3; - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 5, Math.Min(10, EndpointBits), r0); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 15, Math.Min(10, EndpointBits), g0); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 25, Math.Min(10, EndpointBits), b0); - + switch (Type) { case Bc6BlockType.Type2: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 40, 1, r0 >> 10); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 49, 1, g0 >> 10); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 59, 1, b0 >> 10); - + break; case Bc6BlockType.Type6: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 39, 1, r0 >> 10); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 50, 1, g0 >> 10); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 59, 1, b0 >> 10); - + break; case Bc6BlockType.Type10: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 39, 1, r0 >> 10); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 49, 1, g0 >> 10); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 60, 1, b0 >> 10); - + break; case Bc6BlockType.Type7: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 44, 1, r0 >> 10); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 54, 1, g0 >> 10); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 64, 1, b0 >> 10); - + break; case Bc6BlockType.Type11: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 44, 1, r0 >> 10); @@ -248,7 +249,7 @@ internal void StoreEp0((int, int, int) endpoint) (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 43, 1, r0 >> 11); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 53, 1, g0 >> 11); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 63, 1, b0 >> 11); - + break; case Bc6BlockType.Type15: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 44, 1, r0 >> 10); @@ -266,15 +267,15 @@ internal void StoreEp0((int, int, int) endpoint) (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 41, 1, r0 >> 13); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 51, 1, g0 >> 13); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 61, 1, b0 >> 13); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 40, 1, r0 >> 14); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 50, 1, g0 >> 14); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 60, 1, b0 >> 14); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 39, 1, r0 >> 15); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 49, 1, g0 >> 15); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 59, 1, b0 >> 15); - + break; } } @@ -376,7 +377,7 @@ internal void StoreEp1((int, int, int) endpoint) (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 35, Math.Min(5, DeltaBits.Item1), r1); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 45, Math.Min(5, DeltaBits.Item2), g1); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 55, Math.Min(5, DeltaBits.Item3), b1); - + } switch (Type) @@ -385,12 +386,12 @@ internal void StoreEp1((int, int, int) endpoint) (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 40, 1, r1 >> 5); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 50, 1, g1 >> 5); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 60, 1, b1 >> 5); - + break; case Bc6BlockType.Type18: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 40, 1, r1 >> 5); - + break; case Bc6BlockType.Type22: @@ -505,17 +506,17 @@ internal void StoreEp2((int, int, int) endpoint) case Bc6BlockType.Type0: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 2, 1, g2 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 3, 1, b2 >> 4); - + break; case Bc6BlockType.Type1: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 70, 1, r2 >> 5); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 24, 1, g2 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 2, 1, g2 >> 5); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 14, 1, b2 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 22, 1, b2 >> 5); - + break; case Bc6BlockType.Type6: @@ -530,14 +531,14 @@ internal void StoreEp2((int, int, int) endpoint) case Bc6BlockType.Type14: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 24, 1, g2 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 14, 1, b2 >> 4); - + break; case Bc6BlockType.Type18: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 70, 1, r2 >> 4); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 24, 1, g2 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 14, 1, b2 >> 4); - + break; case Bc6BlockType.Type22: @@ -545,17 +546,17 @@ internal void StoreEp2((int, int, int) endpoint) (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 23, 1, g2 >> 5); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 14, 1, b2 >> 4); - + break; case Bc6BlockType.Type26: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 24, 1, g2 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 14, 1, b2 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 23, 1, b2 >> 5); - + break; case Bc6BlockType.Type30: - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 65, 6, r2); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 24, 1, g2 >> 4); @@ -563,7 +564,7 @@ internal void StoreEp2((int, int, int) endpoint) (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 14, 1, b2 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 22, 1, b2 >> 5); - + break; } } @@ -581,7 +582,7 @@ internal readonly (int, int, int) ExtractEp2() switch (Type) { case Bc6BlockType.Type0: - + g2 |= ByteHelper.ExtractFrom128(lowBits, highBits, 2, 1) << 4; b2 |= ByteHelper.ExtractFrom128(lowBits, highBits, 3, 1) << 4; break; @@ -658,7 +659,7 @@ internal void StoreEp3((int, int, int) endpoint) switch (Type) { case Bc6BlockType.Type0: - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 40, 1, g3 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 50, 1, b3 >> 0); @@ -666,7 +667,7 @@ internal void StoreEp3((int, int, int) endpoint) (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 70, 1, b3 >> 2); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 76, 1, b3 >> 3); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 4, 1, b3 >> 4); - + break; case Bc6BlockType.Type1: @@ -682,7 +683,7 @@ internal void StoreEp3((int, int, int) endpoint) break; case Bc6BlockType.Type2: - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 50, 1, b3 >> 0); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 60, 1, b3 >> 1); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 70, 1, b3 >> 2); @@ -706,69 +707,69 @@ internal void StoreEp3((int, int, int) endpoint) (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 70, 1, b3 >> 2); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 76, 1, b3 >> 3); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 75, 1, b3 >> 4); - + break; case Bc6BlockType.Type14: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 40, 1, g3 >> 4); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 50, 1, b3 >> 0); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 60, 1, b3 >> 1); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 70, 1, b3 >> 2); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 76, 1, b3 >> 3); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 34, 1, b3 >> 4); - + break; case Bc6BlockType.Type18: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 76, 1, r3 >> 4); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 13, 1, g3 >> 4); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 50, 1, b3 >> 0); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 60, 1, b3 >> 1); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 23, 1, b3 >> 2); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 33, 1, b3 >> 3); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 34, 1, b3 >> 4); - + break; case Bc6BlockType.Type22: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 40, 1, g3 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 33, 1, g3 >> 5); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 13, 1, b3 >> 0); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 60, 1, b3 >> 1); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 70, 1, b3 >> 2); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 76, 1, b3 >> 3); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 34, 1, b3 >> 4); - + break; case Bc6BlockType.Type26: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 40, 1, g3 >> 4); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 50, 1, b3 >> 0); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 13, 1, b3 >> 1); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 70, 1, b3 >> 2); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 76, 1, b3 >> 3); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 34, 1, b3 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 33, 1, b3 >> 5); - + break; case Bc6BlockType.Type30: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 71, 6, r3); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 11, 1, g3 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 31, 1, g3 >> 5); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 12, 2, b3 >> 0); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 23, 1, b3 >> 2); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 32, 1, b3 >> 3); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 34, 1, b3 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 33, 1, b3 >> 5); - + break; } } @@ -1036,7 +1037,7 @@ internal static Half FinishUnQuantize(int component, bool signedBc6) if (!signedBc6) { component = (component * 31) >> 6; // scale the magnitude by 31/64 - return Half.ToHalf((ushort)component); + return BitConverter.UInt16BitsToHalf((ushort)component); } else // signed format { @@ -1047,7 +1048,7 @@ internal static Half FinishUnQuantize(int component, bool signedBc6) s = 0x8000; component = -component; } - return Half.ToHalf((ushort)(s | component)); + return BitConverter.UInt16BitsToHalf((ushort)(s | component)); } } @@ -1137,9 +1138,9 @@ internal static (int, int, int) InterpolateColor((int, int, int) endPointStart, return result; } - public readonly RawBlock4X4RgbFloat Decode(bool signed) + public readonly RawBlock4X4RgbaFloat Decode(bool signed) { - var output = new RawBlock4X4RgbFloat(); + var output = new RawBlock4X4RgbaFloat(); var pixels = output.AsSpan; if (Type == Bc6BlockType.Unknown) @@ -1156,7 +1157,7 @@ public readonly RawBlock4X4RgbFloat Decode(bool signed) numSubsets = 2; partitionIndex = PartitionSetId; } - + for (var i = 0; i < NumEndpoints; i++) { endpoints[i] = UnQuantize(endpoints[i], EndpointBits, signed); @@ -1173,7 +1174,8 @@ public readonly RawBlock4X4RgbFloat Decode(bool signed) var (r, g, b) = FinishUnQuantize(InterpolateColor(endPointStart, endPointEnd, colorIndex, ColorIndexBitCount), signed); - pixels[i] = new ColorRgbFloat(r, g, b); + + pixels[i] = new ColorRgbaFloat((float)r, (float)g, (float)b, 1); } return output; @@ -1203,7 +1205,7 @@ private void StorePartitionSetId(int partitionSetId) { highBits = ByteHelper.Store5(highBits, 13, (byte) partitionSetId); } - + public static Bc6Block PackType0((int, int, int) endpoint0, (int, int, int) endpoint1, (int, int, int) endpoint2, (int, int, int) endpoint3, int partitionSetId, Span indices) { diff --git a/BCnEnc.Net/Shared/Bc7Block.cs b/BCnEnc.Net/Shared/BlockCompression/Bc7Block.cs similarity index 97% rename from BCnEnc.Net/Shared/Bc7Block.cs rename to BCnEnc.Net/Shared/BlockCompression/Bc7Block.cs index def6762..d786227 100644 --- a/BCnEnc.Net/Shared/Bc7Block.cs +++ b/BCnEnc.Net/Shared/BlockCompression/Bc7Block.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using BCnEncoder.Encoder.Bptc; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Shared { @@ -192,7 +193,7 @@ internal struct Bc7Block 15, 15, 15, 15, 3, 15, 15, 8 }; - public static readonly RawBlock4X4Rgba32 ErrorBlock = new RawBlock4X4Rgba32(new ColorRgba32(255, 0, 255)); + public static readonly RawBlock4X4RgbaFloat ErrorBlock = new RawBlock4X4RgbaFloat(new ColorRgbaFloat(1, 0, 1)); public Bc7BlockType Type { @@ -763,9 +764,9 @@ private static ColorRgba32 SwapChannels(ColorRgba32 source, int rotation) { } - public RawBlock4X4Rgba32 Decode() + public RawBlock4X4RgbaFloat Decode() { - var output = new RawBlock4X4Rgba32(); + var output = new RawBlock4X4RgbaFloat(); var type = Type; if (type == Bc7BlockType.Type8Reserved) @@ -814,7 +815,7 @@ public RawBlock4X4Rgba32 Decode() outputColor = SwapChannels(outputColor, rotation); } - pixels[i] = outputColor; + outputColor.To(ref pixels[i]); } return output; @@ -853,12 +854,12 @@ public void PackType0(int partitionIndex4Bit, byte[][] subsetEndpoints, byte[] p var colorBitCount = ColorIndexBitCount; var indexBegin = GetIndexBegin(Bc7BlockType.Type0, colorBitCount, false); for (var i = 0; i < 16; i++) { - var indexOffset = GetIndexOffset(Bc7BlockType.Type0, NumSubsets, + var indexOffset = GetIndexOffset(Bc7BlockType.Type0, NumSubsets, partitionIndex4Bit, colorBitCount, i); - var indexBitCount = GetIndexBitCount(NumSubsets, + var indexBitCount = GetIndexBitCount(NumSubsets, partitionIndex4Bit, colorBitCount, i); - - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, indexBegin + indexOffset, indexBitCount, indices[i]); } } @@ -895,12 +896,12 @@ public void PackType1(int partitionIndex6Bit, byte[][] subsetEndpoints, byte[] p var colorBitCount = ColorIndexBitCount; var indexBegin = GetIndexBegin(Bc7BlockType.Type1, colorBitCount, false); for (var i = 0; i < 16; i++) { - var indexOffset = GetIndexOffset(Bc7BlockType.Type1, NumSubsets, + var indexOffset = GetIndexOffset(Bc7BlockType.Type1, NumSubsets, partitionIndex6Bit, colorBitCount, i); - var indexBitCount = GetIndexBitCount(NumSubsets, + var indexBitCount = GetIndexBitCount(NumSubsets, partitionIndex6Bit, colorBitCount, i); - - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, indexBegin + indexOffset, indexBitCount, indices[i]); } } @@ -922,7 +923,7 @@ public void PackType2(int partitionIndex6Bit, byte[][] subsetEndpoints, byte[] //Store endpoints for (var i = 0; i < subsetEndpoints[0].Length; i++) { for (var j = 0; j < subsetEndpoints.Length; j++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, 5, subsetEndpoints[j][i]); nextIdx += 5; } @@ -932,12 +933,12 @@ public void PackType2(int partitionIndex6Bit, byte[][] subsetEndpoints, byte[] var colorBitCount = ColorIndexBitCount; var indexBegin = GetIndexBegin(Bc7BlockType.Type2, colorBitCount, false); for (var i = 0; i < 16; i++) { - var indexOffset = GetIndexOffset(Bc7BlockType.Type2, NumSubsets, + var indexOffset = GetIndexOffset(Bc7BlockType.Type2, NumSubsets, partitionIndex6Bit, colorBitCount, i); - var indexBitCount = GetIndexBitCount(NumSubsets, + var indexBitCount = GetIndexBitCount(NumSubsets, partitionIndex6Bit, colorBitCount, i); - - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, indexBegin + indexOffset, indexBitCount, indices[i]); } } @@ -960,14 +961,14 @@ public void PackType3(int partitionIndex6Bit, byte[][] subsetEndpoints, byte[] p //Store endpoints for (var i = 0; i < subsetEndpoints[0].Length; i++) { for (var j = 0; j < subsetEndpoints.Length; j++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, 7, subsetEndpoints[j][i]); nextIdx += 7; } } //Store pBits for (var i = 0; i < pBits.Length; i++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, 1, pBits[i]); nextIdx++; } @@ -976,12 +977,12 @@ public void PackType3(int partitionIndex6Bit, byte[][] subsetEndpoints, byte[] p var colorBitCount = ColorIndexBitCount; var indexBegin = GetIndexBegin(Bc7BlockType.Type3, colorBitCount, false); for (var i = 0; i < 16; i++) { - var indexOffset = GetIndexOffset(Bc7BlockType.Type3, NumSubsets, + var indexOffset = GetIndexOffset(Bc7BlockType.Type3, NumSubsets, partitionIndex6Bit, colorBitCount, i); - var indexBitCount = GetIndexBitCount(NumSubsets, + var indexBitCount = GetIndexBitCount(NumSubsets, partitionIndex6Bit, colorBitCount, i); - - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, indexBegin + indexOffset, indexBitCount, indices[i]); } } @@ -1010,14 +1011,14 @@ public void PackType4(int rotation, byte idxMode, byte[][] colorEndPoints, byte[ //Store color endpoints for (var i = 0; i < colorEndPoints[0].Length; i++) { for (var j = 0; j < colorEndPoints.Length; j++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, 5, colorEndPoints[j][i]); nextIdx += 5; } } //Store alpha endpoints for (var i = 0; i < alphaEndPoints.Length; i++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, 6, alphaEndPoints[i]); nextIdx+=6; } @@ -1026,17 +1027,17 @@ public void PackType4(int rotation, byte idxMode, byte[][] colorEndPoints, byte[ var colorBitCount = ColorIndexBitCount; var colorIndexBegin = GetIndexBegin(Bc7BlockType.Type4, colorBitCount, false); for (var i = 0; i < 16; i++) { - var indexOffset = GetIndexOffset(Bc7BlockType.Type4, NumSubsets, + var indexOffset = GetIndexOffset(Bc7BlockType.Type4, NumSubsets, 0, colorBitCount, i); - var indexBitCount = GetIndexBitCount(NumSubsets, + var indexBitCount = GetIndexBitCount(NumSubsets, 0, colorBitCount, i); if (idxMode == 0) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, colorIndexBegin + indexOffset, indexBitCount, indices2Bit[i]); } else { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, colorIndexBegin + indexOffset, indexBitCount, indices3Bit[i]); } } @@ -1044,17 +1045,17 @@ public void PackType4(int rotation, byte idxMode, byte[][] colorEndPoints, byte[ var alphaBitCount = AlphaIndexBitCount; var alphaIndexBegin = GetIndexBegin(Bc7BlockType.Type4, alphaBitCount, true); for (var i = 0; i < 16; i++) { - var indexOffset = GetIndexOffset(Bc7BlockType.Type4, NumSubsets, + var indexOffset = GetIndexOffset(Bc7BlockType.Type4, NumSubsets, 0, alphaBitCount, i); - var indexBitCount = GetIndexBitCount(NumSubsets, + var indexBitCount = GetIndexBitCount(NumSubsets, 0, alphaBitCount, i); if (idxMode == 0) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, alphaIndexBegin + indexOffset, indexBitCount, indices3Bit[i]); } else { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, alphaIndexBegin + indexOffset, indexBitCount, indices2Bit[i]); } } @@ -1081,14 +1082,14 @@ public void PackType5(int rotation, byte[][] colorEndPoints, byte[] alphaEndPoin //Store color endpoints for (var i = 0; i < colorEndPoints[0].Length; i++) { for (var j = 0; j < colorEndPoints.Length; j++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, 7, colorEndPoints[j][i]); nextIdx += 7; } } //Store alpha endpoints for (var i = 0; i < alphaEndPoints.Length; i++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, 8, alphaEndPoints[i]); nextIdx+=8; } @@ -1097,42 +1098,42 @@ public void PackType5(int rotation, byte[][] colorEndPoints, byte[] alphaEndPoin var colorBitCount = ColorIndexBitCount; var colorIndexBegin = GetIndexBegin(Bc7BlockType.Type5, colorBitCount, false); for (var i = 0; i < 16; i++) { - var indexOffset = GetIndexOffset(Bc7BlockType.Type5, NumSubsets, + var indexOffset = GetIndexOffset(Bc7BlockType.Type5, NumSubsets, 0, colorBitCount, i); - var indexBitCount = GetIndexBitCount(NumSubsets, + var indexBitCount = GetIndexBitCount(NumSubsets, 0, colorBitCount, i); - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, colorIndexBegin + indexOffset, indexBitCount, colorIndices[i]); - + } var alphaBitCount = AlphaIndexBitCount; var alphaIndexBegin = GetIndexBegin(Bc7BlockType.Type5, alphaBitCount, true); for (var i = 0; i < 16; i++) { - var indexOffset = GetIndexOffset(Bc7BlockType.Type5, NumSubsets, + var indexOffset = GetIndexOffset(Bc7BlockType.Type5, NumSubsets, 0, alphaBitCount, i); - var indexBitCount = GetIndexBitCount(NumSubsets, + var indexBitCount = GetIndexBitCount(NumSubsets, 0, alphaBitCount, i); - - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, alphaIndexBegin + indexOffset, indexBitCount, alphaIndices[i]); } } public void PackType6(byte[][] colorAlphaEndPoints, byte[] pBits, byte[] indices) { - Debug.Assert(colorAlphaEndPoints.Length == 2, + Debug.Assert(colorAlphaEndPoints.Length == 2, "Mode 6 should have 2 endpoints"); - Debug.Assert(colorAlphaEndPoints.All(x => x.Length == 4) , + Debug.Assert(colorAlphaEndPoints.All(x => x.Length == 4) , "Mode 6 should have RGBA color endpoints"); - Debug.Assert(colorAlphaEndPoints.All(x => x.All(y => y < 1 << 7)) , + Debug.Assert(colorAlphaEndPoints.All(x => x.All(y => y < 1 << 7)) , "Mode 6 should have 7bit color and alpha endpoints"); Debug.Assert(pBits.Length == 2, "Mode 6 should have 2 pBits"); Debug.Assert(indices.Length == 16, "Provide 16 indices"); Debug.Assert(indices.All(x => x < 1 << 4) , "Mode 6 should have 4bit color indices"); - + lowBits = 0b1000000; // Set Mode 6 highBits = 0; @@ -1140,14 +1141,14 @@ public void PackType6(byte[][] colorAlphaEndPoints, byte[] pBits, byte[] indices //Store color and alpha endpoints for (var i = 0; i < colorAlphaEndPoints[0].Length; i++) { for (var j = 0; j < colorAlphaEndPoints.Length; j++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, 7, colorAlphaEndPoints[j][i]); nextIdx += 7; } } //Store pBits for (var i = 0; i < pBits.Length; i++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, 1, pBits[i]); nextIdx++; } @@ -1156,12 +1157,12 @@ public void PackType6(byte[][] colorAlphaEndPoints, byte[] pBits, byte[] indices var colorBitCount = ColorIndexBitCount; var colorIndexBegin = GetIndexBegin(Bc7BlockType.Type6, colorBitCount, false); for (var i = 0; i < 16; i++) { - var indexOffset = GetIndexOffset(Bc7BlockType.Type6, NumSubsets, + var indexOffset = GetIndexOffset(Bc7BlockType.Type6, NumSubsets, 0, colorBitCount, i); - var indexBitCount = GetIndexBitCount(NumSubsets, + var indexBitCount = GetIndexBitCount(NumSubsets, 0, colorBitCount, i); - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, colorIndexBegin + indexOffset, indexBitCount, indices[i]); } } @@ -1184,14 +1185,14 @@ public void PackType7(int partitionIndex6Bit, byte[][] subsetEndpoints, byte[] p //Store endpoints for (var i = 0; i < subsetEndpoints[0].Length; i++) { for (var j = 0; j < subsetEndpoints.Length; j++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, 5, subsetEndpoints[j][i]); nextIdx += 5; } } //Store pBits for (var i = 0; i < pBits.Length; i++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, 1, pBits[i]); nextIdx++; } @@ -1200,12 +1201,12 @@ public void PackType7(int partitionIndex6Bit, byte[][] subsetEndpoints, byte[] p var colorBitCount = ColorIndexBitCount; var indexBegin = GetIndexBegin(Bc7BlockType.Type7, colorBitCount, false); for (var i = 0; i < 16; i++) { - var indexOffset = GetIndexOffset(Bc7BlockType.Type7, NumSubsets, + var indexOffset = GetIndexOffset(Bc7BlockType.Type7, NumSubsets, partitionIndex6Bit, colorBitCount, i); - var indexBitCount = GetIndexBitCount(NumSubsets, + var indexBitCount = GetIndexBitCount(NumSubsets, partitionIndex6Bit, colorBitCount, i); - - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, indexBegin + indexOffset, indexBitCount, indices[i]); } } diff --git a/BCnEnc.Net/Shared/EncodedBlocks.cs b/BCnEnc.Net/Shared/BlockCompression/EncodedBlocks.cs similarity index 57% rename from BCnEnc.Net/Shared/EncodedBlocks.cs rename to BCnEnc.Net/Shared/BlockCompression/EncodedBlocks.cs index e67b205..fe04d58 100644 --- a/BCnEnc.Net/Shared/EncodedBlocks.cs +++ b/BCnEnc.Net/Shared/BlockCompression/EncodedBlocks.cs @@ -1,14 +1,15 @@ using System; using System.Runtime.InteropServices; using BCnEncoder.Encoder; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Shared { [StructLayout(LayoutKind.Sequential)] internal struct Bc1Block { - public ColorRgb565 color0; - public ColorRgb565 color1; + public ColorB5G6R5Packed color0; + public ColorB5G6R5Packed color1; public uint colorIndices; public int this[int index] @@ -24,27 +25,27 @@ public int this[int index] public readonly bool HasAlphaOrBlack => color0.data <= color1.data; - public readonly RawBlock4X4Rgba32 Decode(bool useAlpha) + public readonly RawBlock4X4RgbaFloat Decode(bool useColorComparisonModeSwitch, bool useAlpha) { - var output = new RawBlock4X4Rgba32(); + var output = new RawBlock4X4RgbaFloat(); var pixels = output.AsSpan; - var color0 = this.color0.ToColorRgb24(); - var color1 = this.color1.ToColorRgb24(); + var color0 = this.color0.ToColorRgbaFloat(); + var color1 = this.color1.ToColorRgbaFloat(); useAlpha = useAlpha && HasAlphaOrBlack; - var colors = HasAlphaOrBlack ? - stackalloc ColorRgb24[] { + var colors = HasAlphaOrBlack && useColorComparisonModeSwitch ? + stackalloc ColorRgbaFloat[] { color0, color1, - color0.InterpolateHalf(color1), - new ColorRgb24(0, 0, 0) - } : stackalloc ColorRgb24[] { + Interpolation.InterpolateColor(color0, color1, .5f), + new ColorRgbaFloat(0, 0, 0) + } : stackalloc ColorRgbaFloat[] { color0, color1, - color0.InterpolateThird(color1, 1), - color0.InterpolateThird(color1, 2) + Interpolation.InterpolateColor(color0, color1, 1f / 3f), + Interpolation.InterpolateColor(color0, color1, 2f / 3f) }; for (var i = 0; i < pixels.Length; i++) @@ -53,13 +54,14 @@ stackalloc ColorRgb24[] { var color = colors[colorIndex]; if (useAlpha && colorIndex == 3) { - pixels[i] = new ColorRgba32(0, 0, 0, 0); + pixels[i] = new ColorRgbaFloat(0, 0, 0, 0); } else { - pixels[i] = new ColorRgba32(color.r, color.g, color.b, 255); + pixels[i] = color; } } + return output; } } @@ -68,47 +70,28 @@ stackalloc ColorRgb24[] { internal struct Bc2Block { public Bc2AlphaBlock alphaBlock; - public ColorRgb565 color0; - public ColorRgb565 color1; - public uint colorIndices; + public Bc1Block colorBlock; public int this[int index] { - readonly get => (int)(colorIndices >> (index * 2)) & 0b11; - set - { - colorIndices = (uint)(colorIndices & ~(0b11 << (index * 2))); - var val = value & 0b11; - colorIndices = colorIndices | ((uint)val << (index * 2)); - } + readonly get => colorBlock[index]; + set => colorBlock[index] = value; } - public readonly byte GetAlpha(int index) => alphaBlock.GetAlpha(index); + public readonly float GetAlpha(int index) => alphaBlock.GetAlpha(index); - public void SetAlpha(int index, byte alpha) => alphaBlock.SetAlpha(index, alpha); + public void SetAlpha(int index, float alpha) => alphaBlock.SetAlpha(index, alpha); - public readonly RawBlock4X4Rgba32 Decode() + public readonly RawBlock4X4RgbaFloat Decode() { - var output = new RawBlock4X4Rgba32(); + var output = colorBlock.Decode(false, false); var pixels = output.AsSpan; - var color0 = this.color0.ToColorRgb24(); - var color1 = this.color1.ToColorRgb24(); - - Span colors = stackalloc ColorRgb24[] { - color0, - color1, - color0.InterpolateThird(color1, 1), - color0.InterpolateThird(color1, 2) - }; - for (var i = 0; i < pixels.Length; i++) { - var colorIndex = (int)((colorIndices >> (i * 2)) & 0b11); - var color = colors[colorIndex]; - - pixels[i] = new ColorRgba32(color.r, color.g, color.b, GetAlpha(i)); + pixels[i].a = GetAlpha(i); } + return output; } } @@ -117,60 +100,18 @@ public readonly RawBlock4X4Rgba32 Decode() internal struct Bc3Block { public Bc4ComponentBlock alphaBlock; - public ColorRgb565 color0; - public ColorRgb565 color1; - public uint colorIndices; - - public int this[int index] - { - readonly get => (int)(colorIndices >> (index * 2)) & 0b11; - set - { - colorIndices = (uint)(colorIndices & ~(0b11 << (index * 2))); - var val = value & 0b11; - colorIndices = colorIndices | ((uint)val << (index * 2)); - } - } + public Bc1Block colorBlock; - public byte Alpha0 + public readonly RawBlock4X4RgbaFloat Decode() { - get => alphaBlock.Endpoint0; - set => alphaBlock.Endpoint0 = value; - } - - public byte Alpha1 - { - get => alphaBlock.Endpoint1; - set => alphaBlock.Endpoint1 = value; - } - - public readonly byte GetAlphaIndex(int pixelIndex) => alphaBlock.GetComponentIndex(pixelIndex); - - public void SetAlphaIndex(int pixelIndex, byte alphaIndex) => alphaBlock.SetComponentIndex(pixelIndex, alphaIndex); - - public readonly RawBlock4X4Rgba32 Decode() - { - var output = new RawBlock4X4Rgba32(); + var output = colorBlock.Decode(false, false); var pixels = output.AsSpan; - var color0 = this.color0.ToColorRgb24(); - var color1 = this.color1.ToColorRgb24(); - - Span colors = stackalloc ColorRgb24[] { - color0, - color1, - color0.InterpolateThird(color1, 1), - color0.InterpolateThird(color1, 2) - }; - var alphas = alphaBlock.Decode(); for (var i = 0; i < pixels.Length; i++) { - var colorIndex = (int)((colorIndices >> (i * 2)) & 0b11); - var color = colors[colorIndex]; - - pixels[i] = new ColorRgba32(color.r, color.g, color.b, alphas[i]); + pixels[i].a = alphas[i]; } return output; } @@ -197,9 +138,9 @@ public byte Endpoint1 public void SetComponentIndex(int pixelIndex, byte redIndex) => componentBlock.SetComponentIndex(pixelIndex, redIndex); - public readonly RawBlock4X4Rgba32 Decode(ColorComponent component = ColorComponent.R) + public readonly RawBlock4X4RgbaFloat Decode(ColorComponent component = ColorComponent.R) { - var output = new RawBlock4X4Rgba32(); + var output = new RawBlock4X4RgbaFloat(); var pixels = output.AsSpan; var components = componentBlock.Decode(); @@ -219,30 +160,6 @@ internal struct Bc5Block public Bc4ComponentBlock redBlock; public Bc4ComponentBlock greenBlock; - public byte Red0 - { - readonly get => redBlock.Endpoint0; - set => redBlock.Endpoint0 = value; - } - - public byte Red1 - { - readonly get => redBlock.Endpoint1; - set => redBlock.Endpoint1 = value; - } - - public byte Green0 - { - readonly get => greenBlock.Endpoint0; - set => greenBlock.Endpoint0 = value; - } - - public byte Green1 - { - readonly get => greenBlock.Endpoint1; - set => greenBlock.Endpoint1 = value; - } - public readonly byte GetRedIndex(int pixelIndex) => redBlock.GetComponentIndex(pixelIndex); public void SetRedIndex(int pixelIndex, byte redIndex) => redBlock.SetComponentIndex(pixelIndex, redIndex); @@ -251,9 +168,9 @@ public byte Green1 public void SetGreenIndex(int pixelIndex, byte greenIndex) => greenBlock.SetComponentIndex(pixelIndex, greenIndex); - public readonly RawBlock4X4Rgba32 Decode(ColorComponent component1 = ColorComponent.R, ColorComponent component2 = ColorComponent.G) + public readonly RawBlock4X4RgbaFloat Decode(ColorComponent component1 = ColorComponent.R, ColorComponent component2 = ColorComponent.G, ColorComponent componentCalculated = ColorComponent.None) { - var output = new RawBlock4X4Rgba32(); + var output = new RawBlock4X4RgbaFloat(); var pixels = output.AsSpan; var reds = redBlock.Decode(); @@ -261,8 +178,25 @@ public readonly RawBlock4X4Rgba32 Decode(ColorComponent component1 = ColorCompon for (var i = 0; i < pixels.Length; i++) { - pixels[i] = ComponentHelper.ComponentToColor(component1, reds[i]); - pixels[i] = ComponentHelper.ComponentToColor(pixels[i], component2, greens[i]); + ColorRgbaFloat color = ComponentHelper.ComponentToColor(component1, reds[i]); + ComponentHelper.ComponentToColor(ref color, component2, greens[i]); + + // Calculate Z + if (componentCalculated != ColorComponent.None) + { + float x = reds[i] * 2 - 1; + float y = greens[i] * 2 - 1; + float z = 1 - x * x - y * y; + + if (z < 0) + z = 0; + else + z = MathF.Sqrt(z); + + ComponentHelper.ComponentToColor(ref color, componentCalculated, z); + } + + pixels[i] = color; } return output; @@ -272,8 +206,8 @@ public readonly RawBlock4X4Rgba32 Decode(ColorComponent component1 = ColorCompon [StructLayout(LayoutKind.Sequential)] internal struct AtcBlock { - public ColorRgb555 color0; - public ColorRgb565 color1; + public ColorB5G5R5M1Packed color0; + public ColorB5G6R5Packed color1; public uint colorIndices; public int this[int index] @@ -287,16 +221,18 @@ public int this[int index] } } - public readonly RawBlock4X4Rgba32 Decode() + public readonly RawBlock4X4RgbaFloat Decode() { - var output = new RawBlock4X4Rgba32(); + var output = new RawBlock4X4RgbaFloat(); var pixels = output.AsSpan; - var color0 = this.color0.ToColorRgb24(); - var color1 = this.color1.ToColorRgb24(); + var color0 = this.color0.ToColorRgbaFloat(); + var color1 = this.color1.ToColorRgbaFloat(); + + int mode = this.color0.Mode; - Span colors = stackalloc ColorRgb24[] { - new ColorRgb24(0, 0, 0), + Span colorsMode1 = stackalloc ColorRgbaFloat[] { + new ColorRgbaFloat(0, 0, 0), color0.InterpolateFourthAtc(color1, 1), color0, color1 @@ -306,9 +242,9 @@ public readonly RawBlock4X4Rgba32 Decode() { var colorIndex = this[i]; - var color = this.color0.Mode == 0 ? color0.InterpolateThird(color1, colorIndex) : colors[colorIndex]; + var color = mode == 0 ? color0.InterpolateThird(color1, colorIndex) : colorsMode1[colorIndex]; - pixels[i] = new ColorRgba32(color.r, color.g, color.b, 255); + pixels[i] = color; } return output; } @@ -319,20 +255,22 @@ internal struct Bc2AlphaBlock { public ulong alphas; - public readonly byte GetAlpha(int index) + public readonly float GetAlpha(int index) { var mask = 0xFUL << (index * 4); var shift = index * 4; + + // Unorm4 alpha var alphaUnscaled = (alphas & mask) >> shift; - return (byte)(alphaUnscaled * 17); + return ColorBitConversionHelpers.Unorm4ToFloat((uint)alphaUnscaled); } - public void SetAlpha(int index, byte alpha) + public void SetAlpha(int index, float alpha) { var mask = 0xFUL << (index * 4); var shift = index * 4; alphas &= ~mask; - var a = (byte)(alpha / 17); + var a = ColorBitConversionHelpers.FloatToUnorm4(alpha); alphas |= (ulong)(a & 0xF) << shift; } } @@ -342,6 +280,26 @@ internal struct Bc4ComponentBlock { public ulong componentBlock; + public float FloatEndpoint0 + { + readonly get => ColorBitConversionHelpers.Unorm8ToFloat((byte)(componentBlock & 0xFFUL)); + set + { + componentBlock &= ~0xFFUL; + componentBlock |= ColorBitConversionHelpers.FloatToUnorm8(value); + } + } + + public float FloatEndpoint1 + { + readonly get =>ColorBitConversionHelpers.Unorm8ToFloat((byte)((componentBlock >> 8) & 0xFFUL)); + set + { + componentBlock &= ~0xFF00UL; + componentBlock |= (ulong)ColorBitConversionHelpers.FloatToUnorm8(value) << 8; + } + } + public byte Endpoint0 { readonly get => (byte)(componentBlock & 0xFFUL); @@ -378,14 +336,14 @@ public void SetComponentIndex(int pixelIndex, byte redIndex) componentBlock |= (ulong)(redIndex & 0b111) << shift; } - public readonly byte[] Decode() + public readonly float[] Decode() { - var output = new byte[16]; + var output = new float[16]; - var c0 = Endpoint0; - var c1 = Endpoint1; + var c0 = FloatEndpoint0; + var c1 = FloatEndpoint1; - var components = c0 > c1 ? stackalloc byte[] { + var components = c0 > c1 ? stackalloc float[] { c0, c1, c0.InterpolateSeventh(c1, 1), @@ -394,7 +352,7 @@ public readonly byte[] Decode() c0.InterpolateSeventh(c1, 4), c0.InterpolateSeventh(c1, 5), c0.InterpolateSeventh(c1, 6), - } : stackalloc byte[] { + } : stackalloc float[] { c0, c1, c0.InterpolateFifth(c1, 1), @@ -402,7 +360,7 @@ public readonly byte[] Decode() c0.InterpolateFifth(c1, 3), c0.InterpolateFifth(c1, 4), 0, - 255 + 1 }; for (var i = 0; i < output.Length; i++) @@ -421,7 +379,7 @@ internal struct AtcExplicitAlphaBlock public Bc2AlphaBlock alphas; public AtcBlock colors; - public readonly RawBlock4X4Rgba32 Decode() + public readonly RawBlock4X4RgbaFloat Decode() { var output = colors.Decode(); var pixels = output.AsSpan; @@ -440,7 +398,7 @@ internal struct AtcInterpolatedAlphaBlock public Bc4ComponentBlock alphas; public AtcBlock colors; - public readonly RawBlock4X4Rgba32 Decode() + public readonly RawBlock4X4RgbaFloat Decode() { var output = colors.Decode(); var pixels = output.AsSpan; diff --git a/BCnEnc.Net/Shared/BlockCompression/ImageToBlocks.cs b/BCnEnc.Net/Shared/BlockCompression/ImageToBlocks.cs new file mode 100644 index 0000000..12eee64 --- /dev/null +++ b/BCnEnc.Net/Shared/BlockCompression/ImageToBlocks.cs @@ -0,0 +1,150 @@ +using System; +using BCnEncoder.Shared.Colors; +using CommunityToolkit.HighPerformance; + +namespace BCnEncoder.Shared +{ + public enum BlockPixelSize + { + // No blocks. + Size1x1x1, + // 2D Blocks + Size4x4x1, + Size5x4x1, + Size5x5x1, + Size6x5x1, + Size6x6x1, + Size8x5x1, + Size8x6x1, + Size8x8x1, + Size10x5x1, + Size10x6x1, + Size10x8x1, + Size10x10x1, + Size12x10x1, + Size12x12x1, + // 3D Blocks + Size3x3x3, + Size4x3x3, + Size4x4x4, + Size5x4x4, + Size5x5x4, + Size5x5x5, + Size6x5x5, + Size6x6x5, + Size6x6x6 + } + internal static class ImageToBlocks + { + internal static void ColorsFromRawBlocks(RawBlock4X4RgbaFloat[] blocks, Span output, int pixelWidth, int pixelHeight) + { + var blocksWidth = ((pixelWidth + 3) & ~3) >> 2; + + for (var y = 0; y < pixelHeight; y++) + { + for (var x = 0; x < pixelWidth; x++) + { + var blockIndexX = x >> 2; + var blockIndexY = y >> 2; + var blockInternalIndexX = x & 3; + var blockInternalIndexY = y & 3; + + var blockIndex = blockIndexX + blockIndexY * blocksWidth; + + output[x + y * pixelWidth] = blocks[blockIndex][blockInternalIndexX, blockInternalIndexY]; + } + } + } + + internal static RawBlock4X4RgbaFloat[] ImageTo4X4(ReadOnlyMemory image, int width, int height, out int blocksWidth, out int blocksHeight) + { + blocksWidth = ((width + 3) & ~3) >> 2; + blocksHeight = ((height + 3) & ~3) >> 2; + + var output = new RawBlock4X4RgbaFloat[blocksWidth * blocksHeight]; + + var span = image.Span; + + for (var y = 0; y < height; y++) + { + for (var x = 0; x < width; x++) + { + var color = span[y * width + x]; + + var blockIndexX = x >> 2; + var blockIndexY = y >> 2; + var blockInternalIndexX = x & 3; + var blockInternalIndexY = y & 3; + + output[blockIndexX + blockIndexY * blocksWidth][blockInternalIndexX, blockInternalIndexY] = color; + } + } + + // Fill in block y with edge color + if ((height & 3) != 0) + { + var yPaddingStart = height & 3; + for (var i = 0; i < blocksWidth; i++) + { + var lastBlock = output[i + blocksWidth * (blocksHeight - 1)]; + for (var y = yPaddingStart; y < 4; y++) + { + for (var x = 0; x < 4; x++) + { + lastBlock[x, y] = lastBlock[x, y - 1]; + } + } + output[i + blocksWidth * (blocksHeight - 1)] = lastBlock; + } + } + + // Fill in block x with edge color + if ((width & 3) != 0) + { + var xPaddingStart = width & 3; + for (var i = 0; i < blocksHeight; i++) + { + var lastBlock = output[blocksWidth - 1 + i * blocksWidth]; + for (var x = xPaddingStart; x < 4; x++) + { + for (var y = 0; y < 4; y++) + { + lastBlock[x, y] = lastBlock[x - 1, y]; + } + } + output[blocksWidth - 1 + i * blocksWidth] = lastBlock; + } + } + + return output; + } + + public static int CalculateNumOfBlocks(CompressionFormat blockFormat, int pixelWidth, int pixelHeight, int pixelDepth = 1) + { + CalculateNumOfBlocks(blockFormat, pixelWidth, pixelHeight, pixelDepth, out var blocksWidth, out var blocksHeight, out var blocksDepth); + return blocksWidth * blocksHeight * blocksDepth; + } + public static void CalculateNumOfBlocks(CompressionFormat blockFormat, int pixelWidth, int pixelHeight, int pixelDepth, out int blocksWidth, out int blocksHeight, out int blocksDepth) + { + BlockPixelSize blockSize = blockFormat.GetBlockPixelSize(); + + // Default case + if (blockSize == BlockPixelSize.Size4x4x1) + { + blocksWidth = ((pixelWidth + 3) & ~3) >> 2; + blocksHeight = ((pixelHeight + 3) & ~3) >> 2; + blocksDepth = pixelDepth; + } + else if (blockSize == BlockPixelSize.Size1x1x1) + { + blocksWidth = pixelWidth; + blocksHeight = pixelHeight; + blocksDepth = pixelDepth; + } + else + { + throw new NotImplementedException(); + } + } + } +} diff --git a/BCnEnc.Net/Shared/BlockCompression/RawBlocks.cs b/BCnEnc.Net/Shared/BlockCompression/RawBlocks.cs new file mode 100644 index 0000000..c601cb8 --- /dev/null +++ b/BCnEnc.Net/Shared/BlockCompression/RawBlocks.cs @@ -0,0 +1,396 @@ +using System; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using BCnEncoder.Encoder.Bptc; +using BCnEncoder.Shared.Colors; +using CommunityToolkit.HighPerformance; + +namespace BCnEncoder.Shared +{ + + // public struct RawBlock4X4Rgba32 + // { + // public ColorRgba32 p00, p10, p20, p30; + // public ColorRgba32 p01, p11, p21, p31; + // public ColorRgba32 p02, p12, p22, p32; + // public ColorRgba32 p03, p13, p23, p33; + // + // public RawBlock4X4Rgba32(ColorRgba32 fillColor) + // { + // p00 = p01 = p02 = p03 = + // p10 = p11 = p12 = p13 = + // p20 = p21 = p22 = p23 = + // p30 = p31 = p32 = p33 = fillColor; + // } + // + // public Span AsSpan => MemoryMarshal.CreateSpan(ref p00, 16); + // + // public ColorRgba32 this[int x, int y] + // { + // get => AsSpan[x + y * 4]; + // set => AsSpan[x + y * 4] = value; + // } + // + // public ColorRgba32 this[int index] + // { + // get => AsSpan[index]; + // set => AsSpan[index] = value; + // } + // + // internal int CalculateError(RawBlock4X4Rgba32 other, bool useAlpha = false) + // { + // float error = 0; + // var pix1 = AsSpan; + // var pix2 = other.AsSpan; + // for (var i = 0; i < pix1.Length; i++) + // { + // var col1 = pix1[i]; + // var col2 = pix2[i]; + // + // var re = col1.r - col2.r; + // var ge = col1.g - col2.g; + // var be = col1.b - col2.b; + // + // error += re * re; + // error += ge * ge; + // error += be * be; + // + // if (useAlpha) + // { + // var ae = col1.a - col2.a; + // error += ae * ae * 4; + // } + // } + // + // error /= pix1.Length; + // error = MathF.Sqrt(error); + // + // return (int)error; + // } + // + // internal float CalculateYCbCrError(RawBlock4X4Rgba32 other) + // { + // float yError = 0; + // float cbError = 0; + // float crError = 0; + // var pix1 = AsSpan; + // var pix2 = other.AsSpan; + // for (var i = 0; i < pix1.Length; i++) + // { + // var col1 = pix1[i].As(); + // var col2 = pix2[i].As(); + // + // var ye = col1.y - col2.y; + // var cbe = col1.cb - col2.cb; + // var cre = col1.cr - col2.cr; + // + // yError += ye * ye; + // cbError += cbe * cbe; + // crError += cre * cre; + // } + // + // var error = yError * 2 + cbError / 2 + crError / 2; + // + // return error; + // } + // + // internal float CalculateYCbCrAlphaError(RawBlock4X4Rgba32 other, float yMultiplier = 2, float alphaMultiplier = 1) + // { + // float yError = 0; + // float cbError = 0; + // float crError = 0; + // float alphaError = 0; + // var pix1 = AsSpan; + // var pix2 = other.AsSpan; + // for (var i = 0; i < pix1.Length; i++) + // { + // var col1 = pix1[i].As(); + // var col2 = pix2[i].As(); + // + // var ye = (col1.y - col2.y) * yMultiplier; + // var cbe = col1.cb - col2.cb; + // var cre = col1.cr - col2.cr; + // var ae = (col1.a - col2.a) * alphaMultiplier; + // + // yError += ye * ye; + // cbError += cbe * cbe; + // crError += cre * cre; + // alphaError += ae * ae; + // } + // + // var error = yError + cbError + crError + alphaError; + // return error; + // } + // + // internal RawBlock4X4Ycbcr ToRawBlockYcbcr() + // { + // var rawYcbcr = new RawBlock4X4Ycbcr(); + // var pixels = AsSpan; + // var ycbcrPs = rawYcbcr.AsSpan; + // for (var i = 0; i < pixels.Length; i++) + // { + // ycbcrPs[i] = pixels[i].As(); + // } + // return rawYcbcr; + // } + // + // public bool HasTransparentPixels() + // { + // var pixels = AsSpan; + // for (var i = 0; i < pixels.Length; i++) + // { + // if (pixels[i].a < 255) return true; + // } + // return false; + // } + // } + + public struct RawBlock4X4RgbaFloat + { + public ColorRgbaFloat p00, p10, p20, p30; + public ColorRgbaFloat p01, p11, p21, p31; + public ColorRgbaFloat p02, p12, p22, p32; + public ColorRgbaFloat p03, p13, p23, p33; + + public RawBlock4X4RgbaFloat(ColorRgbaFloat fillColor) + { + p00 = p01 = p02 = p03 = + p10 = p11 = p12 = p13 = + p20 = p21 = p22 = p23 = + p30 = p31 = p32 = p33 = fillColor; + } + + public Span AsSpan => MemoryMarshal.CreateSpan(ref p00, 16); + internal Span > AsVector128 => MemoryMarshal.CreateSpan(ref p00, 16).Cast>(); + internal Span > AsVector256 => MemoryMarshal.CreateSpan(ref p00, 16).Cast>(); + + public ColorRgbaFloat this[int x, int y] + { + get => AsSpan[x + y * 4]; + set => AsSpan[x + y * 4] = value; + } + + public ColorRgbaFloat this[int index] + { + get => AsSpan[index]; + set => AsSpan[index] = value; + } + + internal float CalculateError(RawBlock4X4RgbaFloat other) + { + float error = 0; + var pix1 = AsSpan; + var pix2 = other.AsSpan; + + for (var i = 0; i < pix1.Length; i++) + { + var col1 = pix1[i]; + var col2 = pix2[i]; + + var re = Math.Sign(col1.r) * MathF.Log( 1 + MathF.Abs(col1.r)) - Math.Sign(col2.r) * MathF.Log( 1 + MathF.Abs(col2.r)); + var ge = Math.Sign(col1.g) * MathF.Log( 1 + MathF.Abs(col1.g)) - Math.Sign(col2.g) * MathF.Log( 1 + MathF.Abs(col2.g)); + var be = Math.Sign(col1.b) * MathF.Log( 1 + MathF.Abs(col1.b)) - Math.Sign(col2.b) * MathF.Log( 1 + MathF.Abs(col2.b)); + var ae = Math.Sign(col1.a) * MathF.Log( 1 + MathF.Abs(col1.a)) - Math.Sign(col2.a) * MathF.Log( 1 + MathF.Abs(col2.a)); + + error += re * re; + error += ge * ge; + error += be * be; + error += ae * ae; + } + + error /= pix1.Length * 4; + error = MathF.Sqrt(error); + + return error; + } + + + internal RawBlock4X4YcbcrAlpha ToRawBlockYcbcrAlpha() + { + var rawYcbcr = new RawBlock4X4YcbcrAlpha(); + var pixels = AsSpan; + var ycbcrPs = rawYcbcr.AsSpan; + for (var i = 0; i < pixels.Length; i++) + { + pixels[i].To(ref ycbcrPs[i]); + } + return rawYcbcr; + } + + public bool HasTransparentPixels(float alphaThreshold = 1) + { + var pixels = AsSpan; + for (var i = 0; i < pixels.Length; i++) + { + if (pixels[i].a < alphaThreshold) return true; + } + return false; + } + + internal void ColorConvert(ColorConversionMode mode) + { + if (mode == ColorConversionMode.None) + return; + Span pixels = AsSpan; + + if (mode == ColorConversionMode.LinearToSrgb) + { + for (var i = 0; i < pixels.Length; i++) + { + pixels[i] = ColorSpace.LrgbToSrgb(pixels[i]); + } + } + else + { + for (var i = 0; i < pixels.Length; i++) + { + pixels[i] = ColorSpace.SrgbToLrgb(pixels[i]); + } + } + } + + internal float CalculateYCbCrAlphaError(RawBlock4X4RgbaFloat other, float yMultiplier = 2, + float alphaMultiplier = 1) + { + // TODO: COLORSPACE + return ToRawBlockYcbcrAlpha() + .CalculateError(other.ToRawBlockYcbcrAlpha(), yMultiplier, 1, 1, alphaMultiplier); + } + + internal RawBlock4X4Vector4 AsVector4() + { + RawBlock4X4Vector4 result = new RawBlock4X4Vector4(); + var pixels = AsSpan; + var resultPixels = result.AsSpan; + + pixels.Cast().CopyTo(resultPixels); + + return result; + } + } + + public struct RawBlock4X4Vector4 + { + public Vector4 p00, p10, p20, p30; + public Vector4 p01, p11, p21, p31; + public Vector4 p02, p12, p22, p32; + public Vector4 p03, p13, p23, p33; + + public RawBlock4X4Vector4(Vector4 fillColor) + { + p00 = p01 = p02 = p03 = + p10 = p11 = p12 = p13 = + p20 = p21 = p22 = p23 = + p30 = p31 = p32 = p33 = fillColor; + } + + public Span AsSpan => MemoryMarshal.CreateSpan(ref p00, 16); + internal Span > AsVector128 => MemoryMarshal.CreateSpan(ref p00, 16).Cast>(); + internal Span > AsVector256 => MemoryMarshal.CreateSpan(ref p00, 16).Cast>(); + + public Vector4 this[int x, int y] + { + get => AsSpan[x + y * 4]; + set => AsSpan[x + y * 4] = value; + } + + public Vector4 this[int index] + { + get => AsSpan[index]; + set => AsSpan[index] = value; + } + + public bool HasTransparentPixels(float alphaThreshold = 1) + { + var pixels = AsSpan; + for (var i = 0; i < pixels.Length; i++) + { + if (pixels[i].W < alphaThreshold) return true; + } + return false; + } + } + + //Used for Bc6H + // internal struct RawBlock4X4RgbHalfInt + // { + // public (int, int, int) p00, p10, p20, p30; + // public (int, int, int) p01, p11, p21, p31; + // public (int, int, int) p02, p12, p22, p32; + // public (int, int, int) p03, p13, p23, p33; + // public Span<(int, int, int)> AsSpan => MemoryMarshal.CreateSpan(ref p00, 16); + // + // public (int, int, int) this[int x, int y] + // { + // get => AsSpan[x + y * 4]; + // set => AsSpan[x + y * 4] = value; + // } + // + // public (int, int, int) this[int index] + // { + // get => AsSpan[index]; + // set => AsSpan[index] = value; + // } + // + // public static RawBlock4X4RgbHalfInt FromRawFloats(RawBlock4X4RgbFloat other, bool signed) + // { + // var output = new RawBlock4X4RgbHalfInt(); + // var span = output.AsSpan; + // var floats = other.AsSpan; + // for (var i = 0; i < 16; i++) + // { + // span[i] = Bc6EncodingHelpers.PreQuantizeRawEndpoint(floats[i], signed); + // } + // return output; + // } + // + // } + + [StructLayout(LayoutKind.Sequential)] + internal struct RawBlock4X4YcbcrAlpha + { + public ColorYCbCrAlpha p00, p10, p20, p30; + public ColorYCbCrAlpha p01, p11, p21, p31; + public ColorYCbCrAlpha p02, p12, p22, p32; + public ColorYCbCrAlpha p03, p13, p23, p33; + public Span AsSpan => MemoryMarshal.CreateSpan(ref p00, 16); + + public ColorYCbCrAlpha this[int x, int y] + { + get => AsSpan[x + y * 4]; + set => AsSpan[x + y * 4] = value; + } + + public float CalculateError(RawBlock4X4YcbcrAlpha other, float yErrorWeight, float cbErrorWeight, float crErrorWeight, float aErrorWeight) + { + float yError = 0; + float cbError = 0; + float crError = 0; + float aError = 0; + + var pix1 = AsSpan; + var pix2 = other.AsSpan; + + for (var i = 0; i < pix1.Length; i++) + { + var col1 = pix1[i]; + var col2 = pix2[i]; + + var ye = col1.y - col2.y; + var cbe = col1.cb - col2.cb; + var cre = col1.cr - col2.cr; + var ae = col1.a - col2.a; + + yError += ye * ye; + cbError += cbe * cbe; + crError += cre * cre; + aError += ae * ae; + } + + var error = yError * yErrorWeight + cbError * cbErrorWeight + crError * crErrorWeight + aError * aErrorWeight; + + return error; + } + } +} diff --git a/BCnEnc.Net/Shared/ByteHelper.cs b/BCnEnc.Net/Shared/ByteHelper.cs index 7c60b60..ae27453 100644 --- a/BCnEnc.Net/Shared/ByteHelper.cs +++ b/BCnEnc.Net/Shared/ByteHelper.cs @@ -1,4 +1,6 @@ -namespace BCnEncoder.Shared +using System; + +namespace BCnEncoder.Shared { internal static class ByteHelper { @@ -10,7 +12,10 @@ public static byte ClampToByte(int i) } public static byte ClampToByte(float f) - => ClampToByte((int)f); + => ClampToByte((int)MathF.Floor(f + 0.5f)); + + public static byte FloatToByte(float f) + => ClampToByte((int)MathF.Floor(f * 255f + 0.5f)); public static byte Extract1(ulong source, int index) { diff --git a/BCnEnc.Net/Shared/ColorComponent.cs b/BCnEnc.Net/Shared/ColorComponent.cs index 81452b6..3780646 100644 --- a/BCnEnc.Net/Shared/ColorComponent.cs +++ b/BCnEnc.Net/Shared/ColorComponent.cs @@ -26,8 +26,13 @@ public enum ColorComponent A, /// - /// Use the color's luminance value as the component. + /// Fill all components of an Rgba color (excluding alpha). /// - Luminance + RGB, + + /// + /// Use no component. + /// + None } } diff --git a/BCnEnc.Net/Shared/Colors.cs b/BCnEnc.Net/Shared/Colors.cs deleted file mode 100644 index 565301d..0000000 --- a/BCnEnc.Net/Shared/Colors.cs +++ /dev/null @@ -1,1338 +0,0 @@ -using System; -using System.Numerics; - -namespace BCnEncoder.Shared -{ - public struct ColorRgba32 : IEquatable - { - public byte r, g, b, a; - - public ColorRgba32(byte r, byte g, byte b, byte a) - { - this.r = r; - this.g = g; - this.b = b; - this.a = a; - } - - public ColorRgba32(byte r, byte g, byte b) - { - this.r = r; - this.g = g; - this.b = b; - this.a = 255; - } - - public bool Equals(ColorRgba32 other) - { - return r == other.r && g == other.g && b == other.b && a == other.a; - } - - public override bool Equals(object obj) - { - return obj is ColorRgba32 other && Equals(other); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = r.GetHashCode(); - hashCode = (hashCode * 397) ^ g.GetHashCode(); - hashCode = (hashCode * 397) ^ b.GetHashCode(); - hashCode = (hashCode * 397) ^ a.GetHashCode(); - return hashCode; - } - } - - public static bool operator ==(ColorRgba32 left, ColorRgba32 right) - { - return left.Equals(right); - } - - public static bool operator !=(ColorRgba32 left, ColorRgba32 right) - { - return !left.Equals(right); - } - - public static ColorRgba32 operator +(ColorRgba32 left, ColorRgba32 right) - { - return new ColorRgba32( - ByteHelper.ClampToByte(left.r + right.r), - ByteHelper.ClampToByte(left.g + right.g), - ByteHelper.ClampToByte(left.b + right.b), - ByteHelper.ClampToByte(left.a + right.a)); - } - - public static ColorRgba32 operator -(ColorRgba32 left, ColorRgba32 right) - { - return new ColorRgba32( - ByteHelper.ClampToByte(left.r - right.r), - ByteHelper.ClampToByte(left.g - right.g), - ByteHelper.ClampToByte(left.b - right.b), - ByteHelper.ClampToByte(left.a - right.a)); - } - - public static ColorRgba32 operator /(ColorRgba32 left, double right) - { - return new ColorRgba32( - ByteHelper.ClampToByte((int)(left.r / right)), - ByteHelper.ClampToByte((int)(left.g / right)), - ByteHelper.ClampToByte((int)(left.b / right)), - ByteHelper.ClampToByte((int)(left.a / right)) - ); - } - - public static ColorRgba32 operator *(ColorRgba32 left, double right) - { - return new ColorRgba32( - ByteHelper.ClampToByte((int)(left.r * right)), - ByteHelper.ClampToByte((int)(left.g * right)), - ByteHelper.ClampToByte((int)(left.b * right)), - ByteHelper.ClampToByte((int)(left.a * right)) - ); - } - - /// - /// Component-wise left shift - /// - public static ColorRgba32 operator <<(ColorRgba32 left, int right) - { - return new ColorRgba32( - ByteHelper.ClampToByte(left.r << right), - ByteHelper.ClampToByte(left.g << right), - ByteHelper.ClampToByte(left.b << right), - ByteHelper.ClampToByte(left.a << right) - ); - } - - /// - /// Component-wise right shift - /// - public static ColorRgba32 operator >>(ColorRgba32 left, int right) - { - return new ColorRgba32( - ByteHelper.ClampToByte(left.r >> right), - ByteHelper.ClampToByte(left.g >> right), - ByteHelper.ClampToByte(left.b >> right), - ByteHelper.ClampToByte(left.a >> right) - ); - } - - /// - /// Component-wise bitwise OR operation - /// - public static ColorRgba32 operator |(ColorRgba32 left, ColorRgba32 right) - { - return new ColorRgba32( - ByteHelper.ClampToByte(left.r | right.r), - ByteHelper.ClampToByte(left.g | right.g), - ByteHelper.ClampToByte(left.b | right.b), - ByteHelper.ClampToByte(left.a | right.a) - ); - } - - /// - /// Component-wise bitwise OR operation - /// - public static ColorRgba32 operator |(ColorRgba32 left, int right) - { - return new ColorRgba32( - ByteHelper.ClampToByte(left.r | right), - ByteHelper.ClampToByte(left.g | right), - ByteHelper.ClampToByte(left.b | right), - ByteHelper.ClampToByte(left.a | right) - ); - } - - /// - /// Component-wise bitwise AND operation - /// - public static ColorRgba32 operator &(ColorRgba32 left, ColorRgba32 right) - { - return new ColorRgba32( - ByteHelper.ClampToByte(left.r & right.r), - ByteHelper.ClampToByte(left.g & right.g), - ByteHelper.ClampToByte(left.b & right.b), - ByteHelper.ClampToByte(left.a & right.a) - ); - } - - /// - /// Component-wise bitwise AND operation - /// - public static ColorRgba32 operator &(ColorRgba32 left, int right) - { - return new ColorRgba32( - ByteHelper.ClampToByte(left.r & right), - ByteHelper.ClampToByte(left.g & right), - ByteHelper.ClampToByte(left.b & right), - ByteHelper.ClampToByte(left.a & right) - ); - } - - public override string ToString() - { - return $"r : {r} g : {g} b : {b} a : {a}"; - } - - internal readonly ColorRgbaFloat ToFloat() - { - return new ColorRgbaFloat(this); - } - - public readonly ColorRgbFloat ToRgbFloat() - { - return new ColorRgbFloat(this); - } - } - - internal struct ColorRgbaFloat : IEquatable - { - public float r, g, b, a; - - public ColorRgbaFloat(float r, float g, float b, float a) - { - this.r = r; - this.g = g; - this.b = b; - this.a = a; - } - - public ColorRgbaFloat(ColorRgba32 other) - { - this.r = other.r / 255f; - this.g = other.g / 255f; - this.b = other.b / 255f; - this.a = other.a / 255f; - } - - public ColorRgbaFloat(float r, float g, float b) - { - this.r = r; - this.g = g; - this.b = b; - this.a = 1; - } - - public bool Equals(ColorRgbaFloat other) - { - return r == other.r && g == other.g && b == other.b && a == other.a; - } - - public override bool Equals(object obj) - { - return obj is ColorRgbaFloat other && Equals(other); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = r.GetHashCode(); - hashCode = (hashCode * 397) ^ g.GetHashCode(); - hashCode = (hashCode * 397) ^ b.GetHashCode(); - hashCode = (hashCode * 397) ^ a.GetHashCode(); - return hashCode; - } - } - - public static bool operator ==(ColorRgbaFloat left, ColorRgbaFloat right) - { - return left.Equals(right); - } - - public static bool operator !=(ColorRgbaFloat left, ColorRgbaFloat right) - { - return !left.Equals(right); - } - - public static ColorRgbaFloat operator +(ColorRgbaFloat left, ColorRgbaFloat right) - { - return new ColorRgbaFloat( - left.r + right.r, - left.g + right.g, - left.b + right.b, - left.a + right.a); - } - - public static ColorRgbaFloat operator -(ColorRgbaFloat left, ColorRgbaFloat right) - { - return new ColorRgbaFloat( - left.r - right.r, - left.g - right.g, - left.b - right.b, - left.a - right.a); - } - - public static ColorRgbaFloat operator /(ColorRgbaFloat left, float right) - { - return new ColorRgbaFloat( - left.r / right, - left.g / right, - left.b / right, - left.a / right - ); - } - - public static ColorRgbaFloat operator *(ColorRgbaFloat left, float right) - { - return new ColorRgbaFloat( - left.r * right, - left.g * right, - left.b * right, - left.a * right - ); - } - - public static ColorRgbaFloat operator *(float left, ColorRgbaFloat right) - { - return new ColorRgbaFloat( - right.r * left, - right.g * left, - right.b * left, - right.a * left - ); - } - - public override string ToString() - { - return $"r : {r:0.00} g : {g:0.00} b : {b:0.00} a : {a:0.00}"; - } - - public ColorRgba32 ToRgba32() - { - return new ColorRgba32( - (byte)(ByteHelper.ClampToByte(r * 255)), - (byte)(ByteHelper.ClampToByte(g * 255)), - (byte)(ByteHelper.ClampToByte(b * 255)), - (byte)(ByteHelper.ClampToByte(a * 255)) - ); - } - - } - - public struct ColorRgbFloat : IEquatable - { - public float r, g, b; - - public ColorRgbFloat(float r, float g, float b) - { - this.r = r; - this.g = g; - this.b = b; - } - - public ColorRgbFloat(ColorRgba32 other) - { - this.r = other.r / 255f; - this.g = other.g / 255f; - this.b = other.b / 255f; - } - - public ColorRgbFloat(Vector3 vector) - { - r = vector.X; - g = vector.Y; - b = vector.Z; - } - - public bool Equals(ColorRgbFloat other) - { - return r == other.r && g == other.g && b == other.b; - } - - public override bool Equals(object obj) - { - return obj is ColorRgbFloat other && Equals(other); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = r.GetHashCode(); - hashCode = (hashCode * 397) ^ g.GetHashCode(); - hashCode = (hashCode * 397) ^ b.GetHashCode(); - return hashCode; - } - } - - public static bool operator ==(ColorRgbFloat left, ColorRgbFloat right) - { - return left.Equals(right); - } - - public static bool operator !=(ColorRgbFloat left, ColorRgbFloat right) - { - return !left.Equals(right); - } - - public static ColorRgbFloat operator +(ColorRgbFloat left, ColorRgbFloat right) - { - return new ColorRgbFloat( - left.r + right.r, - left.g + right.g, - left.b + right.b); - } - - public static ColorRgbFloat operator -(ColorRgbFloat left, ColorRgbFloat right) - { - return new ColorRgbFloat( - left.r - right.r, - left.g - right.g, - left.b - right.b); - } - - public static ColorRgbFloat operator /(ColorRgbFloat left, float right) - { - return new ColorRgbFloat( - left.r / right, - left.g / right, - left.b / right - ); - } - - public static ColorRgbFloat operator *(ColorRgbFloat left, float right) - { - return new ColorRgbFloat( - left.r * right, - left.g * right, - left.b * right - ); - } - - public static ColorRgbFloat operator *(float left, ColorRgbFloat right) - { - return new ColorRgbFloat( - right.r * left, - right.g * left, - right.b * left - ); - } - - public override string ToString() - { - return $"r : {r:0.00} g : {g:0.00} b : {b:0.00}"; - } - - public ColorRgba32 ToRgba32() - { - return new ColorRgba32( - (byte)(ByteHelper.ClampToByte(r * 255)), - (byte)(ByteHelper.ClampToByte(g * 255)), - (byte)(ByteHelper.ClampToByte(b * 255)), - 255 - ); - } - - public Vector3 ToVector3() - { - return new Vector3(r, g, b); - } - - internal float CalcLogDist(ColorRgbFloat other) - { - var dr = Math.Sign(other.r) * MathF.Log(1 + MathF.Abs(other.r)) - Math.Sign(r) * MathF.Log(1 + MathF.Abs(r)); - var dg = Math.Sign(other.g) * MathF.Log(1 + MathF.Abs(other.g)) - Math.Sign(g) * MathF.Log(1 + MathF.Abs(g)); - var db = Math.Sign(other.b) * MathF.Log(1 + MathF.Abs(other.b)) - Math.Sign(b) * MathF.Log(1 + MathF.Abs(b)); - return MathF.Sqrt((dr * dr) + (dg * dg) + (db * db)); - } - - internal float CalcDist(ColorRgbFloat other) - { - var dr = other.r - r; - var dg = other.g - g; - var db = other.b - b; - return MathF.Sqrt((dr * dr) + (dg * dg) + (db * db)); - } - - internal void ClampToPositive() - { - if (r < 0) r = 0; - if (g < 0) g = 0; - if (b < 0) b = 0; - } - - internal void ClampToHalf() - { - if (r < Half.MinValue) r = Half.MinValue; - else if (g > Half.MaxValue) g = Half.MaxValue; - if (b < Half.MinValue) b = Half.MinValue; - else if (r > Half.MaxValue) r = Half.MaxValue; - if (g < Half.MinValue) g = Half.MinValue; - else if (b > Half.MaxValue) b = Half.MaxValue; - } - } - - internal struct ColorYCbCr - { - public float y; - public float cb; - public float cr; - - public ColorYCbCr(float y, float cb, float cr) - { - this.y = y; - this.cb = cb; - this.cr = cr; - } - - internal ColorYCbCr(ColorRgb24 rgb) - { - var fr = (float)rgb.r / 255; - var fg = (float)rgb.g / 255; - var fb = (float)rgb.b / 255; - - y = 0.2989f * fr + 0.5866f * fg + 0.1145f * fb; - cb = -0.1687f * fr - 0.3313f * fg + 0.5000f * fb; - cr = 0.5000f * fr - 0.4184f * fg - 0.0816f * fb; - } - - internal ColorYCbCr(ColorRgbaFloat rgb) - { - var fr = rgb.r; - var fg = rgb.g; - var fb = rgb.b; - - y = 0.2989f * fr + 0.5866f * fg + 0.1145f * fb; - cb = -0.1687f * fr - 0.3313f * fg + 0.5000f * fb; - cr = 0.5000f * fr - 0.4184f * fg - 0.0816f * fb; - } - - internal ColorYCbCr(ColorRgbFloat rgb) - { - var fr = rgb.r; - var fg = rgb.g; - var fb = rgb.b; - - y = 0.2989f * fr + 0.5866f * fg + 0.1145f * fb; - cb = -0.1687f * fr - 0.3313f * fg + 0.5000f * fb; - cr = 0.5000f * fr - 0.4184f * fg - 0.0816f * fb; - } - - internal ColorYCbCr(ColorRgb565 rgb) - { - var fr = (float)rgb.R / 255; - var fg = (float)rgb.G / 255; - var fb = (float)rgb.B / 255; - - y = 0.2989f * fr + 0.5866f * fg + 0.1145f * fb; - cb = -0.1687f * fr - 0.3313f * fg + 0.5000f * fb; - cr = 0.5000f * fr - 0.4184f * fg - 0.0816f * fb; - } - - public ColorYCbCr(ColorRgba32 rgba) - { - var fr = (float)rgba.r / 255; - var fg = (float)rgba.g / 255; - var fb = (float)rgba.b / 255; - - y = 0.2989f * fr + 0.5866f * fg + 0.1145f * fb; - cb = -0.1687f * fr - 0.3313f * fg + 0.5000f * fb; - cr = 0.5000f * fr - 0.4184f * fg - 0.0816f * fb; - } - - public ColorYCbCr(Vector3 vec) - { - var fr = (float)vec.X; - var fg = (float)vec.Y; - var fb = (float)vec.Z; - - y = 0.2989f * fr + 0.5866f * fg + 0.1145f * fb; - cb = -0.1687f * fr - 0.3313f * fg + 0.5000f * fb; - cr = 0.5000f * fr - 0.4184f * fg - 0.0816f * fb; - } - - public ColorRgb565 ToColorRgb565() - { - var r = Math.Max(0.0f, Math.Min(1.0f, (float)(y + 0.0000 * cb + 1.4022 * cr))); - var g = Math.Max(0.0f, Math.Min(1.0f, (float)(y - 0.3456 * cb - 0.7145 * cr))); - var b = Math.Max(0.0f, Math.Min(1.0f, (float)(y + 1.7710 * cb + 0.0000 * cr))); - - return new ColorRgb565((byte)(r * 255), (byte)(g * 255), (byte)(b * 255)); - } - - public ColorRgba32 ToColorRgba32() - { - var r = Math.Max(0.0f, Math.Min(1.0f, (float)(y + 0.0000 * cb + 1.4022 * cr))); - var g = Math.Max(0.0f, Math.Min(1.0f, (float)(y - 0.3456 * cb - 0.7145 * cr))); - var b = Math.Max(0.0f, Math.Min(1.0f, (float)(y + 1.7710 * cb + 0.0000 * cr))); - - return new ColorRgba32((byte)(r * 255), (byte)(g * 255), (byte)(b * 255), 255); - } - - public override string ToString() - { - var r = Math.Max(0.0f, Math.Min(1.0f, (float)(y + 0.0000 * cb + 1.4022 * cr))); - var g = Math.Max(0.0f, Math.Min(1.0f, (float)(y - 0.3456 * cb - 0.7145 * cr))); - var b = Math.Max(0.0f, Math.Min(1.0f, (float)(y + 1.7710 * cb + 0.0000 * cr))); - - return $"r : {r * 255} g : {g * 255} b : {b * 255}"; - } - - public float CalcDistWeighted(ColorYCbCr other, float yWeight = 4) - { - var dy = (y - other.y) * (y - other.y) * yWeight; - var dcb = (cb - other.cb) * (cb - other.cb); - var dcr = (cr - other.cr) * (cr - other.cr); - - return MathF.Sqrt(dy + dcb + dcr); - } - - public static ColorYCbCr operator +(ColorYCbCr left, ColorYCbCr right) - { - return new ColorYCbCr( - left.y + right.y, - left.cb + right.cb, - left.cr + right.cr); - } - - public static ColorYCbCr operator /(ColorYCbCr left, float right) - { - return new ColorYCbCr( - left.y / right, - left.cb / right, - left.cr / right); - } - } - - internal struct ColorRgb555 : IEquatable - { - public bool Equals(ColorRgb555 other) - { - return data == other.data; - } - - public override bool Equals(object obj) - { - return obj is ColorRgb555 other && Equals(other); - } - - public override int GetHashCode() - { - return data.GetHashCode(); - } - - public static bool operator ==(ColorRgb555 left, ColorRgb555 right) - { - return left.Equals(right); - } - - public static bool operator !=(ColorRgb555 left, ColorRgb555 right) - { - return !left.Equals(right); - } - - private const ushort ModeMask = 0b1_00000_00000_00000; - private const int ModeShift = 15; - private const ushort RedMask = 0b0_11111_00000_00000; - private const int RedShift = 10; - private const ushort GreenMask = 0b0_00000_11111_00000; - private const int GreenShift = 5; - private const ushort BlueMask = 0b0_00000_00000_11111; - - public ushort data; - - public byte Mode - { - readonly get - { - var mode = (data & ModeMask) >> ModeShift; - return (byte)mode; - } - set - { - var mode = value; - data = (ushort)(data & ~ModeMask); - data = (ushort)(data | (mode << ModeShift)); - } - } - - public byte R - { - readonly get - { - var r5 = (data & RedMask) >> RedShift; - return (byte)((r5 << 3) | (r5 >> 2)); - } - set - { - var r5 = value >> 3; - data = (ushort)(data & ~RedMask); - data = (ushort)(data | (r5 << RedShift)); - } - } - - public byte G - { - readonly get - { - var g5 = (data & GreenMask) >> GreenShift; - return (byte)((g5 << 3) | (g5 >> 2)); - } - set - { - var g5 = value >> 3; - data = (ushort)(data & ~GreenMask); - data = (ushort)(data | (g5 << GreenShift)); - } - } - - public byte B - { - readonly get - { - var b5 = data & BlueMask; - return (byte)((b5 << 3) | (b5 >> 2)); - } - set - { - var b5 = value >> 3; - data = (ushort)(data & ~BlueMask); - data = (ushort)(data | b5); - } - } - - public int RawR - { - readonly get => (data & RedMask) >> RedShift; - set - { - if (value > 31) value = 31; - if (value < 0) value = 0; - data = (ushort)(data & ~RedMask); - data = (ushort)(data | (value << RedShift)); - } - } - - public int RawG - { - readonly get => (data & GreenMask) >> GreenShift; - set - { - if (value > 31) value = 31; - if (value < 0) value = 0; - data = (ushort)(data & ~GreenMask); - data = (ushort)(data | (value << GreenShift)); - } - } - - public int RawB - { - readonly get => data & BlueMask; - set - { - if (value > 31) value = 31; - if (value < 0) value = 0; - data = (ushort)(data & ~BlueMask); - data = (ushort)(data | value); - } - } - - public ColorRgb555(byte r, byte g, byte b) - { - data = 0; - R = r; - G = g; - B = b; - } - - public ColorRgb555(Vector3 colorVector) - { - data = 0; - R = ByteHelper.ClampToByte(colorVector.X * 255); - G = ByteHelper.ClampToByte(colorVector.Y * 255); - B = ByteHelper.ClampToByte(colorVector.Z * 255); - } - - public ColorRgb555(ColorRgb24 color) - { - data = 0; - R = color.r; - G = color.g; - B = color.b; - } - - public readonly ColorRgb24 ToColorRgb24() - { - return new ColorRgb24(R, G, B); - } - - public override string ToString() - { - return $"r : {R} g : {G} b : {B}"; - } - - public ColorRgba32 ToColorRgba32() - { - return new ColorRgba32(R, G, B, 255); - } - } - - internal struct ColorRgb565 : IEquatable - { - public bool Equals(ColorRgb565 other) - { - return data == other.data; - } - - public override bool Equals(object obj) - { - return obj is ColorRgb565 other && Equals(other); - } - - public override int GetHashCode() - { - return data.GetHashCode(); - } - - public static bool operator ==(ColorRgb565 left, ColorRgb565 right) - { - return left.Equals(right); - } - - public static bool operator !=(ColorRgb565 left, ColorRgb565 right) - { - return !left.Equals(right); - } - - private const ushort RedMask = 0b11111_000000_00000; - private const int RedShift = 11; - private const ushort GreenMask = 0b00000_111111_00000; - private const int GreenShift = 5; - private const ushort BlueMask = 0b00000_000000_11111; - - public ushort data; - - public byte R - { - readonly get - { - var r5 = (data & RedMask) >> RedShift; - return (byte)((r5 << 3) | (r5 >> 2)); - } - set - { - var r5 = value >> 3; - data = (ushort)(data & ~RedMask); - data = (ushort)(data | (r5 << RedShift)); - } - } - - public byte G - { - readonly get - { - var g6 = (data & GreenMask) >> GreenShift; - return (byte)((g6 << 2) | (g6 >> 4)); - } - set - { - var g6 = value >> 2; - data = (ushort)(data & ~GreenMask); - data = (ushort)(data | (g6 << GreenShift)); - } - } - - public byte B - { - readonly get - { - var b5 = data & BlueMask; - return (byte)((b5 << 3) | (b5 >> 2)); - } - set - { - var b5 = value >> 3; - data = (ushort)(data & ~BlueMask); - data = (ushort)(data | b5); - } - } - - public int RawR - { - readonly get { return (data & RedMask) >> RedShift; } - set - { - if (value > 31) value = 31; - if (value < 0) value = 0; - data = (ushort)(data & ~RedMask); - data = (ushort)(data | (value << RedShift)); - } - } - - public int RawG - { - readonly get { return (data & GreenMask) >> GreenShift; } - set - { - if (value > 63) value = 63; - if (value < 0) value = 0; - data = (ushort)(data & ~GreenMask); - data = (ushort)(data | (value << GreenShift)); - } - } - - public int RawB - { - readonly get { return data & BlueMask; } - set - { - if (value > 31) value = 31; - if (value < 0) value = 0; - data = (ushort)(data & ~BlueMask); - data = (ushort)(data | value); - } - } - - public ColorRgb565(byte r, byte g, byte b) - { - data = 0; - R = r; - G = g; - B = b; - } - - public ColorRgb565(Vector3 colorVector) - { - data = 0; - R = ByteHelper.ClampToByte(colorVector.X * 255); - G = ByteHelper.ClampToByte(colorVector.Y * 255); - B = ByteHelper.ClampToByte(colorVector.Z * 255); - } - - public ColorRgb565(ColorRgb24 color) - { - data = 0; - R = color.r; - G = color.g; - B = color.b; - } - - public readonly ColorRgb24 ToColorRgb24() - { - return new ColorRgb24(R, G, B); - } - - public override string ToString() - { - return $"r : {R} g : {G} b : {B}"; - } - - public ColorRgba32 ToColorRgba32() - { - return new ColorRgba32(R, G, B, 255); - } - } - - internal struct ColorRgb24 : IEquatable - { - public byte r, g, b; - - public ColorRgb24(byte r, byte g, byte b) - { - this.r = r; - this.g = g; - this.b = b; - } - - public ColorRgb24(ColorRgb565 color) - { - this.r = color.R; - this.g = color.G; - this.b = color.B; - } - - public ColorRgb24(ColorRgba32 color) - { - this.r = color.r; - this.g = color.g; - this.b = color.b; - } - - public bool Equals(ColorRgb24 other) - { - return r == other.r && g == other.g && b == other.b; - } - - public override bool Equals(object obj) - { - return obj is ColorRgb24 other && Equals(other); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = r.GetHashCode(); - hashCode = (hashCode * 397) ^ g.GetHashCode(); - hashCode = (hashCode * 397) ^ b.GetHashCode(); - return hashCode; - } - } - - public static bool operator ==(ColorRgb24 left, ColorRgb24 right) - { - return left.Equals(right); - } - - public static bool operator !=(ColorRgb24 left, ColorRgb24 right) - { - return !left.Equals(right); - } - - public static ColorRgb24 operator +(ColorRgb24 left, ColorRgb24 right) - { - return new ColorRgb24( - ByteHelper.ClampToByte(left.r + right.r), - ByteHelper.ClampToByte(left.g + right.g), - ByteHelper.ClampToByte(left.b + right.b)); - } - - public static ColorRgb24 operator -(ColorRgb24 left, ColorRgb24 right) - { - return new ColorRgb24( - ByteHelper.ClampToByte(left.r - right.r), - ByteHelper.ClampToByte(left.g - right.g), - ByteHelper.ClampToByte(left.b - right.b)); - } - - public static ColorRgb24 operator /(ColorRgb24 left, double right) - { - return new ColorRgb24( - ByteHelper.ClampToByte((int)(left.r / right)), - ByteHelper.ClampToByte((int)(left.g / right)), - ByteHelper.ClampToByte((int)(left.b / right)) - ); - } - - public static ColorRgb24 operator *(ColorRgb24 left, double right) - { - return new ColorRgb24( - ByteHelper.ClampToByte((int)(left.r * right)), - ByteHelper.ClampToByte((int)(left.g * right)), - ByteHelper.ClampToByte((int)(left.b * right)) - ); - } - - public override string ToString() - { - return $"r : {r} g : {g} b : {b}"; - } - } - - internal struct ColorYCbCrAlpha - { - public float y; - public float cb; - public float cr; - public float alpha; - - public ColorYCbCrAlpha(float y, float cb, float cr, float alpha) - { - this.y = y; - this.cb = cb; - this.cr = cr; - this.alpha = alpha; - } - - public ColorYCbCrAlpha(ColorRgb24 rgb) - { - var fr = (float)rgb.r / 255; - var fg = (float)rgb.g / 255; - var fb = (float)rgb.b / 255; - - y = 0.2989f * fr + 0.5866f * fg + 0.1145f * fb; - cb = -0.1687f * fr - 0.3313f * fg + 0.5000f * fb; - cr = 0.5000f * fr - 0.4184f * fg - 0.0816f * fb; - alpha = 1; - } - - public ColorYCbCrAlpha(ColorRgb565 rgb) - { - var fr = (float)rgb.R / 255; - var fg = (float)rgb.G / 255; - var fb = (float)rgb.B / 255; - - y = 0.2989f * fr + 0.5866f * fg + 0.1145f * fb; - cb = -0.1687f * fr - 0.3313f * fg + 0.5000f * fb; - cr = 0.5000f * fr - 0.4184f * fg - 0.0816f * fb; - alpha = 1; - } - - public ColorYCbCrAlpha(ColorRgba32 rgba) - { - var fr = (float)rgba.r / 255; - var fg = (float)rgba.g / 255; - var fb = (float)rgba.b / 255; - - y = 0.2989f * fr + 0.5866f * fg + 0.1145f * fb; - cb = -0.1687f * fr - 0.3313f * fg + 0.5000f * fb; - cr = 0.5000f * fr - 0.4184f * fg - 0.0816f * fb; - alpha = rgba.a / 255f; - } - - public ColorYCbCrAlpha(ColorRgbaFloat rgba) - { - var fr = rgba.r; - var fg = rgba.g; - var fb = rgba.b; - - y = 0.2989f * fr + 0.5866f * fg + 0.1145f * fb; - cb = -0.1687f * fr - 0.3313f * fg + 0.5000f * fb; - cr = 0.5000f * fr - 0.4184f * fg - 0.0816f * fb; - alpha = rgba.a; - } - - - public ColorRgb565 ToColorRgb565() - { - var r = Math.Max(0.0f, Math.Min(1.0f, (float)(y + 0.0000 * cb + 1.4022 * cr))); - var g = Math.Max(0.0f, Math.Min(1.0f, (float)(y - 0.3456 * cb - 0.7145 * cr))); - var b = Math.Max(0.0f, Math.Min(1.0f, (float)(y + 1.7710 * cb + 0.0000 * cr))); - - return new ColorRgb565((byte)(r * 255), (byte)(g * 255), (byte)(b * 255)); - } - - public override string ToString() - { - var r = Math.Max(0.0f, Math.Min(1.0f, (float)(y + 0.0000 * cb + 1.4022 * cr))); - var g = Math.Max(0.0f, Math.Min(1.0f, (float)(y - 0.3456 * cb - 0.7145 * cr))); - var b = Math.Max(0.0f, Math.Min(1.0f, (float)(y + 1.7710 * cb + 0.0000 * cr))); - - return $"r : {r * 255} g : {g * 255} b : {b * 255}"; - } - - public float CalcDistWeighted(ColorYCbCrAlpha other, float yWeight = 4, float aWeight = 1) - { - var dy = (y - other.y) * (y - other.y) * yWeight; - var dcb = (cb - other.cb) * (cb - other.cb); - var dcr = (cr - other.cr) * (cr - other.cr); - var da = (alpha - other.alpha) * (alpha - other.alpha) * aWeight; - - return MathF.Sqrt(dy + dcb + dcr + da); - } - - public static ColorYCbCrAlpha operator +(ColorYCbCrAlpha left, ColorYCbCrAlpha right) - { - return new ColorYCbCrAlpha( - left.y + right.y, - left.cb + right.cb, - left.cr + right.cr, - left.alpha + right.alpha); - } - - public static ColorYCbCrAlpha operator /(ColorYCbCrAlpha left, float right) - { - return new ColorYCbCrAlpha( - left.y / right, - left.cb / right, - left.cr / right, - left.alpha / right); - } - } - - internal struct ColorXyz - { - public float x; - public float y; - public float z; - - public ColorXyz(float x, float y, float z) - { - this.x = x; - this.y = y; - this.z = z; - } - - public ColorXyz(ColorRgb24 color) - { - this = ColorToXyz(color); - } - - public ColorXyz(ColorRgbFloat color) - { - this = ColorToXyz(color); - } - - public ColorRgbFloat ToColorRgbFloat() - { - return new ColorRgbFloat( - 3.2404542f * x - 1.5371385f * y - 0.4985314f * z, - -0.9692660f * x + 1.8760108f * y + 0.0415560f * z, - 0.0556434f * x - 0.2040259f * y + 1.0572252f * z - ); - } - - public static ColorXyz ColorToXyz(ColorRgb24 color) - { - var r = PivotRgb(color.r / 255.0f); - var g = PivotRgb(color.g / 255.0f); - var b = PivotRgb(color.b / 255.0f); - - // Observer. = 2°, Illuminant = D65 - return new ColorXyz(r * 0.4124f + g * 0.3576f + b * 0.1805f, r * 0.2126f + g * 0.7152f + b * 0.0722f, r * 0.0193f + g * 0.1192f + b * 0.9505f); - } - - public static ColorXyz ColorToXyz(ColorRgbFloat color) - { - var r = PivotRgb(color.r); - var g = PivotRgb(color.g); - var b = PivotRgb(color.b); - - // Observer. = 2°, Illuminant = D65 - return new ColorXyz(r * 0.4124f + g * 0.3576f + b * 0.1805f, r * 0.2126f + g * 0.7152f + b * 0.0722f, r * 0.0193f + g * 0.1192f + b * 0.9505f); - } - - private static float PivotRgb(float n) - { - return (n > 0.04045f ? MathF.Pow((n + 0.055f) / 1.055f, 2.4f) : n / 12.92f) * 100; - } - } - - internal struct ColorLab - { - public float l; - public float a; - public float b; - - public ColorLab(float l, float a, float b) - { - this.l = l; - this.a = a; - this.b = b; - } - - public ColorLab(ColorRgb24 color) - { - this = ColorToLab(color); - } - - public ColorLab(ColorRgba32 color) - { - this = ColorToLab(new ColorRgb24(color.r, color.g, color.b)); - } - - public ColorLab(ColorRgbFloat color) - { - this = XyzToLab(new ColorXyz(color)); - } - - public static ColorLab ColorToLab(ColorRgb24 color) - { - var xyz = new ColorXyz(color); - return XyzToLab(xyz); - } - - - public static ColorLab XyzToLab(ColorXyz xyz) - { - var refX = 95.047f; // Observer= 2°, Illuminant= D65 - var refY = 100.000f; - var refZ = 108.883f; - - var x = PivotXyz(xyz.x / refX); - var y = PivotXyz(xyz.y / refY); - var z = PivotXyz(xyz.z / refZ); - - return new ColorLab(116 * y - 16, 500 * (x - y), 200 * (y - z)); - } - - private static float PivotXyz(float n) - { - var i = MathF.Cbrt(n); - return n > 0.008856f ? i : 7.787f * n + 16 / 116f; - } - } - - internal struct ColorRgbe : IEquatable - { - public byte r; - public byte g; - public byte b; - public byte e; - - public ColorRgbe(byte r, byte g, byte b, byte e) - { - this.r = r; - this.g = g; - this.b = b; - this.e = e; - } - - public ColorRgbe(ColorRgbFloat color) - { - var max = MathF.Max(color.b, MathF.Max(color.g, color.r)); - if (max <= 1e-32f) - { - r = g = b = e = 0; - } - else - { - MathHelper.FrExp(max, out var exponent); - var scale = MathHelper.LdExp(1f, -exponent + 8); - r = (byte)(scale * color.r); - g = (byte)(scale * color.g); - b = (byte)(scale * color.b); - e = (byte)(exponent + 128); - } - } - - public ColorRgbFloat ToColorRgbFloat(float exposure = 1.0f) - { - if (e == 0) - { - return new ColorRgbFloat(0, 0, 0); - } - else - { - var fexp = MathHelper.LdExp(1f, e - (128 + 8)) / exposure; - - return new ColorRgbFloat( - (r + 0.5f) * fexp, - (g + 0.5f) * fexp, - (b + 0.5f) * fexp - ); - } - } - - - public bool Equals(ColorRgbe other) - { - return r == other.r && g == other.g && b == other.b && e == other.e; - } - - public override bool Equals(object obj) - { - return obj is ColorRgbe other && Equals(other); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = r.GetHashCode(); - hashCode = (hashCode * 397) ^ g.GetHashCode(); - hashCode = (hashCode * 397) ^ b.GetHashCode(); - hashCode = (hashCode * 397) ^ e.GetHashCode(); - return hashCode; - } - } - - public static bool operator ==(ColorRgbe left, ColorRgbe right) - { - return left.Equals(right); - } - - public static bool operator !=(ColorRgbe left, ColorRgbe right) - { - return !left.Equals(right); - } - - public override string ToString() - { - return $"{nameof(r)}: {r}, {nameof(g)}: {g}, {nameof(b)}: {b}, {nameof(e)}: {e}"; - } - } -} diff --git a/BCnEnc.Net/Shared/Colors/ColorBitConversionHelpers.cs b/BCnEnc.Net/Shared/Colors/ColorBitConversionHelpers.cs new file mode 100644 index 0000000..4ec485c --- /dev/null +++ b/BCnEnc.Net/Shared/Colors/ColorBitConversionHelpers.cs @@ -0,0 +1,404 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace BCnEncoder.Shared.Colors +{ + /// + /// Provides helper methods for converting between different color formats. + /// + internal static class ColorBitConversionHelpers + { + #region Helper Methods + /// + /// Saturates a float value between 0 and 1 + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Saturate(float v) + { + if (float.IsNaN(v)) + { + return 0; + } + return Math.Min(Math.Max(v, 0), 1); + } + + /// + /// Saturates a float value between -1 and 1 + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float SaturateSigned(float v) + { + if (float.IsNaN(v)) + { + return 0; + } + return Math.Min(Math.Max(v, -1), 1); + } + #endregion + + /// + /// Converts a signed normalized integer to its floating-point value in the range [-1, 1]. + /// + /// The signed normalized integer value. + /// The number of bits used to represent the signed normalized integer. + /// The floating-point value of the signed normalized integer. + /// + /// This function is the inverse of . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float SnormToFloat(int value, int bits) + { + // (1 << (bits - 1)) == MathF.Pow(2, bits - 1) + float mult = 1.0f / ((1 << (bits - 1)) - 1); + return MathF.Max(value * mult, -1.0f); + } + + /// + /// Converts a floating-point value in the range [-1, 1] to its signed normalized integer representation. + /// + /// The floating-point value to convert. + /// The number of bits used to represent the signed normalized integer. + /// The signed normalized integer representation of the floating-point value. + /// + /// This function is the inverse of . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FloatToSnorm(float value, int bits) + { + // (1 << (bits - 1)) == MathF.Pow(2, bits - 1) + float mult = ((1 << (bits - 1)) - 1); + return (int)MathF.Round(SaturateSigned(value) * mult); + } + + + /// + /// Converts an unsigned normalized integer to its floating-point value in the range [0, 1]. + /// + /// The unsigned normalized integer value. + /// The number of bits used to represent the unsigned normalized integer. + /// The floating-point value of the unsigned normalized integer. + /// + /// This function is the inverse of . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float UnormToFloat(uint value, int bits) + { + return value * (1.0f / (MathF.Pow(2, bits) - 1)); + } + + /// + /// Converts a floating-point value in the range [0, 1] to its unsigned normalized integer representation. + /// + /// The floating-point value to convert. + /// The number of bits used to represent the unsigned normalized integer. + /// The unsigned normalized integer representation of the floating-point value. + /// + /// This function is the inverse of . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint FloatToUnorm(float value, int bits) + { + return (uint)(Saturate(value) * (MathF.Pow(2, bits) - 1) + 0.5f); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Unorm1ToFloat(uint value) + { + // We don't need to do any scaling here + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte FloatToUnorm1(float value) + { + // Only do saturation and rounding + return (byte)(Saturate(value) + 0.5f); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Unorm2ToFloat(uint value) + { + // (1 << 2) == MathF.Pow(2, 2) + const float mult = 1.0f / ((1 << 2) - 1); + return value * mult; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte FloatToUnorm2(float value) + { + // (1 << 2) == MathF.Pow(2, 2) + const float mult = ((1 << 2) - 1); + return (byte)(Saturate(value) * mult + 0.5f); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Unorm4ToFloat(uint value) + { + // (1 << 4) == MathF.Pow(2, 4) + const float mult = 1.0f / ((1 << 4) - 1); + return value * mult; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte FloatToUnorm4(float value) + { + // (1 << 4) == MathF.Pow(2, 4) + const float mult = ((1 << 4) - 1); + return (byte)(Saturate(value) * mult + 0.5f); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Unorm5ToFloat(uint value) + { + // (1 << 5) == MathF.Pow(2, 5) + const float mult = 1.0f / ((1 << 5) - 1); + return value * mult; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte FloatToUnorm5(float value) + { + // (1 << 5) == MathF.Pow(2, 5) + const float mult = ((1 << 5) - 1); + return (byte)(Saturate(value) * mult + 0.5f); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Unorm6ToFloat(uint value) + { + // (1 << 6) == MathF.Pow(2, 6) + const float mult = 1.0f / ((1 << 6) - 1); + return value * mult; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte FloatToUnorm6(float value) + { + // (1 << 6) == MathF.Pow(2, 6) + const float mult = ((1 << 6) - 1); + return (byte)(Saturate(value) * mult + 0.5f); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Unorm8ToFloat(uint value) + { + // (1 << 8) == MathF.Pow(2, 8) + const float mult = 1.0f / ((1 << 8) - 1); + return value * mult; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte FloatToUnorm8(float value) + { + // (1 << 8) == MathF.Pow(2, 8) + const float mult = ((1 << 8) - 1); + return (byte)(Saturate(value) * mult + 0.5f); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Unorm10ToFloat(uint value) + { + // (1 << 10) == MathF.Pow(2, 10) + const float mult = 1.0f / ((1 << 10) - 1); + return value * mult; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort FloatToUnorm10(float value) + { + // (1 << 10) == MathF.Pow(2, 10) + const float mult = ((1 << 10) - 1); + return (ushort)(Saturate(value) * mult + 0.5f); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Unorm16ToFloat(uint value) + { + // (1 << 16) == MathF.Pow(2, 16) + const float mult = 1.0f / ((1 << 16) - 1); + return value * mult; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort FloatToUnorm16(float value) + { + // (1 << 16) == MathF.Pow(2, 16) + const float mult = ((1 << 16) - 1); + return (ushort)(Saturate(value) * mult + 0.5f); + } + + #region Special Floating Point Formats + + /// + /// Packs a float value into an 11-bit floating point representation (5 exponent, 6 mantissa). + /// + /// + /// Conforms to the packed 11-bit floating point format as defined in the Khronos Data Format Specification 1.4. + /// See: + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint FloatToFloat11(float value) + { + // Handle special cases + if (float.IsNaN(value)) return 0x7C0 | 1; // Exponent all 1s, mantissa non-zero + if (value <= 0) return 0; // Map negative values to zero + if (float.IsPositiveInfinity(value)) return 0x7C0; // Exponent all 1s, mantissa 0 + + // Extract components from the floating point value + uint floatBits = (uint)BitConverter.SingleToInt32Bits(value); + int biasedExponent = (int)((floatBits >> 23) & 0xFF); + int unbiasedExponent = biasedExponent - 127; // Remove IEEE-754 bias + uint mantissa = floatBits & 0x7FFFFF; + + // Case 1: Denormalized values (exponent < -14) + if (unbiasedExponent < -14) + { + // Special case for very small values that can't be normalized + // For extremely small values, directly calculate the mantissa bits + float e = -14.0f; // Denorm exponent + float m = value / (float)Math.Pow(2, e); // Scale to get mantissa + uint bits = (uint)Math.Round(m * 64.0f); // Scale to 6-bit mantissa range + return Math.Max(1, Math.Min(bits, 0x3F)); // Ensure within valid range and non-zero + } + + // Case 2: Normalized values within representable range + if (unbiasedExponent <= 15) + { + // Adjust exponent bias (from -127 to -15) + uint biased11Exponent = (uint)(unbiasedExponent + 15); + + // Extract the 6 most significant bits of mantissa + uint bits11Mantissa = mantissa >> 17; + + // Combine exponent and mantissa + return (biased11Exponent << 6) | bits11Mantissa; + } + + // Case 3: Values too large (exponent > 15) become infinity + return 0x7C0; // Infinity representation in float11 + } + + /// + /// Unpacks an 11-bit floating point value to a standard 32-bit float. + /// + /// + /// Conforms to the packed 11-bit floating point format as defined in the Khronos Data Format Specification 1.4. + /// See: + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Float11ToFloat(uint packedValue) + { + // Extract components + uint mantissa = packedValue & 0x3F; // 6-bit mantissa + uint exponent = (packedValue >> 6) & 0x1F; // 5-bit exponent + + // Handle special cases + if (exponent == 0) + { + if (mantissa == 0) return 0.0f; // Zero + + // Denormalized value: 2^-14 * (mantissa/64) + float value = (float)(Math.Pow(2, -14) * (mantissa / 64.0)); + return value; + } + else if (exponent == 31) // 5 bits all set + { + if (mantissa == 0) return float.PositiveInfinity; // Infinity + return float.NaN; // NaN + } + + // Normalized value: 2^(exponent-15) * (1 + mantissa/64) + // Cast to int to avoid unsigned integer overflow when exponent < 15 + int expValue = (int)exponent - 15; + float result = (float)(Math.Pow(2, expValue) * (1.0 + mantissa / 64.0)); + return result; + } + + /// + /// Packs a float value into a 10-bit floating point representation (5 exponent, 5 mantissa). + /// + /// + /// Conforms to the packed 10-bit floating point format as defined in the Khronos Data Format Specification 1.4. + /// See: + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint FloatToFloat10(float value) + { + // Handle special cases + if (float.IsNaN(value)) return 0x3E0 | 1; // Exponent all 1s, mantissa non-zero + if (value <= 0) return 0; // Map negative values to zero + if (float.IsPositiveInfinity(value)) return 0x3E0; // Exponent all 1s, mantissa 0 + + // Extract components from the floating point value + uint floatBits = (uint)BitConverter.SingleToInt32Bits(value); + int biasedExponent = (int)((floatBits >> 23) & 0xFF); + int unbiasedExponent = biasedExponent - 127; // Remove IEEE-754 bias + uint mantissa = floatBits & 0x7FFFFF; + + // Case 1: Denormalized values (exponent < -14) + if (unbiasedExponent < -14) + { + // Special case for very small values that can't be normalized + // Directly calculate the mantissa bits + float e = -14.0f; // Denorm exponent + float m = value / (float)Math.Pow(2, e); // Scale to get mantissa + uint bits = (uint)Math.Round(m * 32.0f); // Scale to 5-bit mantissa range + return Math.Max(1, Math.Min(bits, 0x1F)); // Ensure within valid range and non-zero + } + + // Case 2: Normalized values within representable range + if (unbiasedExponent <= 15) + { + // Adjust exponent bias (from -127 to -15) + uint biased10Exponent = (uint)(unbiasedExponent + 15); + + // Extract the 5 most significant bits of mantissa + uint bits10Mantissa = mantissa >> 18; + + // Combine exponent and mantissa + return (biased10Exponent << 5) | bits10Mantissa; + } + + // Case 3: Values too large (exponent > 15) become infinity + return 0x3E0; // Infinity representation in float10 + } + + /// + /// Unpacks a 10-bit floating point value to a standard 32-bit float. + /// + /// + /// Conforms to the packed 10-bit floating point format as defined in the Khronos Data Format Specification 1.4. + /// See: + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Float10ToFloat(uint packedValue) + { + // Extract components + uint mantissa = packedValue & 0x1F; // 5-bit mantissa + uint exponent = (packedValue >> 5) & 0x1F; // 5-bit exponent + + // Handle special cases + if (exponent == 0) + { + if (mantissa == 0) return 0.0f; // Zero + + // Denormalized value: 2^-14 * (mantissa/32) + float value = (float)(Math.Pow(2, -14) * (mantissa / 32.0)); + return value; + } + else if (exponent == 31) // 5 bits all set + { + if (mantissa == 0) return float.PositiveInfinity; + return float.NaN; + } + + // Normalized value: 2^(exponent-15) * (1 + mantissa/32) + // Cast to int to avoid unsigned integer underflow when exponent < 15 + int expValue = (int)exponent - 15; + float result = (float)(Math.Pow(2, expValue) * (1.0 + mantissa / 32.0)); + return result; + } + #endregion + } +} diff --git a/BCnEnc.Net/Shared/Colors/ColorSpace.cs b/BCnEnc.Net/Shared/Colors/ColorSpace.cs new file mode 100644 index 0000000..5a27679 --- /dev/null +++ b/BCnEnc.Net/Shared/Colors/ColorSpace.cs @@ -0,0 +1,477 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using BCnEncoder.Shared.Vectorized; + +namespace BCnEncoder.Shared.Colors; + +public static class ColorSpace +{ + /// + /// Converts sRGB to linear RGB. + /// + /// + /// The sRGB -> linear RGB conversion is based on the sRGB spec: + /// http://www.w3.org/Graphics/Color/sRGB.html + /// + /// A sRGB float value. + /// The linear RGB value. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float SrgbToLrgb(float srgb) + { + float lrgb; + + if (srgb <= 0.04045f) + { + lrgb = srgb / 12.92f; + } + else + { + lrgb = MathF.Pow((srgb + 0.055f) / 1.055f, 2.4f); + } + + return lrgb; + } + + /// + /// Converts linear RGB to sRGB. + /// + /// + /// The linear RGB -> sRGB conversion is based on the sRGB spec: + /// http://www.w3.org/Graphics/Color/sRGB.html + /// + /// A linear RGB value. + /// The sRGB float value. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float LrgbToSrgb(float lrgb) + { + float srgb; + + if (lrgb <= 0.0031308f) + { + srgb = lrgb * 12.92f; + } + else + { + srgb = 1.055f * MathF.Pow(lrgb, 1 / 2.4f) - 0.055f; + } + + return srgb; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ColorRgbaFloat SrgbToLrgb(ColorRgbaFloat lrgb) + { + return new ColorRgbaFloat( + SrgbToLrgb(lrgb.r), + SrgbToLrgb(lrgb.g), + SrgbToLrgb(lrgb.b), + lrgb.a); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ColorRgbaFloat LrgbToSrgb(ColorRgbaFloat lrgb) + { + return new ColorRgbaFloat( + LrgbToSrgb(lrgb.r), + LrgbToSrgb(lrgb.g), + LrgbToSrgb(lrgb.b), + lrgb.a); + } + + + public static void LrgbToYCbCr(float r, float g, float b, out float y, out float cb, out float cr) + { + y = 0.2989f * r + 0.5866f * g + 0.1145f * b; + cb = -0.1687f * r - 0.3313f * g + 0.5000f * b; + cr = 0.5000f * r - 0.4184f * g - 0.0816f * b; + } + + public static void YCbCrToLrgb(float y, float cb, float cr, out float r, out float g, out float b) + { + r = y + 0.0000f * cb + 1.4022f * cr; + g = y - 0.3456f * cb - 0.7145f * cr; + b = y + 1.7710f * cb + 0.0000f * cr; + } + + public static void LrgbToXyz(float r, float g, float b, out float x, out float y, out float z) + { + // Observer. = 2°, Illuminant = D65 + // x = 0.4124f * r + 0.3576f * g + 0.1805f * b; + // y = 0.2126f * r + 0.7152f * g + 0.0722f * b; + // z = 0.0193f * r + 0.1192f * g + 0.9505f * b; + + // Which one??? + + x = 0.5142f * r + 0.3240f * g + 0.1618f * b; + y = 0.2652f * r + 0.6702f * g + 0.0646f * b; + z = 0.0240f * r + 0.1229f * g + 0.8531f * b; + } + + public static void XyzToLrgb(float x, float y, float z, out float r, out float g, out float b) + { + // Observer. = 2°, Illuminant = D65 + // r = 3.2406f * x - 1.5372f * y - 0.4986f * z; + // g = -0.9689f * x + 1.8758f * y + 0.0415f * z; + // b = 0.0557f * x - 0.2040f * y + 1.0570f * z; + + // Which one???? + + r = 2.565f * x - 1.167f * y - 0.398f * z; + g = -1.022f * x + 1.978f * y + 0.044f * z; + b = 0.075f * x - 0.252f * y + 1.177f * z; + } + + public static class Srgb + { + public static Vector4 ToLrgb(Vector4 srgb) + { + // Preserve alpha + float A = srgb.W; + srgb.W = 0f; + + Vector128 cmp128 = Vector128.Create(0.04045f); + Vector128 linearMultiplier128 = Vector128.Create(12.92f); + Vector128 gammaMultiplier128 = Vector128.Create(1.055f); + Vector128 gammaBias128 = Vector128.Create(0.055f); + + Vector128 srgb128 = srgb.AsVector128(); + + // if (srgb <= 0.04045f) + // { + // lrgb = srgb / 12.92f; + // } + Vector128 linearMask = Vector128.LessThanOrEqual(srgb128, cmp128); + Vector128 lrgb128 = srgb128 / linearMultiplier128; + + // else + // { + // lrgb = MathF.Pow((srgb + 0.055f) / 1.055f, 2.4f); + // } + + // Avoid expensive operations if possible + if (!Vector128.EqualsAll(linearMask, Vector128.AllBitsSet)) + { + Vector128 gamma = srgb128 + gammaBias128; + gamma = gamma / gammaMultiplier128; + gamma = MathV.Pow(gamma, Vector128.Create(2.4f)); + + lrgb128 = Vector128.ConditionalSelect(linearMask, lrgb128, gamma); + } + + Vector4 lrgb = lrgb128.AsVector4(); + + // Restore Alpha + lrgb.W = A; + + return lrgb; + } + + public static Vector4 ToSrgb(Vector4 lrgb) + { + // Preserve alpha + float A = lrgb.W; + lrgb.W = 0f; + + Vector128 cmp128 = Vector128.Create(0.0031308f); + Vector128 linearMultiplier128 = Vector128.Create(12.92f); + Vector128 gammaMultiplier128 = Vector128.Create(1.055f); + Vector128 gammaBias128 = Vector128.Create(0.055f); + + Vector128 lrgb128 = lrgb.AsVector128(); + + // if (lrgb <= 0.0031308f) + // { + // srgb = lrgb * 12.92f; + // } + + Vector128 linearMask = Vector128.LessThanOrEqual(lrgb128, cmp128); + Vector128 srgb128 = Vector128.Multiply(lrgb128, linearMultiplier128); + + // else + // { + // srgb = 1.055f * MathF.Pow(lrgb, 1 / 2.4f) - 0.055f; + // } + + // Avoid expensive operations if possible + if (!Vector128.EqualsAll(linearMask, Vector128.AllBitsSet)) + { + Vector128 gamma = MathV.Pow(lrgb128, Vector128.Create(1f / 2.4f)); + gamma = gamma * gammaMultiplier128; + gamma = gamma - gammaBias128; + srgb128 = Vector128.ConditionalSelect(linearMask, srgb128, gamma); + } + + Vector4 srgb = srgb128.AsVector4(); + + // Restore Alpha + srgb.W = A; + + return srgb; + } + } + + public static class CieXyz + { + public static readonly Vector4 WhitePointD65 = new Vector4(0.95047f, 1.0f, 1.08883f, 1.0f); + + public static readonly Matrix4x4 RgbToXyz = new Matrix4x4( + 0.4124564f, 0.2126729f, 0.0193339f, 0f, + 0.3575761f, 0.7151522f, 0.1191920f, 0f, + 0.1804375f, 0.0721750f, 0.9503041f, 0f, + 0f, 0f, 0f, 1f + ); + + public static readonly Matrix4x4 XyzToRgb = new Matrix4x4( + 3.2404542f, -1.5371385f, -0.4985314f, 0f, + -0.9692660f, 1.8759121f, 0.0415550f, 0f, + 0.0556434f, -0.2040259f, 1.0572252f, 0f, + 0f, 0f, 0f, 1f + ); + + public static Vector4 ToXyza(Vector4 rgba) => Vector4.Transform(rgba, RgbToXyz); + + public static Vector4 ToRgb(Vector4 xyza) => Vector4.Transform(xyza, XyzToRgb); + } + + public static class CieLuv + { + private const float Yn = 1.0f; + private const float epsilon = 0.008856f; + private const float kappa = 903.3f; + + private static readonly Vector4 DenomWeights = new Vector4(1f, 15f, 3f, 0f); + private static readonly Vector4 UWeights = new Vector4(4f, 0f, 0f, 0f); + private static readonly Vector4 VWeights = new Vector4(0f, 9f, 0f, 0f); + + private const float uPrime_n = 0.197830f; + private const float vPrime_n = 0.468319f; + + public static Vector4 XyzaToLuva(Vector4 xyzAlpha) + { + float Y = xyzAlpha.Y; + float A = xyzAlpha.W; + + float denom = Vector4.Dot(xyzAlpha, DenomWeights); + float uPrime = denom == 0 ? 0 : Vector4.Dot(xyzAlpha, UWeights) / denom; + float vPrime = denom == 0 ? 0 : Vector4.Dot(xyzAlpha, VWeights) / denom; + + float yr = Y / Yn; + float L = yr > epsilon ? 116f * MathF.Pow(yr, 1f / 3f) - 16f : kappa * yr; + + float u = 13f * L * (uPrime - uPrime_n); + float v = 13f * L * (vPrime - vPrime_n); + + return new Vector4(L, u, v, A); + } + } + + + public static class CieLab + { + public static Vector4 XyzaToLaba(Vector4 xyza) + { + Vector3 xyz = new Vector3(xyza.X, xyza.Y, xyza.Z); + Vector3 wp = new Vector3(CieXyz.WhitePointD65.X, CieXyz.WhitePointD65.Y, CieXyz.WhitePointD65.Z); + + // Normalize by white point + xyz /= wp; + + // Apply the nonlinear function + Vector3 f = new Vector3( + PivotXyz(xyz.X), + PivotXyz(xyz.Y), + PivotXyz(xyz.Z) + ); + + float L = 116f * f.Y - 16f; + float a = 500f * (f.X - f.Y); + float b = 200f * (f.Y - f.Z); + + return new Vector4(L, a, b, xyza.W); // Preserve alpha + } + + private static float PivotXyz(float t) + { + const float epsilon = 0.008856f; // (6/29)^3 + const float kappa = 903.3f; // (29/3)^3 + + return (t > epsilon) ? MathF.Pow(t, 1f / 3f) : (kappa * t + 16f) / 116f; + } + + public static float CieDE2000(Vector4 lab1, Vector4 lab2) + { + // Step 1: Extract components + float L1 = lab1.X, a1 = lab1.Y, b1 = lab1.Z; + float L2 = lab2.X, a2 = lab2.Y, b2 = lab2.Z; + + float avgLp = (L1 + L2) / 2f; + + float C1 = MathF.Sqrt(a1 * a1 + b1 * b1); + float C2 = MathF.Sqrt(a2 * a2 + b2 * b2); + float avgC = (C1 + C2) / 2f; + + float G = 0.5f * (1 - MathF.Sqrt(MathF.Pow(avgC, 7) / (MathF.Pow(avgC, 7) + MathF.Pow(25f, 7)))); + + float a1p = (1 + G) * a1; + float a2p = (1 + G) * a2; + + float C1p = MathF.Sqrt(a1p * a1p + b1 * b1); + float C2p = MathF.Sqrt(a2p * a2p + b2 * b2); + + float avgCp = (C1p + C2p) / 2f; + + float h1p = MathF.Atan2(b1, a1p); + float h2p = MathF.Atan2(b2, a2p); + if (h1p < 0) h1p += 2 * MathF.PI; + if (h2p < 0) h2p += 2 * MathF.PI; + + float deltahp; + if (MathF.Abs(h1p - h2p) > MathF.PI) + { + deltahp = h2p <= h1p ? h2p - h1p + 2 * MathF.PI : h2p - h1p - 2 * MathF.PI; + } + else + { + deltahp = h2p - h1p; + } + + float deltaLp = L2 - L1; + float deltaCp = C2p - C1p; + float deltaHp = 2 * MathF.Sqrt(C1p * C2p) * MathF.Sin(deltahp / 2f); + + float avgHp; + if (MathF.Abs(h1p - h2p) > MathF.PI) + { + avgHp = (h1p + h2p + 2 * MathF.PI) / 2f; + } + else + { + avgHp = (h1p + h2p) / 2f; + } + + float T = 1 + - 0.17f * MathF.Cos(avgHp - MathF.PI / 6) + + 0.24f * MathF.Cos(2 * avgHp) + + 0.32f * MathF.Cos(3 * avgHp + MathF.PI / 30) + - 0.20f * MathF.Cos(4 * avgHp - 21 * MathF.PI / 60); + + float deltaTheta = 30 * MathF.PI / 180 * MathF.Exp(-MathF.Pow((avgHp * 180f / MathF.PI - 275f) / 25f, 2)); + float Rc = 2 * MathF.Sqrt(MathF.Pow(avgCp, 7) / (MathF.Pow(avgCp, 7) + MathF.Pow(25f, 7))); + float Sl = 1 + ((0.015f * MathF.Pow(avgLp - 50, 2)) / MathF.Sqrt(20 + MathF.Pow(avgLp - 50, 2))); + float Sc = 1 + 0.045f * avgCp; + float Sh = 1 + 0.015f * avgCp * T; + float Rt = -MathF.Sin(2 * deltaTheta) * Rc; + + float deltaE = MathF.Sqrt( + MathF.Pow(deltaLp / Sl, 2) + + MathF.Pow(deltaCp / Sc, 2) + + MathF.Pow(deltaHp / Sh, 2) + + Rt * (deltaCp / Sc) * (deltaHp / Sh) + ); + + return deltaE; + } + } + + public static class Oklab + { + private static readonly Matrix4x4 M1 = new Matrix4x4( + 0.8189330101f, 0.0329845436f, 0.0482003018f, 0, + 0.3618667424f, 0.9293118715f, 0.2643662691f, 0, + -0.1288597137f, 0.0361456387f, 0.6338517070f, 0, + 0, 0, 0, 1); + + private static readonly Matrix4x4 M2 = new Matrix4x4( + 0.2104542553f, 1.9779984951f, 0.0259040371f, 0, + 0.7936177850f, -2.4285922050f, 0.7827717662f, 0, + -0.0040720468f, 0.4505937099f, -0.8086757660f, 0, + 0, 0, 0, 1); + + public static Vector4 XyzaToLaba(Vector4 xyzAlpha) + { + // xyzAlpha /= CieXyz.WhitePointD65; + + // Apply first linear transform + Vector4 labAlpha = Vector4.Transform(xyzAlpha, M1); + + labAlpha = new Vector4( + MathF.Cbrt(labAlpha.X), + MathF.Cbrt(labAlpha.Y), + MathF.Cbrt(labAlpha.Z), + labAlpha.W); + + labAlpha = Vector4.Transform(labAlpha, M2); + + return labAlpha; + } + + public static Vector4 XyzaToLCha(Vector4 xyzAlpha) + { + Vector4 labAlpha = XyzaToLaba(xyzAlpha); + + float C = MathF.Sqrt(labAlpha.Y * labAlpha.Y + labAlpha.Z * labAlpha.Z); + float h = MathF.Atan2(labAlpha.Z, labAlpha.Y); + if (h < 0) h += 2 * MathF.PI; // Normalize to [0, 2π) + + return new Vector4(labAlpha.X, C, h, labAlpha.W); + } + + public static Vector4 LrgbToOklab(Vector4 rgba) + { + float l = 0.4122214708f * rgba.X + 0.5363325363f * rgba.Y + 0.0514459929f * rgba.Z; + float m = 0.2119034982f * rgba.X + 0.6806995451f * rgba.Y + 0.1073969566f * rgba.Z; + float s = 0.0883024619f * rgba.X + 0.2817188376f * rgba.Y + 0.6299787005f * rgba.Z; + + l = MathF.Cbrt(l); + m = MathF.Cbrt(m); + s = MathF.Cbrt(s); + + return new Vector4( + 0.2104542553f*l + 0.7936177850f*m - 0.0040720468f*s, + 1.9779984951f*l - 2.4285922050f*m + 0.4505937099f*s, + 0.0259040371f*l + 0.7827717662f*m - 0.8086757660f*s, + rgba.W + ); + } + + public static Vector4 OklabToLrgb(Vector4 okLab) + { + float l = okLab.X + 0.3963377774f * okLab.Y + 0.2158037573f * okLab.Z; + float m = okLab.X - 0.1055613458f * okLab.Y - 0.0638541728f * okLab.Z; + float s = okLab.X - 0.0894841775f * okLab.Y - 1.2914855480f * okLab.Z; + + l = l*l*l; + m = m*m*m; + s = s*s*s; + + return new Vector4( + +4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s, + -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s, + -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s, + okLab.W + ); + } + + public static Vector4 OklabToLcha(Vector4 okLab) + { + float C = MathF.Sqrt(okLab.Y * okLab.Y + okLab.Z * okLab.Z); + float h = MathF.Atan2(okLab.Z, okLab.Y); + if (h < 0) h += 2 * MathF.PI; // Normalize to [0, 2π) + + return new Vector4(okLab.X, C, h, okLab.W); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float DeltaE(Vector4 lab1, Vector4 lab2) + { + Vector4 lScale = new Vector4(2.016f, 1, 1, 1); + + Vector4 diff = lab1 - lab2; + diff *= lScale; + + return diff.Length(); + } + } +} diff --git a/BCnEnc.Net/Shared/Colors/ColorUtils.cs b/BCnEnc.Net/Shared/Colors/ColorUtils.cs new file mode 100644 index 0000000..613d8aa --- /dev/null +++ b/BCnEnc.Net/Shared/Colors/ColorUtils.cs @@ -0,0 +1,59 @@ +using System; +using System.Diagnostics; + +namespace BCnEncoder.Shared.Colors; + +public class ColorUtils +{ + /// + /// From: https://www.khronos.org/registry/vulkan/specs/1.2/html/chap16.html#_conversion_formulas + /// + public static (uint red, uint green, uint blue, uint exponent) RgbToSharedExponent(float red, float green, float blue, int mantissaBits, int bias, int exponentMax) + { + var twoPowN = (float)(1 << mantissaBits); + var twoPowEmaxMinusB = MathF.Pow(2, exponentMax - bias); + var sharedExpMax = ((twoPowN - 1) / twoPowN) * twoPowEmaxMinusB; + + var redClamp = MathF.Max(0, MathF.Min(sharedExpMax, red)); + var greClamp = MathF.Max(0, MathF.Min(sharedExpMax, green)); + var bluClamp = MathF.Max(0, MathF.Min(sharedExpMax, blue)); + + var maxClamped = MathF.Max(redClamp, MathF.Max(bluClamp, greClamp)); + + var prelimExp = maxClamped > MathF.Pow(2, -(bias + 1)) ? + (int)MathF.Floor(MathF.Log(maxClamped, 2)) + (bias + 1) + : 0; + + var maxShared = (int) MathF.Floor((maxClamped / MathF.Pow(2, prelimExp - bias - mantissaBits)) + 0.5f); + var expShared = maxShared == (1 << mantissaBits) ? + prelimExp + 1 : + prelimExp; + + Debug.Assert(expShared >= 0, "expShared < 0!!"); + + var twoPowExpSharedMinusBMantissa = MathF.Pow(2, expShared - bias - mantissaBits); + var redShared = (uint)MathF.Floor((redClamp / twoPowExpSharedMinusBMantissa) + 0.5f); + var greShared = (uint)MathF.Floor((greClamp / twoPowExpSharedMinusBMantissa) + 0.5f); + var bluShared = (uint)MathF.Floor((bluClamp / twoPowExpSharedMinusBMantissa) + 0.5f); + + return ( + red: redShared, + green: greShared, + blue: bluShared, + exponent: (uint)expShared); + } + + /// + /// From: https://www.khronos.org/registry/vulkan/specs/1.2/html/chap16.html#_conversion_formulas + /// + public static (float red, float green, float blue) SharedExponentToRgb((uint Red, uint Green, uint Blue, uint Exp) shared, int mantissaBits, + int bias) + { + var twoPowExp = MathF.Pow(2, shared.Exp - bias - mantissaBits); + var red = shared.Red * twoPowExp; + var green = shared.Green * twoPowExp; + var blue = shared.Blue * twoPowExp; + + return (red, green, blue); + } +} diff --git a/BCnEnc.Net/Shared/Colors/ColorsNonRgb.cs b/BCnEnc.Net/Shared/Colors/ColorsNonRgb.cs new file mode 100644 index 0000000..30fd0ce --- /dev/null +++ b/BCnEnc.Net/Shared/Colors/ColorsNonRgb.cs @@ -0,0 +1,276 @@ +using System; +using System.Numerics; + +namespace BCnEncoder.Shared.Colors; + +internal struct ColorYCbCr : IColor +{ + public float y; + public float cb; + public float cr; + + public ColorYCbCr(float y, float cb, float cr) + { + this.y = y; + this.cb = cb; + this.cr = cr; + } + + public override string ToString() + { + ColorSpace.YCbCrToLrgb(y, cb, cr, out var r, out var g, out var b); + + return $"r : {r * 255} g : {g * 255} b : {b * 255}"; + } + + public float CalcDistWeighted(ColorYCbCr other, float yWeight = 4) + { + var dy = (y - other.y) * (y - other.y) * yWeight; + var dcb = (cb - other.cb) * (cb - other.cb); + var dcr = (cr - other.cr) * (cr - other.cr); + + return MathF.Sqrt(dy + dcb + dcr); + } + + public static ColorYCbCr operator +(ColorYCbCr left, ColorYCbCr right) + { + return new ColorYCbCr( + left.y + right.y, + left.cb + right.cb, + left.cr + right.cr); + } + + public static ColorYCbCr operator /(ColorYCbCr left, float right) + { + return new ColorYCbCr( + left.y / right, + left.cb / right, + left.cr / right); + } + + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + ColorSpace.YCbCrToLrgb(y, cb, cr, out var r, out var g, out var b); + return new ColorRgbaFloat(r, g, b); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + ColorSpace.LrgbToYCbCr(color.r, color.g, color.b, out y, out cb, out cr); + } + + /// + public bool Equals(ColorYCbCr other) + { + return y.Equals(other.y) && cb.Equals(other.cb) && cr.Equals(other.cr); + } + + /// + public override bool Equals(object obj) + { + return obj is ColorYCbCr other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(y, cb, cr); + } + + public Vector3 ToVector3() + { + return new Vector3(y, cb, cr); + } +} + +internal struct ColorYCbCrAlpha : IColor +{ + public float y; + public float cb; + public float cr; + public float a; + + public ColorYCbCrAlpha(float y, float cb, float cr, float a) + { + this.y = y; + this.cb = cb; + this.cr = cr; + this.a = a; + } + + /// + public bool Equals(ColorYCbCrAlpha other) + { + return + y == other.y && + cb == other.cb && + cr == other.cr && + a == other.a; + } + + public override string ToString() + { + ColorSpace.YCbCrToLrgb(y, cb, cr, out var r, out var g, out var b); + + return $"r : {r * 255} g : {g * 255} b : {b * 255}"; + } + + public float CalcDistWeighted(ColorYCbCrAlpha other, float yWeight = 4, float aWeight = 1) + { + var dy = (y - other.y) * (y - other.y) * yWeight; + var dcb = (cb - other.cb) * (cb - other.cb); + var dcr = (cr - other.cr) * (cr - other.cr); + var da = (a - other.a) * (a - other.a) * aWeight; + + return MathF.Sqrt(dy + dcb + dcr + da); + } + + public static ColorYCbCrAlpha operator +(ColorYCbCrAlpha left, ColorYCbCrAlpha right) + { + return new ColorYCbCrAlpha( + left.y + right.y, + left.cb + right.cb, + left.cr + right.cr, + left.a + right.a); + } + + public static ColorYCbCrAlpha operator /(ColorYCbCrAlpha left, float right) + { + return new ColorYCbCrAlpha( + left.y / right, + left.cb / right, + left.cr / right, + left.a / right); + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + ColorSpace.YCbCrToLrgb(y, cb, cr, out var r, out var g, out var b); + return new ColorRgbaFloat(r, g, b, a); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + ColorSpace.LrgbToYCbCr(color.r, color.g, color.b, out y, out cb, out cr); + a = color.a; + } +} + +internal struct ColorXyz : IColor +{ + public float x; + public float y; + public float z; + + public ColorXyz(float x, float y, float z) + { + this.x = x; + this.y = y; + this.z = z; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + ColorSpace.XyzToLrgb(x, y, z, out var r, out var g, out var b); + return new ColorRgbaFloat(r, g, b); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + ColorSpace.LrgbToXyz(color.r, color.g, color.b, out x, out y, out z); + } + + /// + public bool Equals(ColorXyz other) + { + return + x == other.x && + y == other.y && + z == other.z; + } +} + +internal struct ColorLab : IColor +{ + public float l; + public float a; + public float b; + + public ColorLab(float l, float a, float b) + { + this.l = l; + this.a = a; + this.b = b; + } + + private static ColorLab XyzToLab(ColorXyz xyz) + { + var refX = 95.047f; // Observer= 2°, Illuminant= D65 + var refY = 100.000f; + var refZ = 108.883f; + + var x = PivotXyz(xyz.x * 100 / refX); + var y = PivotXyz(xyz.y * 100 / refY); + var z = PivotXyz(xyz.z * 100 / refZ); + + return new ColorLab(116 * y - 16, 500 * (x - y), 200 * (y - z)); + } + + private static ColorXyz LabToXyz(ColorLab lab) + { + var y = (lab.l + 16) / 116f; + var x = lab.a / 500f + y; + var z = y - lab.b / 200f; + + var refX = 95.047f; // Observer= 2°, Illuminant= D65 + var refY = 100.000f; + var refZ = 108.883f; + + x = ReversePivotXyz(x) * refX / 100; + y = ReversePivotXyz(y) * refY / 100; + z = ReversePivotXyz(z) * refZ / 100; + + return new ColorXyz(x, y, z); + } + + private static float ReversePivotXyz(float n) + { + return n > 0.2068966f ? MathF.Pow(n, 3) : (n - 16 / 116f) / 7.787f; + } + + private static float PivotXyz(float n) + { + var i = MathF.Cbrt(n); + return n > 0.008856f ? i : 7.787f * n + 16 / 116f; + } + + public ColorRgbaFloat ToColorRgbaFloat() + { + var xyz = LabToXyz(this); + return xyz.ToColorRgbaFloat(); + } + + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + ColorXyz xyz = color.As(); + var lab = XyzToLab(xyz); + l = lab.l; + a = lab.a; + b = lab.b; + } + + public bool Equals(ColorLab other) + { + return + l == other.l && + a == other.a && + b == other.b; + } +} diff --git a/BCnEnc.Net/Shared/Colors/ColorsPacked.cs b/BCnEnc.Net/Shared/Colors/ColorsPacked.cs new file mode 100644 index 0000000..b7afade --- /dev/null +++ b/BCnEnc.Net/Shared/Colors/ColorsPacked.cs @@ -0,0 +1,2055 @@ +using System.Numerics; +using System.Runtime.InteropServices; + +namespace BCnEncoder.Shared.Colors; + +using static ColorBitConversionHelpers; + +[StructLayout(LayoutKind.Sequential)] +public struct ColorB4G4R4A4Packed : IColorRgba, IColorPacked +{ + /// + public static PackedRgbaMask ChannelMask { get; } = "B4G4R4A4"; + + ushort IColorPacked.Data + { + get => data; + set => data = value; + } + + public bool Equals(ColorB4G4R4A4Packed other) + { + return data == other.data; + } + + public override bool Equals(object obj) + { + return obj is ColorB4G4R4A4Packed other && Equals(other); + } + + public override int GetHashCode() + { + return data.GetHashCode(); + } + + public static bool operator ==(ColorB4G4R4A4Packed left, ColorB4G4R4A4Packed right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorB4G4R4A4Packed left, ColorB4G4R4A4Packed right) + { + return !left.Equals(right); + } + + private const ushort BlueMask = 0x000F; + private const int BlueShift = 0; + private const ushort GreenMask = 0x00F0; + private const int GreenShift = 4; + private const ushort RedMask = 0x0F00; + private const int RedShift = 8; + private const ushort AlphaMask = 0xF000; + private const int AlphaShift = 12; + + private const ushort RedMaxValue = 15; + private const ushort GreenMaxValue = 15; + private const ushort BlueMaxValue = 15; + private const ushort AlphaMaxValue = 15; + + public ushort data; + + public float R + { + readonly get => Unorm4ToFloat(RawR); + set => RawR = FloatToUnorm4(value); + } + + public float G + { + readonly get => Unorm4ToFloat(RawG); + set => RawG = FloatToUnorm4(value); + } + + public float B + { + readonly get => Unorm4ToFloat(RawB); + set => RawB = FloatToUnorm4(value); + } + + public float A + { + readonly get => Unorm4ToFloat(RawA); + set => RawA = FloatToUnorm4(value); + } + + public ushort RawR + { + readonly get => (ushort)((data & RedMask) >> RedShift); + set + { + if (value > RedMaxValue) value = RedMaxValue; + data = (ushort)(data & ~RedMask); + data = (ushort)(data | (value << RedShift)); + } + } + + public ushort RawG + { + readonly get => (ushort)((data & GreenMask) >> GreenShift); + set + { + if (value > GreenMaxValue) value = GreenMaxValue; + data = (ushort)(data & ~GreenMask); + data = (ushort)(data | (value << GreenShift)); + } + } + + public ushort RawB + { + readonly get => (ushort)((data & BlueMask) >> BlueShift); + set + { + if (value > BlueMaxValue) value = BlueMaxValue; + data = (ushort)(data & ~BlueMask); + data = (ushort)(data | (value << BlueShift)); + } + } + + public ushort RawA + { + readonly get => (ushort)((data & AlphaMask) >> AlphaShift); + set + { + if (value > AlphaMaxValue) value = AlphaMaxValue; + data = (ushort)(data & ~AlphaMask); + data = (ushort)(data | (value << AlphaShift)); + } + } + + public ColorB4G4R4A4Packed(float r, float g, float b, float a) + { + data = 0; + R = r; + G = g; + B = b; + A = a; + } + + public override string ToString() + { + return $"r : {R} g : {G} b : {B} a : {A}"; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat(R, G, B, A); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + B = color.b; + A = color.a; + } +} + +[StructLayout(LayoutKind.Sequential)] +public struct ColorR4G4B4A4Packed : IColorRgba, IColorPacked +{ + /// + public static PackedRgbaMask ChannelMask { get; } = "R4G4B4A4"; + + ushort IColorPacked.Data + { + get => data; + set => data = value; + } + + public bool Equals(ColorR4G4B4A4Packed other) + { + return data == other.data; + } + + public override bool Equals(object obj) + { + return obj is ColorR4G4B4A4Packed other && Equals(other); + } + + public override int GetHashCode() + { + return data.GetHashCode(); + } + + public static bool operator ==(ColorR4G4B4A4Packed left, ColorR4G4B4A4Packed right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorR4G4B4A4Packed left, ColorR4G4B4A4Packed right) + { + return !left.Equals(right); + } + + private const ushort RedMask = 0x000F; + private const int RedShift = 0; + private const ushort GreenMask = 0x00F0; + private const int GreenShift = 4; + private const ushort BlueMask = 0x0F00; + private const int BlueShift = 8; + private const ushort AlphaMask = 0xF000; + private const int AlphaShift = 12; + + private const ushort RedMaxValue = 15; + private const ushort GreenMaxValue = 15; + private const ushort BlueMaxValue = 15; + private const ushort AlphaMaxValue = 15; + + public ushort data; + + public float R + { + readonly get => Unorm4ToFloat(RawR); + set => RawR = FloatToUnorm4(value); + } + + public float G + { + readonly get => Unorm4ToFloat(RawG); + set => RawG = FloatToUnorm4(value); + } + + public float B + { + readonly get => Unorm4ToFloat(RawB); + set => RawB = FloatToUnorm4(value); + } + + public float A + { + readonly get => Unorm4ToFloat(RawA); + set => RawA = FloatToUnorm4(value); + } + + public ushort RawR + { + readonly get => (ushort)((data & RedMask) >> RedShift); + set + { + if (value > RedMaxValue) value = RedMaxValue; + data = (ushort)(data & ~RedMask); + data = (ushort)(data | (value << RedShift)); + } + } + + public ushort RawG + { + readonly get => (ushort)((data & GreenMask) >> GreenShift); + set + { + if (value > GreenMaxValue) value = GreenMaxValue; + data = (ushort)(data & ~GreenMask); + data = (ushort)(data | (value << GreenShift)); + } + } + + public ushort RawB + { + readonly get => (ushort)((data & BlueMask) >> BlueShift); + set + { + if (value > BlueMaxValue) value = BlueMaxValue; + data = (ushort)(data & ~BlueMask); + data = (ushort)(data | (value << BlueShift)); + } + } + + public ushort RawA + { + readonly get => (ushort)((data & AlphaMask) >> AlphaShift); + set + { + if (value > AlphaMaxValue) value = AlphaMaxValue; + data = (ushort)(data & ~AlphaMask); + data = (ushort)(data | (value << AlphaShift)); + } + } + + public ColorR4G4B4A4Packed(float r, float g, float b, float a) + { + data = 0; + R = r; + G = g; + B = b; + A = a; + } + + public override string ToString() + { + return $"r : {R} g : {G} b : {B} a : {A}"; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat(R, G, B, A); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + B = color.b; + A = color.a; + } +} + +[StructLayout(LayoutKind.Sequential)] +public struct ColorA4B4G4R4Packed : IColorRgba, IColorPacked +{ + /// + public static PackedRgbaMask ChannelMask { get; } = "A4B4G4R4"; + + ushort IColorPacked.Data + { + get => data; + set => data = value; + } + + public bool Equals(ColorA4B4G4R4Packed other) + { + return data == other.data; + } + + public override bool Equals(object obj) + { + return obj is ColorA4B4G4R4Packed other && Equals(other); + } + + public override int GetHashCode() + { + return data.GetHashCode(); + } + + public static bool operator ==(ColorA4B4G4R4Packed left, ColorA4B4G4R4Packed right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorA4B4G4R4Packed left, ColorA4B4G4R4Packed right) + { + return !left.Equals(right); + } + + private const ushort AlphaMask = 0x000F; + private const int AlphaShift = 0; + private const ushort BlueMask = 0x00F0; + private const int BlueShift = 4; + private const ushort GreenMask = 0x0F00; + private const int GreenShift = 8; + private const ushort RedMask = 0xF000; + private const int RedShift = 12; + + private const ushort RedMaxValue = 15; + private const ushort GreenMaxValue = 15; + private const ushort BlueMaxValue = 15; + private const ushort AlphaMaxValue = 15; + + public ushort data; + + public float R + { + readonly get => Unorm4ToFloat(RawR); + set => RawR = FloatToUnorm4(value); + } + + public float G + { + readonly get => Unorm4ToFloat(RawG); + set => RawG = FloatToUnorm4(value); + } + + public float B + { + readonly get => Unorm4ToFloat(RawB); + set => RawB = FloatToUnorm4(value); + } + + public float A + { + readonly get => Unorm4ToFloat(RawA); + set => RawA = FloatToUnorm4(value); + } + + public ushort RawR + { + readonly get => (ushort)((data & RedMask) >> RedShift); + set + { + if (value > RedMaxValue) value = RedMaxValue; + data = (ushort)(data & ~RedMask); + data = (ushort)(data | (value << RedShift)); + } + } + + public ushort RawG + { + readonly get => (ushort)((data & GreenMask) >> GreenShift); + set + { + if (value > GreenMaxValue) value = GreenMaxValue; + data = (ushort)(data & ~GreenMask); + data = (ushort)(data | (value << GreenShift)); + } + } + + public ushort RawB + { + readonly get => (ushort)((data & BlueMask) >> BlueShift); + set + { + if (value > BlueMaxValue) value = BlueMaxValue; + data = (ushort)(data & ~BlueMask); + data = (ushort)(data | (value << BlueShift)); + } + } + + public ushort RawA + { + readonly get => (ushort)((data & AlphaMask) >> AlphaShift); + set + { + if (value > AlphaMaxValue) value = AlphaMaxValue; + data = (ushort)(data & ~AlphaMask); + data = (ushort)(data | (value << AlphaShift)); + } + } + + public ColorA4B4G4R4Packed(float r, float g, float b, float a) + { + data = 0; + R = r; + G = g; + B = b; + A = a; + } + + public override string ToString() + { + return $"r : {R} g : {G} b : {B} a : {A}"; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat(R, G, B, A); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + B = color.b; + A = color.a; + } +} + +[StructLayout(LayoutKind.Sequential)] +public struct ColorA4R4G4B4Packed : IColorRgba, IColorPacked +{ + /// + public static PackedRgbaMask ChannelMask { get; } = "A4R4G4B4"; + + ushort IColorPacked.Data + { + get => data; + set => data = value; + } + + public bool Equals(ColorA4R4G4B4Packed other) + { + return data == other.data; + } + + public override bool Equals(object obj) + { + return obj is ColorA4R4G4B4Packed other && Equals(other); + } + + public override int GetHashCode() + { + return data.GetHashCode(); + } + + public static bool operator ==(ColorA4R4G4B4Packed left, ColorA4R4G4B4Packed right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorA4R4G4B4Packed left, ColorA4R4G4B4Packed right) + { + return !left.Equals(right); + } + + private const ushort AlphaMask = 0x000F; + private const int AlphaShift = 0; + private const ushort RedMask = 0x00F0; + private const int RedShift = 4; + private const ushort GreenMask = 0x0F00; + private const int GreenShift = 8; + private const ushort BlueMask = 0xF000; + private const int BlueShift = 12; + + private const ushort RedMaxValue = 15; + private const ushort GreenMaxValue = 15; + private const ushort BlueMaxValue = 15; + private const ushort AlphaMaxValue = 15; + + public ushort data; + + public float R + { + readonly get => Unorm4ToFloat(RawR); + set => RawR = FloatToUnorm4(value); + } + + public float G + { + readonly get => Unorm4ToFloat(RawG); + set => RawG = FloatToUnorm4(value); + } + + public float B + { + readonly get => Unorm4ToFloat(RawB); + set => RawB = FloatToUnorm4(value); + } + + public float A + { + readonly get => Unorm4ToFloat(RawA); + set => RawA = FloatToUnorm4(value); + } + + public ushort RawR + { + readonly get => (ushort)((data & RedMask) >> RedShift); + set + { + if (value > RedMaxValue) value = RedMaxValue; + data = (ushort)(data & ~RedMask); + data = (ushort)(data | (value << RedShift)); + } + } + + public ushort RawG + { + readonly get => (ushort)((data & GreenMask) >> GreenShift); + set + { + if (value > GreenMaxValue) value = GreenMaxValue; + data = (ushort)(data & ~GreenMask); + data = (ushort)(data | (value << GreenShift)); + } + } + + public ushort RawB + { + readonly get => (ushort)((data & BlueMask) >> BlueShift); + set + { + if (value > BlueMaxValue) value = BlueMaxValue; + data = (ushort)(data & ~BlueMask); + data = (ushort)(data | (value << BlueShift)); + } + } + + public ushort RawA + { + readonly get => (ushort)((data & AlphaMask) >> AlphaShift); + set + { + if (value > AlphaMaxValue) value = AlphaMaxValue; + data = (ushort)(data & ~AlphaMask); + data = (ushort)(data | (value << AlphaShift)); + } + } + + public ColorA4R4G4B4Packed(float r, float g, float b, float a) + { + data = 0; + R = r; + G = g; + B = b; + A = a; + } + + public override string ToString() + { + return $"r : {R} g : {G} b : {B} a : {A}"; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat(R, G, B, A); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + B = color.b; + A = color.a; + } +} + +[StructLayout(LayoutKind.Sequential)] +public struct ColorB5G5R5M1Packed : IColorRgb, IColorPacked +{ + /// + public static PackedRgbaMask ChannelMask { get; } = "B5G5R5M1"; + + ushort IColorPacked.Data + { + readonly get => data; + set => data = value; + } + + public readonly bool Equals(ColorB5G5R5M1Packed other) + { + return data == other.data; + } + + public readonly override bool Equals(object obj) + { + return obj is ColorB5G5R5M1Packed other && Equals(other); + } + + public readonly override int GetHashCode() + { + return data.GetHashCode(); + } + + public static bool operator ==(ColorB5G5R5M1Packed left, ColorB5G5R5M1Packed right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorB5G5R5M1Packed left, ColorB5G5R5M1Packed right) + { + return !left.Equals(right); + } + + private const ushort ModeMask = 0b1_00000_00000_00000; + private const int ModeShift = 15; + private const ushort RedMask = 0b0_11111_00000_00000; + private const int RedShift = 10; + private const ushort GreenMask = 0b0_00000_11111_00000; + private const int GreenShift = 5; + private const ushort BlueMask = 0b0_00000_00000_11111; + + public ushort data; + + public byte Mode + { + readonly get + { + var mode = (data & ModeMask) >> ModeShift; + return (byte)mode; + } + set + { + var mode = value; + data = (ushort)(data & ~ModeMask); + data = (ushort)(data | (mode << ModeShift)); + } + } + + public float R + { + readonly get => Unorm5ToFloat(RawR); + set => RawR = FloatToUnorm5(value); + } + + public float G + { + readonly get => Unorm5ToFloat(RawG); + set => RawG = FloatToUnorm5(value); + } + + public float B + { + readonly get => Unorm5ToFloat(RawB); + set => RawB = FloatToUnorm5(value); + } + + public byte ByteR + { + readonly get + { + var r5 = (data & RedMask) >> RedShift; + return (byte)((r5 << 3) | (r5 >> 2)); + } + set + { + var r5 = value >> 3; + data = (ushort)(data & ~RedMask); + data = (ushort)(data | (r5 << RedShift)); + } + } + + public byte ByteG + { + readonly get + { + var g5 = (data & GreenMask) >> GreenShift; + return (byte)((g5 << 3) | (g5 >> 2)); + } + set + { + var g5 = value >> 3; + data = (ushort)(data & ~GreenMask); + data = (ushort)(data | (g5 << GreenShift)); + } + } + + public byte ByteB + { + readonly get + { + var b5 = data & BlueMask; + return (byte)((b5 << 3) | (b5 >> 2)); + } + set + { + var b5 = value >> 3; + data = (ushort)(data & ~BlueMask); + data = (ushort)(data | b5); + } + } + + public ushort RawR + { + readonly get => (ushort)((data & RedMask) >> RedShift); + set + { + if (value > 31) value = 31; + if (value < 0) value = 0; + data = (ushort)(data & ~RedMask); + data = (ushort)(data | (value << RedShift)); + } + } + + public ushort RawG + { + readonly get => (ushort)((data & GreenMask) >> GreenShift); + set + { + if (value > 31) value = 31; + if (value < 0) value = 0; + data = (ushort)(data & ~GreenMask); + data = (ushort)(data | (value << GreenShift)); + } + } + + public ushort RawB + { + readonly get => (ushort)(data & BlueMask); + set + { + if (value > 31) value = 31; + data = (ushort)(data & ~BlueMask); + data = (ushort)(data | value); + } + } + + public ColorB5G5R5M1Packed(float r, float g, float b) + { + data = 0; + R = r; + G = g; + B = b; + } + + public readonly override string ToString() + { + return $"r : {R} g : {G} b : {B}"; + } + + /// + public readonly ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat( + R, + G, + B); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + B = color.b; + } +} + +[StructLayout(LayoutKind.Sequential)] +public struct ColorB5G6R5Packed : IColorRgb, IColorPacked +{ + /// + public static PackedRgbaMask ChannelMask { get; } = "B5G6R5"; + + ushort IColorPacked.Data + { + readonly get => data; + set => data = value; + } + + public readonly bool Equals(ColorB5G6R5Packed other) + { + return data == other.data; + } + + public readonly override bool Equals(object obj) + { + return obj is ColorB5G6R5Packed other && Equals(other); + } + + public readonly override int GetHashCode() + { + return data.GetHashCode(); + } + + public static bool operator ==(ColorB5G6R5Packed left, ColorB5G6R5Packed right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorB5G6R5Packed left, ColorB5G6R5Packed right) + { + return !left.Equals(right); + } + + private const ushort RedMask = 0b11111_000000_00000; + private const int RedShift = 11; + private const ushort GreenMask = 0b00000_111111_00000; + private const int GreenShift = 5; + private const ushort BlueMask = 0b00000_000000_11111; + + public ushort data; + + public float R + { + readonly get => Unorm5ToFloat(RawR); + set => RawR = FloatToUnorm5(value); + } + + public float G + { + readonly get => Unorm6ToFloat(RawG); + set => RawG = FloatToUnorm6(value); + } + + public float B + { + readonly get => Unorm5ToFloat(RawB); + set => RawB = FloatToUnorm5(value); + } + + public ushort RawR + { + readonly get => (ushort)((data & RedMask) >> RedShift); + set + { + if (value > 31) value = 31; + data = (ushort)(data & ~RedMask); + data = (ushort)(data | (value << RedShift)); + } + } + + public ushort RawG + { + readonly get => (ushort)((data & GreenMask) >> GreenShift); + set + { + if (value > 63) value = 63; + data = (ushort)(data & ~GreenMask); + data = (ushort)(data | (value << GreenShift)); + } + } + + public ushort RawB + { + readonly get => (ushort)(data & BlueMask); + set + { + if (value > 31) value = 31; + data = (ushort)(data & ~BlueMask); + data = (ushort)(data | value); + } + } + + public ColorB5G6R5Packed(float r, float g, float b) + { + data = 0; + R = r; + G = g; + B = b; + } + + public ColorB5G6R5Packed(Vector4 color) + { + data = 0; + R = color.X; + G = color.Y; + B = color.Z; + } + + public readonly override string ToString() + { + return $"r : {R} g : {G} b : {B}"; + } + + /// + public readonly ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat( + R, + G, + B); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + B = color.b; + } +} + +[StructLayout(LayoutKind.Sequential)] +public struct ColorR5G6B5Packed : IColorRgb, IColorPacked +{ + /// + public static PackedRgbaMask ChannelMask { get; } = "R5G6B5"; + + ushort IColorPacked.Data + { + get => data; + set => data = value; + } + + public bool Equals(ColorR5G6B5Packed other) + { + return data == other.data; + } + + public override bool Equals(object obj) + { + return obj is ColorR5G6B5Packed other && Equals(other); + } + + public override int GetHashCode() + { + return data.GetHashCode(); + } + + public static bool operator ==(ColorR5G6B5Packed left, ColorR5G6B5Packed right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorR5G6B5Packed left, ColorR5G6B5Packed right) + { + return !left.Equals(right); + } + + private const ushort RedMask = 0x001F; + private const int RedShift = 0; + private const ushort GreenMask = 0x07E0; + private const int GreenShift = 5; + private const ushort BlueMask = 0xF800; + private const int BlueShift = 11; + + private const ushort RedMaxValue = 31; + private const ushort GreenMaxValue = 63; + private const ushort BlueMaxValue = 31; + + public ushort data; + + public float R + { + readonly get => Unorm5ToFloat(RawR); + set => RawR = FloatToUnorm5(value); + } + + public float G + { + readonly get => Unorm6ToFloat(RawG); + set => RawG = FloatToUnorm6(value); + } + + public float B + { + readonly get => Unorm5ToFloat(RawB); + set => RawB = FloatToUnorm5(value); + } + + + public ushort RawR + { + readonly get => (ushort)((data & RedMask) >> RedShift); + set + { + if (value > RedMaxValue) value = RedMaxValue; + data = (ushort)(data & ~RedMask); + data = (ushort)(data | (value << RedShift)); + } + } + + public ushort RawG + { + readonly get => (ushort)((data & GreenMask) >> GreenShift); + set + { + if (value > GreenMaxValue) value = GreenMaxValue; + data = (ushort)(data & ~GreenMask); + data = (ushort)(data | (value << GreenShift)); + } + } + + public ushort RawB + { + readonly get => (ushort)((data & BlueMask) >> BlueShift); + set + { + if (value > BlueMaxValue) value = BlueMaxValue; + data = (ushort)(data & ~BlueMask); + data = (ushort)(data | (value << BlueShift)); + } + } + + public ColorR5G6B5Packed(float r, float g, float b) + { + data = 0; + R = r; + G = g; + B = b; + } + + public override string ToString() + { + return $"r : {R} g : {G} b : {B}"; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat( + R, + G, + B); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + B = color.b; + } +} + +[StructLayout(LayoutKind.Sequential)] +public struct ColorB5G5R5A1Packed : IColorRgba, IColorPacked +{ + /// + public static PackedRgbaMask ChannelMask { get; } = "B5G5R5A1"; + + ushort IColorPacked.Data + { + get => data; + set => data = value; + } + + public bool Equals(ColorB5G5R5A1Packed other) + { + return data == other.data; + } + + public override bool Equals(object obj) + { + return obj is ColorB5G5R5A1Packed other && Equals(other); + } + + public override int GetHashCode() + { + return data.GetHashCode(); + } + + public static bool operator ==(ColorB5G5R5A1Packed left, ColorB5G5R5A1Packed right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorB5G5R5A1Packed left, ColorB5G5R5A1Packed right) + { + return !left.Equals(right); + } + + private const ushort BlueMask = 0x001F; + private const int BlueShift = 0; + private const ushort GreenMask = 0x03E0; + private const int GreenShift = 5; + private const ushort RedMask = 0x7C00; + private const int RedShift = 10; + private const ushort AlphaMask = 0x8000; + private const int AlphaShift = 15; + + private const ushort RedMaxValue = 31; + private const ushort GreenMaxValue = 31; + private const ushort BlueMaxValue = 31; + private const ushort AlphaMaxValue = 1; + + public ushort data; + + public float R + { + readonly get => Unorm5ToFloat(RawR); + set => RawR = FloatToUnorm5(value); + } + + public float G + { + readonly get => Unorm5ToFloat(RawG); + set => RawG = FloatToUnorm5(value); + } + + public float B + { + readonly get => Unorm5ToFloat(RawB); + set => RawB = FloatToUnorm5(value); + } + + public float A + { + readonly get => Unorm1ToFloat(RawA); + set => RawA = FloatToUnorm1(value); + } + + public ushort RawR + { + readonly get => (ushort)((data & RedMask) >> RedShift); + set + { + if (value > RedMaxValue) value = RedMaxValue; + data = (ushort)(data & ~RedMask); + data = (ushort)(data | (value << RedShift)); + } + } + + public ushort RawG + { + readonly get => (ushort)((data & GreenMask) >> GreenShift); + set + { + if (value > GreenMaxValue) value = GreenMaxValue; + data = (ushort)(data & ~GreenMask); + data = (ushort)(data | (value << GreenShift)); + } + } + + public ushort RawB + { + readonly get => (ushort)((data & BlueMask) >> BlueShift); + set + { + if (value > BlueMaxValue) value = BlueMaxValue; + data = (ushort)(data & ~BlueMask); + data = (ushort)(data | (value << BlueShift)); + } + } + + public ushort RawA + { + readonly get => (ushort)((data & AlphaMask) >> AlphaShift); + set + { + if (value > AlphaMaxValue) value = AlphaMaxValue; + data = (ushort)(data & ~AlphaMask); + data = (ushort)(data | (value << AlphaShift)); + } + } + + public ColorB5G5R5A1Packed(float r, float g, float b, float a) + { + data = 0; + R = r; + G = g; + B = b; + A = a; + } + + public override string ToString() + { + return $"r : {R} g : {G} b : {B} a : {A}"; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat(R, G, B, A); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + B = color.b; + A = color.a; + } +} + +[StructLayout(LayoutKind.Sequential)] +public struct ColorR5G5B5A1Packed : IColorRgba, IColorPacked +{ + /// + public static PackedRgbaMask ChannelMask { get; } = "R5G5B5A1"; + + ushort IColorPacked.Data + { + get => data; + set => data = value; + } + + public bool Equals(ColorR5G5B5A1Packed other) + { + return data == other.data; + } + + public override bool Equals(object obj) + { + return obj is ColorR5G5B5A1Packed other && Equals(other); + } + + public override int GetHashCode() + { + return data.GetHashCode(); + } + + public static bool operator ==(ColorR5G5B5A1Packed left, ColorR5G5B5A1Packed right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorR5G5B5A1Packed left, ColorR5G5B5A1Packed right) + { + return !left.Equals(right); + } + + private const ushort RedMask = 0x001F; + private const int RedShift = 0; + private const ushort GreenMask = 0x03E0; + private const int GreenShift = 5; + private const ushort BlueMask = 0x7C00; + private const int BlueShift = 10; + private const ushort AlphaMask = 0x8000; + private const int AlphaShift = 15; + + private const ushort RedMaxValue = 31; + private const ushort GreenMaxValue = 31; + private const ushort BlueMaxValue = 31; + private const ushort AlphaMaxValue = 1; + + public ushort data; + + public float R + { + readonly get => Unorm5ToFloat(RawR); + set => RawR = FloatToUnorm5(value); + } + + public float G + { + readonly get => Unorm5ToFloat(RawG); + set => RawG = FloatToUnorm5(value); + } + + public float B + { + readonly get => Unorm5ToFloat(RawB); + set => RawB = FloatToUnorm5(value); + } + + public float A + { + readonly get => Unorm1ToFloat(RawA); + set => RawA = FloatToUnorm1(value); + } + + public ushort RawR + { + readonly get => (ushort)((data & RedMask) >> RedShift); + set + { + if (value > RedMaxValue) value = RedMaxValue; + data = (ushort)(data & ~RedMask); + data = (ushort)(data | (value << RedShift)); + } + } + + public ushort RawG + { + readonly get => (ushort)((data & GreenMask) >> GreenShift); + set + { + if (value > GreenMaxValue) value = GreenMaxValue; + data = (ushort)(data & ~GreenMask); + data = (ushort)(data | (value << GreenShift)); + } + } + + public ushort RawB + { + readonly get => (ushort)((data & BlueMask) >> BlueShift); + set + { + if (value > BlueMaxValue) value = BlueMaxValue; + data = (ushort)(data & ~BlueMask); + data = (ushort)(data | (value << BlueShift)); + } + } + + public ushort RawA + { + readonly get => (ushort)((data & AlphaMask) >> AlphaShift); + set + { + if (value > AlphaMaxValue) value = AlphaMaxValue; + data = (ushort)(data & ~AlphaMask); + data = (ushort)(data | (value << AlphaShift)); + } + } + + public ColorR5G5B5A1Packed(float r, float g, float b, float a) + { + data = 0; + R = r; + G = g; + B = b; + A = a; + } + + public override string ToString() + { + return $"r : {R} g : {G} b : {B} a : {A}"; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat(R, G, B, A); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + B = color.b; + A = color.a; + } +} + +[StructLayout(LayoutKind.Sequential)] +public struct ColorA1B5G5R5Packed : IColorRgba, IColorPacked +{ + /// + public static PackedRgbaMask ChannelMask { get; } = "A1B5G5R5"; + + ushort IColorPacked.Data + { + get => data; + set => data = value; + } + + public bool Equals(ColorA1B5G5R5Packed other) + { + return data == other.data; + } + + public override bool Equals(object obj) + { + return obj is ColorA1B5G5R5Packed other && Equals(other); + } + + public override int GetHashCode() + { + return data.GetHashCode(); + } + + public static bool operator ==(ColorA1B5G5R5Packed left, ColorA1B5G5R5Packed right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorA1B5G5R5Packed left, ColorA1B5G5R5Packed right) + { + return !left.Equals(right); + } + + private const ushort AlphaMask = 0x0001; + private const int AlphaShift = 0; + private const ushort BlueMask = 0x003E; + private const int BlueShift = 1; + private const ushort GreenMask = 0x07C0; + private const int GreenShift = 6; + private const ushort RedMask = 0xF800; + private const int RedShift = 11; + + private const ushort RedMaxValue = 31; + private const ushort GreenMaxValue = 31; + private const ushort BlueMaxValue = 31; + private const ushort AlphaMaxValue = 1; + + public ushort data; + + public float R + { + readonly get => Unorm5ToFloat(RawR); + set => RawR = FloatToUnorm5(value); + } + + public float G + { + readonly get => Unorm5ToFloat(RawG); + set => RawG = FloatToUnorm5(value); + } + + public float B + { + readonly get => Unorm5ToFloat(RawB); + set => RawB = FloatToUnorm5(value); + } + + public float A + { + readonly get => Unorm1ToFloat(RawA); + set => RawA = FloatToUnorm1(value); + } + + public ushort RawR + { + readonly get => (ushort)((data & RedMask) >> RedShift); + set + { + if (value > RedMaxValue) value = RedMaxValue; + data = (ushort)(data & ~RedMask); + data = (ushort)(data | (value << RedShift)); + } + } + + public ushort RawG + { + readonly get => (ushort)((data & GreenMask) >> GreenShift); + set + { + if (value > GreenMaxValue) value = GreenMaxValue; + data = (ushort)(data & ~GreenMask); + data = (ushort)(data | (value << GreenShift)); + } + } + + public ushort RawB + { + readonly get => (ushort)((data & BlueMask) >> BlueShift); + set + { + if (value > BlueMaxValue) value = BlueMaxValue; + data = (ushort)(data & ~BlueMask); + data = (ushort)(data | (value << BlueShift)); + } + } + + public ushort RawA + { + readonly get => (ushort)((data & AlphaMask) >> AlphaShift); + set + { + if (value > AlphaMaxValue) value = AlphaMaxValue; + data = (ushort)(data & ~AlphaMask); + data = (ushort)(data | (value << AlphaShift)); + } + } + + public ColorA1B5G5R5Packed(float r, float g, float b, float a) + { + data = 0; + R = r; + G = g; + B = b; + A = a; + } + + public override string ToString() + { + return $"r : {R} g : {G} b : {B} a : {A}"; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat(R, G, B, A); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + B = color.b; + A = color.a; + } +} + +[StructLayout(LayoutKind.Sequential)] +public struct ColorA1R5G5B5Packed : IColorRgba, IColorPacked +{ + /// + public static PackedRgbaMask ChannelMask { get; } = "A1R5G5B5"; + + ushort IColorPacked.Data + { + get => data; + set => data = value; + } + + public bool Equals(ColorA1R5G5B5Packed other) + { + return data == other.data; + } + + public override bool Equals(object obj) + { + return obj is ColorA1R5G5B5Packed other && Equals(other); + } + + public override int GetHashCode() + { + return data.GetHashCode(); + } + + public static bool operator ==(ColorA1R5G5B5Packed left, ColorA1R5G5B5Packed right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorA1R5G5B5Packed left, ColorA1R5G5B5Packed right) + { + return !left.Equals(right); + } + + private const ushort AlphaMask = 0x0001; + private const int AlphaShift = 0; + private const ushort RedMask = 0x003E; + private const int RedShift = 1; + private const ushort GreenMask = 0x07C0; + private const int GreenShift = 6; + private const ushort BlueMask = 0xF800; + private const int BlueShift = 11; + + private const ushort RedMaxValue = 31; + private const ushort GreenMaxValue = 31; + private const ushort BlueMaxValue = 31; + private const ushort AlphaMaxValue = 1; + + public ushort data; + + public float R + { + readonly get => Unorm5ToFloat(RawR); + set => RawR = FloatToUnorm5(value); + } + + public float G + { + readonly get => Unorm5ToFloat(RawG); + set => RawG = FloatToUnorm5(value); + } + + public float B + { + readonly get => Unorm5ToFloat(RawB); + set => RawB = FloatToUnorm5(value); + } + + public float A + { + readonly get => Unorm1ToFloat(RawA); + set => RawA = FloatToUnorm1(value); + } + + public ushort RawR + { + readonly get => (ushort)((data & RedMask) >> RedShift); + set + { + if (value > RedMaxValue) value = RedMaxValue; + data = (ushort)(data & ~RedMask); + data = (ushort)(data | (value << RedShift)); + } + } + + public ushort RawG + { + readonly get => (ushort)((data & GreenMask) >> GreenShift); + set + { + if (value > GreenMaxValue) value = GreenMaxValue; + data = (ushort)(data & ~GreenMask); + data = (ushort)(data | (value << GreenShift)); + } + } + + public ushort RawB + { + readonly get => (ushort)((data & BlueMask) >> BlueShift); + set + { + if (value > BlueMaxValue) value = BlueMaxValue; + data = (ushort)(data & ~BlueMask); + data = (ushort)(data | (value << BlueShift)); + } + } + + public ushort RawA + { + readonly get => (ushort)((data & AlphaMask) >> AlphaShift); + set + { + if (value > AlphaMaxValue) value = AlphaMaxValue; + data = (ushort)(data & ~AlphaMask); + data = (ushort)(data | (value << AlphaShift)); + } + } + + public ColorA1R5G5B5Packed(float r, float g, float b, float a) + { + data = 0; + R = r; + G = g; + B = b; + A = a; + } + + public override string ToString() + { + return $"r : {R} g : {G} b : {B} a : {A}"; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat(R, G, B, A); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + B = color.b; + A = color.a; + } +} + +[StructLayout(LayoutKind.Sequential)] +public struct ColorR10G10B10A2Packed : IColorRgba, IColorPacked +{ + /// + public static PackedRgbaMask ChannelMask { get; } = "R10G10B10A2"; + + uint IColorPacked.Data + { + get => data; + set => data = value; + } + + public bool Equals(ColorR10G10B10A2Packed other) + { + return data == other.data; + } + + public override bool Equals(object obj) + { + return obj is ColorR10G10B10A2Packed other && Equals(other); + } + + public override int GetHashCode() + { + return data.GetHashCode(); + } + + public static bool operator ==(ColorR10G10B10A2Packed left, ColorR10G10B10A2Packed right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorR10G10B10A2Packed left, ColorR10G10B10A2Packed right) + { + return !left.Equals(right); + } + + private const uint RedMask = 0x000003FF; + private const int RedShift = 0; + private const uint GreenMask = 0x000FFC00; + private const int GreenShift = 10; + private const uint BlueMask = 0x3FF00000; + private const int BlueShift = 20; + private const uint AlphaMask = 0xC0000000; + private const int AlphaShift = 30; + + private const uint RedMaxValue = 1023; + private const uint GreenMaxValue = 1023; + private const uint BlueMaxValue = 1023; + private const uint AlphaMaxValue = 3; + + public uint data; + + public float R + { + readonly get => Unorm10ToFloat(RawR); + set => RawR = FloatToUnorm10(value); + } + + public float G + { + readonly get => Unorm10ToFloat(RawG); + set => RawG = FloatToUnorm10(value); + } + + public float B + { + readonly get => Unorm10ToFloat(RawB); + set => RawB = FloatToUnorm10(value); + } + + public float A + { + readonly get => Unorm2ToFloat(RawA); + set => RawA = FloatToUnorm2(value); + } + + public uint RawR + { + readonly get => (data & RedMask) >> RedShift; + set + { + if (value > RedMaxValue) value = RedMaxValue; + data = (data & ~RedMask) | (value << RedShift); + } + } + + public uint RawG + { + readonly get => (data & GreenMask) >> GreenShift; + set + { + if (value > GreenMaxValue) value = GreenMaxValue; + data = (data & ~GreenMask) | (value << GreenShift); + } + } + + public uint RawB + { + readonly get => (data & BlueMask) >> BlueShift; + set + { + if (value > BlueMaxValue) value = BlueMaxValue; + data = (data & ~BlueMask) | (value << BlueShift); + } + } + + public uint RawA + { + readonly get => (data & AlphaMask) >> AlphaShift; + set + { + if (value > AlphaMaxValue) value = AlphaMaxValue; + data = (data & ~AlphaMask) | (value << AlphaShift); + } + } + + public ColorR10G10B10A2Packed(float r, float g, float b, float a) + { + data = 0; + R = r; + G = g; + B = b; + A = a; + } + + public override string ToString() + { + return $"r : {R:F3} g : {G:F3} b : {B:F3} a : {A:F3}"; + } + + /// + public readonly ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat(R, G, B, A); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + B = color.b; + A = color.a; + } +} + +[StructLayout(LayoutKind.Sequential)] +public struct ColorB10G10R10A2Packed : IColorRgba, IColorPacked +{ + /// + public static PackedRgbaMask ChannelMask { get; } = "B10G10R10A2"; + + uint IColorPacked.Data + { + get => data; + set => data = value; + } + + public bool Equals(ColorB10G10R10A2Packed other) + { + return data == other.data; + } + + public override bool Equals(object obj) + { + return obj is ColorB10G10R10A2Packed other && Equals(other); + } + + public override int GetHashCode() + { + return data.GetHashCode(); + } + + public static bool operator ==(ColorB10G10R10A2Packed left, ColorB10G10R10A2Packed right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorB10G10R10A2Packed left, ColorB10G10R10A2Packed right) + { + return !left.Equals(right); + } + + private const uint RedMask = 0x3FF00000; + private const int RedShift = 20; + private const uint GreenMask = 0x000FFC00; + private const int GreenShift = 10; + private const uint BlueMask = 0x000003FF; + private const int BlueShift = 00; + private const uint AlphaMask = 0xC0000000; + private const int AlphaShift = 30; + + private const uint RedMaxValue = 1023; + private const uint GreenMaxValue = 1023; + private const uint BlueMaxValue = 1023; + private const uint AlphaMaxValue = 3; + + public uint data; + + public float R + { + readonly get => Unorm10ToFloat(RawR); + set => RawR = FloatToUnorm10(value); + } + + public float G + { + readonly get => Unorm10ToFloat(RawG); + set => RawG = FloatToUnorm10(value); + } + + public float B + { + readonly get => Unorm10ToFloat(RawB); + set => RawB = FloatToUnorm10(value); + } + + public float A + { + readonly get => Unorm2ToFloat(RawA); + set => RawA = FloatToUnorm2(value); + } + + public uint RawR + { + readonly get => (data & RedMask) >> RedShift; + set + { + if (value > RedMaxValue) value = RedMaxValue; + data = (data & ~RedMask) | (value << RedShift); + } + } + + public uint RawG + { + readonly get => (data & GreenMask) >> GreenShift; + set + { + if (value > GreenMaxValue) value = GreenMaxValue; + data = (data & ~GreenMask) | (value << GreenShift); + } + } + + public uint RawB + { + readonly get => (data & BlueMask) >> BlueShift; + set + { + if (value > BlueMaxValue) value = BlueMaxValue; + data = (data & ~BlueMask) | (value << BlueShift); + } + } + + public uint RawA + { + readonly get => (data & AlphaMask) >> AlphaShift; + set + { + if (value > AlphaMaxValue) value = AlphaMaxValue; + data = (data & ~AlphaMask) | (value << AlphaShift); + } + } + + public ColorB10G10R10A2Packed(float r, float g, float b, float a) + { + data = 0; + R = r; + G = g; + B = b; + A = a; + } + + public override string ToString() + { + return $"r : {R:F3} g : {G:F3} b : {B:F3} a : {A:F3}"; + } + + /// + public readonly ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat(R, G, B, A); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + B = color.b; + A = color.a; + } +} + +[StructLayout(LayoutKind.Sequential)] +public struct ColorR4G4Packed : IColorRedGreen, IColorPacked +{ + /// + public static PackedRgbaMask ChannelMask { get; } = "R4G4"; + + byte IColorPacked.Data + { + get => data; + set => data = value; + } + + public bool Equals(ColorR4G4Packed other) => data == other.data; + + public override bool Equals(object obj) => obj is ColorR4G4Packed other && Equals(other); + + public override int GetHashCode() => data.GetHashCode(); + + public static bool operator ==(ColorR4G4Packed left, ColorR4G4Packed right) => left.Equals(right); + + public static bool operator !=(ColorR4G4Packed left, ColorR4G4Packed right) => !left.Equals(right); + + private const byte RedMask = 0x0F; + private const int RedShift = 0; + private const byte GreenMask = 0xF0; + private const int GreenShift = 4; + + private const byte RedMaxValue = 15; + private const byte GreenMaxValue = 15; + + public byte data; + + /// + public float R + { + readonly get => Unorm4ToFloat(RawR); + set => RawR = FloatToUnorm4(value); + } + + /// + public float G + { + readonly get => Unorm4ToFloat(RawG); + set => RawG = FloatToUnorm4(value); + } + + /// + public byte RawR + { + readonly get => (byte)((data & RedMask) >> RedShift); + set + { + if (value > RedMaxValue) value = RedMaxValue; + data = (byte)(data & ~RedMask); + data = (byte)(data | (value << RedShift)); + } + } + + /// + public byte RawG + { + readonly get => (byte)((data & GreenMask) >> GreenShift); + set + { + if (value > GreenMaxValue) value = GreenMaxValue; + data = (byte)(data & ~GreenMask); + data = (byte)(data | (value << GreenShift)); + } + } + + public ColorR4G4Packed(float r, float g) + { + data = 0; + R = r; + G = g; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() => new ColorRgbaFloat(R, G, 0, 1); + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + } +} diff --git a/BCnEnc.Net/Shared/Colors/ColorsPackedFloat.cs b/BCnEnc.Net/Shared/Colors/ColorsPackedFloat.cs new file mode 100644 index 0000000..686944c --- /dev/null +++ b/BCnEnc.Net/Shared/Colors/ColorsPackedFloat.cs @@ -0,0 +1,167 @@ +using System; +using System.Numerics; +using System.Runtime.InteropServices; + +namespace BCnEncoder.Shared.Colors; + +using static BCnEncoder.Shared.Colors.ColorBitConversionHelpers; + +/// +/// Represents a packed R11G11B10F floating point color. +/// This format uses 11 bits for red, 11 bits for green, and 10 bits for blue, all as floating point values. +/// +[StructLayout(LayoutKind.Sequential)] +public struct ColorR11G11B10PackedUFloat : IColorRgb, IColorPacked +{ + /// + public static PackedRgbaMask ChannelMask { get; } = "R11G11B10"; + + uint IColorPacked.Data + { + get => data; + set => data = value; + } + + // Masks and shifts for accessing components + private const uint RedMask = 0x7FFU; + private const uint GreenMask = 0x7FFU << 11; + private const int GreenShift = 11; + private const uint BlueMask = 0x3FFU << 22; + private const int BlueShift = 22; + + // The packed 32-bit value containing all three channels + public uint data; + + /// + /// Gets or sets the red component as a floating-point value. + /// + public float R + { + readonly get => Float11ToFloat(RawR); + set => RawR = FloatToFloat11(value); + } + + /// + /// Gets or sets the green component as a floating-point value. + /// + public float G + { + readonly get => Float11ToFloat(RawG); + set => RawG = FloatToFloat11(value); + } + + /// + /// Gets or sets the blue component as a floating-point value. + /// + public float B + { + readonly get => Float10ToFloat(RawB); + set => RawB = FloatToFloat10(value); + } + + /// + /// Gets or sets the raw 11-bit red component. + /// + public uint RawR + { + readonly get => (data & RedMask); + set + { + if (value > 0x7FF) value = 0x7FF; // Clamp to 11 bits + data = (data & ~RedMask) | value; + } + } + + /// + /// Gets or sets the raw 11-bit green component. + /// + public uint RawG + { + readonly get => (data & GreenMask) >> GreenShift; + set + { + if (value > 0x7FF) value = 0x7FF; // Clamp to 11 bits + data = (data & ~GreenMask) | (value << GreenShift); + } + } + + /// + /// Gets or sets the raw 10-bit blue component. + /// + public uint RawB + { + readonly get => (data & BlueMask) >> BlueShift; + set + { + if (value > 0x3FF) value = 0x3FF; // Clamp to 10 bits + data = (data & ~BlueMask) | (value << BlueShift); + } + } + + /// + /// Creates a new ColorR11G11B10F from a 32-bit packed value. + /// + public ColorR11G11B10PackedUFloat(uint data) + { + this.data = data; + } + + /// + /// Creates a new ColorR11G11B10F from individual floating point values. + /// + public ColorR11G11B10PackedUFloat(float r, float g, float b) + { + data = 0; + R = r; + G = g; + B = b; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat(R, G, B); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + B = color.b; + } + + /// + public bool Equals(ColorR11G11B10PackedUFloat other) + { + return data == other.data; + } + + /// + public override bool Equals(object obj) + { + return obj is ColorR11G11B10PackedUFloat other && Equals(other); + } + + /// + public override int GetHashCode() + { + return data.GetHashCode(); + } + + public static bool operator ==(ColorR11G11B10PackedUFloat left, ColorR11G11B10PackedUFloat right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorR11G11B10PackedUFloat left, ColorR11G11B10PackedUFloat right) + { + return !left.Equals(right); + } + + /// + public override string ToString() + { + return $"R: {R}, G: {G}, B: {B}"; + } +} diff --git a/BCnEnc.Net/Shared/Colors/ColorsR.cs b/BCnEnc.Net/Shared/Colors/ColorsR.cs new file mode 100644 index 0000000..4bd5ee0 --- /dev/null +++ b/BCnEnc.Net/Shared/Colors/ColorsR.cs @@ -0,0 +1,559 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace BCnEncoder.Shared.Colors; + +using static ColorBitConversionHelpers; + + +[StructLayout(LayoutKind.Sequential)] +public struct ColorR8 : IColorRed +{ + public byte r; + + /// + /// The normalized float value [0-1]. + /// + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm8ToFloat(r); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = FloatToUnorm8(value); + } + + /// + /// The raw 8-bit unsigned integer value. + /// + public byte RawR + { + get => r; + set => r = value; + } + + public ColorR8(byte r) + { + this.r = r; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + float value = Unorm8ToFloat(r); + return new ColorRgbaFloat( + value, + value, + value + ); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + r = FloatToUnorm8(color.r); + } + + /// + public bool Equals(ColorR8 other) + { + return r == other.r; + } + + /// + public override bool Equals(object obj) + { + return obj is ColorR8 other && Equals(other); + } + + /// + public override int GetHashCode() + { + return r.GetHashCode(); + } +} + +[StructLayout(LayoutKind.Sequential)] +public struct ColorR8S : IColorRed +{ + public sbyte r; + + /// + /// The normalized float value [-1 to 1]. + /// + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SnormToFloat(r, 8); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = (sbyte)FloatToSnorm(value, 8); + } + + /// + /// The raw 8-bit signed integer value. + /// + public sbyte RawR + { + get => r; + set => r = value; + } + + public ColorR8S(sbyte r) + { + this.r = r; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + float value = SnormToFloat(r, 8); + return new ColorRgbaFloat( + value, + value, + value + ); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + r = (sbyte)FloatToSnorm(color.r, 8); + } + + /// + public bool Equals(ColorR8S other) + { + return r == other.r; + } + + /// + public override bool Equals(object obj) + { + return obj is ColorR8S other && Equals(other); + } + + /// + public override int GetHashCode() + { + return r.GetHashCode(); + } +} + + +/// +/// Raw 16-bit unsigned normalized single-channel format. 16 bits for Red channel. +/// +[StructLayout(LayoutKind.Sequential)] +public struct ColorR16 : IColorRed +{ + /// + /// The raw 16-bit unsigned integer value. + /// + public ushort r; + + /// + /// The normalized float value [0-1]. + /// + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm16ToFloat(r); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = FloatToUnorm16(value); + } + + /// + /// The raw 16-bit unsigned integer value. + /// + public ushort RawR + { + get => r; + set => r = value; + } + + /// + /// Creates a new ColorR16 instance. + /// + /// The 16-bit raw value. + public ColorR16(ushort r) + { + this.r = r; + } + + /// + /// Creates a new ColorR16 instance from a normalized float value [0-1]. + /// + /// The float value [0-1]. + public ColorR16(float r) + { + this.r = FloatToUnorm16(r); + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + float value = Unorm16ToFloat(r); + return new ColorRgbaFloat( + value, + value, + value + ); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + r = FloatToUnorm16(color.r); + } + + /// + public bool Equals(ColorR16 other) + { + return r == other.r; + } + + /// + public override bool Equals(object obj) + { + return obj is ColorR16 other && Equals(other); + } + + /// + public override int GetHashCode() + { + return r.GetHashCode(); + } + + /// + /// Equality operator. + /// + public static bool operator ==(ColorR16 left, ColorR16 right) + { + return left.Equals(right); + } + + /// + /// Inequality operator. + /// + public static bool operator !=(ColorR16 left, ColorR16 right) + { + return !left.Equals(right); + } + + /// + public override string ToString() + { + return $"R: {r} ({R:F6})"; + } +} + +/// +/// Raw 16-bit signed normalized single-channel format. 16 bits for Red channel. +/// Values range from -1 to 1. +/// +[StructLayout(LayoutKind.Sequential)] +public struct ColorR16S : IColorRed +{ + /// + /// The raw 16-bit signed integer value. + /// + public short r; + + /// + /// The normalized float value [-1 to 1]. + /// + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SnormToFloat(r, 16); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = (short)FloatToSnorm(value, 16); + } + + /// + /// The raw 16-bit signed integer value. + /// + public short RawR + { + get => r; + set => r = value; + } + + /// + /// Creates a new ColorR16S instance. + /// + /// The 16-bit signed raw value. + public ColorR16S(short r) + { + this.r = r; + } + + /// + /// Creates a new ColorR16S instance from a normalized float value [-1 to 1]. + /// + /// The float value [-1 to 1]. + public ColorR16S(float r) + { + this.r = (short)FloatToSnorm(r, 16); + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + float value = SnormToFloat(r, 16); + return new ColorRgbaFloat( + value, + value, + value + ); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + r = (short)FloatToSnorm(color.r, 16); + } + + /// + public bool Equals(ColorR16S other) + { + return r == other.r; + } + + /// + public override bool Equals(object obj) + { + return obj is ColorR16S other && Equals(other); + } + + /// + public override int GetHashCode() + { + return r.GetHashCode(); + } + + /// + /// Equality operator. + /// + public static bool operator ==(ColorR16S left, ColorR16S right) + { + return left.Equals(right); + } + + /// + /// Inequality operator. + /// + public static bool operator !=(ColorR16S left, ColorR16S right) + { + return !left.Equals(right); + } + + /// + public override string ToString() + { + return $"R: {r} ({R:F6})"; + } +} + +/// +/// Raw 16-bit floating-point single-channel format. 16 bits for Red channel. +/// +[StructLayout(LayoutKind.Sequential)] +public struct ColorR16F : IColorRed +{ + /// + /// The 16-bit half-precision floating-point value. + /// + public Half r; + + /// + /// The floating-point value as a regular float. + /// + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (float)r; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = (Half)value; + } + + /// + /// The raw half-precision floating-point value. + /// + public Half RawR + { + get => r; + set => r = value; + } + + /// + /// Creates a new ColorR16F instance. + /// + /// The half-precision value. + public ColorR16F(Half r) + { + this.r = r; + } + + /// + /// Creates a new ColorR16F instance from a float value. + /// + /// The float value. + public ColorR16F(float r) + { + this.r = (Half)r; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat( + (float)r, + (float)r, + (float)r + ); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + r = (Half)color.r; + } + + /// + public bool Equals(ColorR16F other) + { + return r.Equals(other.r); + } + + /// + public override bool Equals(object obj) + { + return obj is ColorR16F other && Equals(other); + } + + /// + public override int GetHashCode() + { + return r.GetHashCode(); + } + + /// + /// Equality operator + /// + public static bool operator ==(ColorR16F left, ColorR16F right) + { + return left.Equals(right); + } + + /// + /// Inequality operator + /// + public static bool operator !=(ColorR16F left, ColorR16F right) + { + return !left.Equals(right); + } + + /// + public override string ToString() + { + return $"R: {r} ({R:F6})"; + } +} + +/// +/// Raw 32-bit floating-point single-channel format. 32 bits for Red channel. +/// +[StructLayout(LayoutKind.Sequential)] +public struct ColorR32F : IColorRed +{ + /// + /// The 32-bit single-precision floating-point value. + /// + public float r; + + /// + /// The floating-point value as a regular float. + /// + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => r; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = value; + } + + /// + /// The raw 32-bit floating-point value. + /// + public float RawR + { + get => r; + set => r = value; + } + + /// + /// Creates a new ColorR32F instance. + /// + /// The float value. + public ColorR32F(float r) + { + this.r = r; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat( + r, + r, + r + ); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + r = color.r; + } + + /// + public bool Equals(ColorR32F other) + { + return r.Equals(other.r); + } + + /// + public override bool Equals(object obj) + { + return obj is ColorR32F other && Equals(other); + } + + /// + public override int GetHashCode() + { + return r.GetHashCode(); + } + + /// + /// Equality operator. + /// + public static bool operator ==(ColorR32F left, ColorR32F right) + { + return left.Equals(right); + } + + /// + /// Inequality operator. + /// + public static bool operator !=(ColorR32F left, ColorR32F right) + { + return !left.Equals(right); + } + + /// + public override string ToString() + { + return $"R: {r:F6}"; + } +} diff --git a/BCnEnc.Net/Shared/Colors/ColorsRG.cs b/BCnEnc.Net/Shared/Colors/ColorsRG.cs new file mode 100644 index 0000000..132c935 --- /dev/null +++ b/BCnEnc.Net/Shared/Colors/ColorsRG.cs @@ -0,0 +1,780 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using BCnEncoder.Shared.Colors; + +namespace BCnEncoder.Shared.Colors; + +using static ColorBitConversionHelpers; + +/// +/// Raw 8-bit unsigned normalized two-channel format. 8 bits for Red, 8 bits for Green. +/// +[StructLayout(LayoutKind.Sequential)] +public struct ColorR8G8 : IColorRedGreen +{ + /// + /// The raw 8-bit unsigned integer red value. + /// + public byte r; + + /// + /// The raw 8-bit unsigned integer green value. + /// + public byte g; + + /// + /// The normalized float value for red [0-1]. + /// + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm8ToFloat(r); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = FloatToUnorm8(value); + } + + /// + /// The normalized float value for green [0-1]. + /// + public float G + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm8ToFloat(g); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => g = FloatToUnorm8(value); + } + + /// + /// The raw 8-bit unsigned integer red value. + /// + public byte RawR + { + get => r; + set => r = value; + } + + /// + /// The raw 8-bit unsigned integer green value. + /// + public byte RawG + { + get => g; + set => g = value; + } + + /// + /// Creates a new ColorR8G8 with the specified values. + /// + public ColorR8G8(byte r, byte g) + { + this.r = r; + this.g = g; + } + + /// + /// Creates a new ColorR8G8 with the specified normalized values. + /// + public ColorR8G8(float r, float g) + { + this.r = FloatToUnorm8(r); + this.g = FloatToUnorm8(g); + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat(R, G, 0, 1); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + } + + /// + /// Returns a string representation of this color. + /// + public override string ToString() + { + return $"R8G8[R={R:F3}, G={G:F3}]"; + } + + /// + public override bool Equals(object? obj) + { + return obj is ColorR8G8 other && Equals(other); + } + + /// + public bool Equals(ColorR8G8 other) + { + return r == other.r && g == other.g; + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(r, g); + } + + /// + /// Compares two objects for equality. + /// + public static bool operator ==(ColorR8G8 left, ColorR8G8 right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + public static bool operator !=(ColorR8G8 left, ColorR8G8 right) + { + return !(left == right); + } +} + +/// +/// Raw 8-bit signed normalized two-channel format. 8 bits for Red, 8 bits for Green. +/// +[StructLayout(LayoutKind.Sequential)] +public struct ColorR8G8S : IColorRedGreen +{ + /// + /// The raw 8-bit signed integer red value. + /// + public sbyte r; + + /// + /// The raw 8-bit signed integer green value. + /// + public sbyte g; + + /// + /// The normalized float value for red [0-1]. + /// + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SnormToFloat(r, 8); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = (sbyte)FloatToSnorm(value, 8); + } + + /// + /// The normalized float value for green [0-1]. + /// + public float G + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SnormToFloat(g, 8); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => g = (sbyte)FloatToSnorm(value, 8); + } + + /// + /// The raw 8-bit unsigned integer red value. + /// + public sbyte RawR + { + get => r; + set => r = value; + } + + /// + /// The raw 8-bit unsigned integer green value. + /// + public sbyte RawG + { + get => g; + set => g = value; + } + + /// + /// Creates a new ColorR8G8 with the specified values. + /// + public ColorR8G8S(sbyte r, sbyte g) + { + this.r = r; + this.g = g; + } + + /// + /// Creates a new ColorR8G8 with the specified normalized values. + /// + public ColorR8G8S(float r, float g) + { + R = r; + G = g; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat(R, G, 0, 1); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + } + + /// + /// Returns a string representation of this color. + /// + public override string ToString() + { + return $"R8G8S[R={R:F3}, G={G:F3}]"; + } + + /// + public override bool Equals(object? obj) + { + return obj is ColorR8G8S other && Equals(other); + } + + /// + public bool Equals(ColorR8G8S other) + { + return r == other.r && g == other.g; + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(r, g); + } + + /// + /// Compares two objects for equality. + /// + public static bool operator ==(ColorR8G8S left, ColorR8G8S right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + public static bool operator !=(ColorR8G8S left, ColorR8G8S right) + { + return !(left == right); + } +} + +/// +/// Raw 16-bit unsigned normalized two-channel format. 16 bits for Red, 16 bits for Green. +/// +[StructLayout(LayoutKind.Sequential)] +public struct ColorR16G16 : IColorRedGreen +{ + /// + /// The 16-bit unsigned integer red value. + /// + public ushort r; + + /// + /// The 16-bit unsigned integer green value. + /// + public ushort g; + + /// + /// The floating-point value for red. + /// + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm16ToFloat(r); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = FloatToUnorm16(value); + } + + /// + /// The floating-point value for green. + /// + public float G + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm16ToFloat(g); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => g = FloatToUnorm16(value); + } + + /// + /// The raw 16-bit unsigned integer red value. + /// + public ushort RawR + { + get => r; + set => r = value; + } + + /// + /// The raw 16-bit unsigned integer green value. + /// + public ushort RawG + { + get => g; + set => g = value; + } + + /// + /// Creates a new ColorR16G16 with the specified values. + /// + public ColorR16G16(ushort r, ushort g) + { + this.r = r; + this.g = g; + } + + /// + /// Creates a new ColorR16G16 with the specified values. + /// + public ColorR16G16(float r, float g) + { + this.R = r; + this.G = g; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat(R, G, 0, 1); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + } + + /// + /// Returns a string representation of this color. + /// + public override string ToString() + { + return $"R16G16[R={R:F3}, G={G:F3}]"; + } + + /// + public override bool Equals(object? obj) + { + return obj is ColorR16G16 other && Equals(other); + } + + /// + public bool Equals(ColorR16G16 other) + { + return r == other.r && g == other.g; + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(r, g); + } + + /// + /// Compares two objects for equality. + /// + public static bool operator ==(ColorR16G16 left, ColorR16G16 right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + public static bool operator !=(ColorR16G16 left, ColorR16G16 right) + { + return !(left == right); + } +} + +/// +/// Raw 16-bit unsigned normalized two-channel format. 16 bits for Red, 16 bits for Green. +/// +[StructLayout(LayoutKind.Sequential)] +public struct ColorR16G16S : IColorRedGreen +{ + /// + /// The 16-bit signed integer red value. + /// + public short r; + + /// + /// The 16-bit signed integer green value. + /// + public short g; + + /// + /// The floating-point value for red. + /// + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SnormToFloat(r, 16); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = (short)FloatToSnorm(value, 16); + } + + /// + /// The floating-point value for green. + /// + public float G + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SnormToFloat(g, 16); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => g = (short)FloatToSnorm(value, 16); + } + + /// + /// The raw 16-bit signed integer red value. + /// + public short RawR + { + get => r; + set => r = value; + } + + /// + /// The raw 16-bit signed integer green value. + /// + public short RawG + { + get => g; + set => g = value; + } + + /// + /// Creates a new ColorR16G16S with the specified values. + /// + public ColorR16G16S(short r, short g) + { + this.r = r; + this.g = g; + } + + /// + /// Creates a new ColorR16G16S with the specified values. + /// + public ColorR16G16S(float r, float g) + { + R = r; + G = g; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat(R, G, 0, 1); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + } + + /// + /// Returns a string representation of this color. + /// + public override string ToString() + { + return $"R16G16S[R={R:F3}, G={G:F3}]"; + } + + /// + public override bool Equals(object? obj) + { + return obj is ColorR16G16S other && Equals(other); + } + + /// + public bool Equals(ColorR16G16S other) + { + return r == other.r && g == other.g; + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(r, g); + } + + /// + /// Compares two objects for equality. + /// + public static bool operator ==(ColorR16G16S left, ColorR16G16S right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + public static bool operator !=(ColorR16G16S left, ColorR16G16S right) + { + return !(left == right); + } +} + +/// +/// Raw 16-bit half-precision floating-point two-channel format. 16 bits for Red, 16 bits for Green. +/// +[StructLayout(LayoutKind.Sequential)] +public struct ColorR16G16F : IColorRedGreen +{ + /// + /// The 16-bit half-precision floating-point red value. + /// + public Half r; + + /// + /// The 16-bit half-precision floating-point green value. + /// + public Half g; + + /// + /// The floating-point value for red. + /// + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (float)r; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = (Half)value; + } + + /// + /// The floating-point value for green. + /// + public float G + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (float)g; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => g = (Half)value; + } + + /// + /// The raw 16-bit half-precision floating-point red value. + /// + public Half RawR + { + get => r; + set => r = value; + } + + /// + /// The raw 16-bit half-precision floating-point green value. + /// + public Half RawG + { + get => g; + set => g = value; + } + + /// + /// Creates a new ColorR16G16F with the specified values. + /// + public ColorR16G16F(Half r, Half g) + { + this.r = r; + this.g = g; + } + + /// + /// Creates a new ColorR16G16F with the specified values. + /// + public ColorR16G16F(float r, float g) + { + this.r = (Half)r; + this.g = (Half)g; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat((float)r, (float)g, 0, 1); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + r = (Half)color.r; + g = (Half)color.g; + } + + /// + /// Returns a string representation of this color. + /// + public override string ToString() + { + return $"R16G16F[R={R:F3}, G={G:F3}]"; + } + + /// + public override bool Equals(object? obj) + { + return obj is ColorR16G16F other && Equals(other); + } + + /// + public bool Equals(ColorR16G16F other) + { + return r == other.r && g == other.g; + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(r, g); + } + + /// + /// Compares two objects for equality. + /// + public static bool operator ==(ColorR16G16F left, ColorR16G16F right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + public static bool operator !=(ColorR16G16F left, ColorR16G16F right) + { + return !(left == right); + } +} + +/// +/// Raw 32-bit single-precision floating-point two-channel format. 32 bits for Red, 32 bits for Green. +/// +[StructLayout(LayoutKind.Sequential)] +public struct ColorR32G32F : IColorRedGreen +{ + /// + /// The 32-bit single-precision floating-point red value. + /// + public float r; + + /// + /// The 32-bit single-precision floating-point green value. + /// + public float g; + + /// + /// The floating-point value for red. + /// + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => r; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = value; + } + + /// + /// The floating-point value for green. + /// + public float G + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => g; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => g = value; + } + + /// + /// The raw 32-bit single-precision floating-point red value. + /// + public float RawR + { + get => r; + set => r = value; + } + + /// + /// The raw 32-bit single-precision floating-point green value. + /// + public float RawG + { + get => g; + set => g = value; + } + + /// + /// Creates a new ColorR32G32F with the specified values. + /// + public ColorR32G32F(float r, float g) + { + this.r = r; + this.g = g; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat(r, g, 0, 1); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + r = color.r; + g = color.g; + } + + /// + /// Returns a string representation of this color. + /// + public override string ToString() + { + return $"R32G32F[R={R:F3}, G={G:F3}]"; + } + + /// + public override bool Equals(object? obj) + { + return obj is ColorR32G32F other && Equals(other); + } + + /// + public bool Equals(ColorR32G32F other) + { + return r == other.r && g == other.g; + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(r, g); + } + + /// + /// Compares two objects for equality. + /// + public static bool operator ==(ColorR32G32F left, ColorR32G32F right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + public static bool operator !=(ColorR32G32F left, ColorR32G32F right) + { + return !(left == right); + } +} diff --git a/BCnEnc.Net/Shared/Colors/ColorsRGB.cs b/BCnEnc.Net/Shared/Colors/ColorsRGB.cs new file mode 100644 index 0000000..1a14503 --- /dev/null +++ b/BCnEnc.Net/Shared/Colors/ColorsRGB.cs @@ -0,0 +1,800 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace BCnEncoder.Shared.Colors; + + +using static BCnEncoder.Shared.Colors.ColorBitConversionHelpers; + + +[StructLayout(LayoutKind.Sequential)] +public struct ColorRgb24 : IColorRgb +{ + public byte r, g, b; + + public ColorRgb24(byte r, byte g, byte b) + { + this.r = r; + this.g = g; + this.b = b; + } + + public ColorRgb24(ColorRgba32 color) + { + this.r = color.r; + this.g = color.g; + this.b = color.b; + } + + /// + /// Gets or sets the red component as a floating-point value. + /// + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm8ToFloat(r); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = FloatToUnorm8(value); + } + + /// + /// Gets or sets the green component as a floating-point value. + /// + public float G + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm8ToFloat(g); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => g = FloatToUnorm8(value); + } + + /// + /// Gets or sets the blue component as a floating-point value. + /// + public float B + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm8ToFloat(b); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => b = FloatToUnorm8(value); + } + + /// + /// Gets or sets the raw red component value. + /// + public byte RawR + { + get => r; + set => r = value; + } + + /// + /// Gets or sets the raw green component value. + /// + public byte RawG + { + get => g; + set => g = value; + } + + /// + /// Gets or sets the raw blue component value. + /// + public byte RawB + { + get => b; + set => b = value; + } + + public bool Equals(ColorRgb24 other) + { + return r == other.r && g == other.g && b == other.b; + } + + public override bool Equals(object obj) + { + return obj is ColorRgb24 other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = r.GetHashCode(); + hashCode = (hashCode * 397) ^ g.GetHashCode(); + hashCode = (hashCode * 397) ^ b.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(ColorRgb24 left, ColorRgb24 right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorRgb24 left, ColorRgb24 right) + { + return !left.Equals(right); + } + + public static ColorRgb24 operator +(ColorRgb24 left, ColorRgb24 right) + { + return new ColorRgb24( + ByteHelper.ClampToByte(left.r + right.r), + ByteHelper.ClampToByte(left.g + right.g), + ByteHelper.ClampToByte(left.b + right.b)); + } + + public static ColorRgb24 operator -(ColorRgb24 left, ColorRgb24 right) + { + return new ColorRgb24( + ByteHelper.ClampToByte(left.r - right.r), + ByteHelper.ClampToByte(left.g - right.g), + ByteHelper.ClampToByte(left.b - right.b)); + } + + public static ColorRgb24 operator /(ColorRgb24 left, double right) + { + return new ColorRgb24( + ByteHelper.ClampToByte((int)(left.r / right)), + ByteHelper.ClampToByte((int)(left.g / right)), + ByteHelper.ClampToByte((int)(left.b / right)) + ); + } + + public static ColorRgb24 operator *(ColorRgb24 left, double right) + { + return new ColorRgb24( + ByteHelper.ClampToByte((int)(left.r * right)), + ByteHelper.ClampToByte((int)(left.g * right)), + ByteHelper.ClampToByte((int)(left.b * right)) + ); + } + + public override string ToString() + { + return $"r : {r} g : {g} b : {b}"; + } + + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat( + Unorm8ToFloat(r), + Unorm8ToFloat(g), + Unorm8ToFloat(b)); + } + + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + r = FloatToUnorm8(color.r); + g = FloatToUnorm8(color.g); + b = FloatToUnorm8(color.b); + } +} + +[StructLayout(LayoutKind.Sequential)] +public struct ColorBgr24 : IColorRgb +{ + public byte b, g, r; + + public ColorBgr24(byte b, byte g, byte r) + { + this.b = b; + this.g = g; + this.r = r; + } + + /// + public bool Equals(ColorBgr24 other) + { + return b == other.b && g == other.g && r == other.r; + } + + /// + public override bool Equals(object obj) + { + return obj is ColorBgr24 other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(b, g, r); + } + + /// + /// Gets or sets the red component as a floating-point value. + /// + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm8ToFloat(r); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = FloatToUnorm8(value); + } + + /// + /// Gets or sets the green component as a floating-point value. + /// + public float G + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm8ToFloat(g); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => g = FloatToUnorm8(value); + } + + /// + /// Gets or sets the blue component as a floating-point value. + /// + public float B + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm8ToFloat(b); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => b = FloatToUnorm8(value); + } + + /// + /// Gets or sets the raw red component value. + /// + public byte RawR + { + get => r; + set => r = value; + } + + /// + /// Gets or sets the raw green component value. + /// + public byte RawG + { + get => g; + set => g = value; + } + + /// + /// Gets or sets the raw blue component value. + /// + public byte RawB + { + get => b; + set => b = value; + } + + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat( + Unorm8ToFloat(r), + Unorm8ToFloat(g), + Unorm8ToFloat(b)); + } + + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + r = FloatToUnorm8(color.r); + g = FloatToUnorm8(color.g); + b = FloatToUnorm8(color.b); + } +} + +public struct ColorRgbFloat : IColorRgb +{ + public float r, g, b; + + public ColorRgbFloat(float r, float g, float b) + { + this.r = r; + this.g = g; + this.b = b; + } + + public ColorRgbFloat(ColorRgba32 other) + { + this.r = other.r / 255f; + this.g = other.g / 255f; + this.b = other.b / 255f; + } + + public ColorRgbFloat(Vector3 vector) + { + r = vector.X; + g = vector.Y; + b = vector.Z; + } + + public bool Equals(ColorRgbFloat other) + { + return r == other.r && g == other.g && b == other.b; + } + + public override bool Equals(object obj) + { + return obj is ColorRgbFloat other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = r.GetHashCode(); + hashCode = (hashCode * 397) ^ g.GetHashCode(); + hashCode = (hashCode * 397) ^ b.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(ColorRgbFloat left, ColorRgbFloat right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorRgbFloat left, ColorRgbFloat right) + { + return !left.Equals(right); + } + + public static ColorRgbFloat operator +(ColorRgbFloat left, ColorRgbFloat right) + { + return new ColorRgbFloat( + left.r + right.r, + left.g + right.g, + left.b + right.b); + } + + public static ColorRgbFloat operator -(ColorRgbFloat left, ColorRgbFloat right) + { + return new ColorRgbFloat( + left.r - right.r, + left.g - right.g, + left.b - right.b); + } + + public static ColorRgbFloat operator /(ColorRgbFloat left, float right) + { + return new ColorRgbFloat( + left.r / right, + left.g / right, + left.b / right + ); + } + + public static ColorRgbFloat operator *(ColorRgbFloat left, float right) + { + return new ColorRgbFloat( + left.r * right, + left.g * right, + left.b * right + ); + } + + public static ColorRgbFloat operator *(float left, ColorRgbFloat right) + { + return new ColorRgbFloat( + right.r * left, + right.g * left, + right.b * left + ); + } + + public override string ToString() + { + return $"r : {r:0.00} g : {g:0.00} b : {b:0.00}"; + } + + public ColorRgba32 ToRgba32() + { + return new ColorRgba32( + (byte)(ByteHelper.ClampToByte(r * 255)), + (byte)(ByteHelper.ClampToByte(g * 255)), + (byte)(ByteHelper.ClampToByte(b * 255)), + 255 + ); + } + + public Vector3 ToVector3() + { + return new Vector3(r, g, b); + } + + internal float CalcLogDist(ColorRgbFloat other) + { + var dr = Math.Sign(other.r) * MathF.Log(1 + MathF.Abs(other.r)) - + Math.Sign(r) * MathF.Log(1 + MathF.Abs(r)); + var dg = Math.Sign(other.g) * MathF.Log(1 + MathF.Abs(other.g)) - + Math.Sign(g) * MathF.Log(1 + MathF.Abs(g)); + var db = Math.Sign(other.b) * MathF.Log(1 + MathF.Abs(other.b)) - + Math.Sign(b) * MathF.Log(1 + MathF.Abs(b)); + return MathF.Sqrt((dr * dr) + (dg * dg) + (db * db)); + } + + internal float CalcDist(ColorRgbFloat other) + { + var dr = other.r - r; + var dg = other.g - g; + var db = other.b - b; + return MathF.Sqrt((dr * dr) + (dg * dg) + (db * db)); + } + + internal void ClampToPositive() + { + if (r < 0) r = 0; + if (g < 0) g = 0; + if (b < 0) b = 0; + } + + internal void ClampToHalf() + { + if (r < (float)Half.MinValue) r = (float)Half.MinValue; + else if (g > (float)Half.MaxValue) g = (float)Half.MaxValue; + if (b < (float)Half.MinValue) b = (float)Half.MinValue; + else if (r > (float)Half.MaxValue) r = (float)Half.MaxValue; + if (g < (float)Half.MinValue) g = (float)Half.MinValue; + else if (b > (float)Half.MaxValue) b = (float)Half.MaxValue; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat(this); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + this.r = color.r; + this.g = color.g; + this.b = color.b; + } + + public static implicit operator ColorRgbaFloat(ColorRgbFloat c) => c.ToColorRgbaFloat(); + + /// + /// Gets or sets the red component as a floating-point value. + /// + public float R + { + get => r; + set => r = value; + } + + /// + /// Gets or sets the green component as a floating-point value. + /// + public float G + { + get => g; + set => g = value; + } + + /// + /// Gets or sets the blue component as a floating-point value. + /// + public float B + { + get => b; + set => b = value; + } + + /// + /// Gets or sets the raw red component value. + /// + public float RawR + { + get => r; + set => r = value; + } + + /// + /// Gets or sets the raw green component value. + /// + public float RawG + { + get => g; + set => g = value; + } + + /// + /// Gets or sets the raw blue component value. + /// + public float RawB + { + get => b; + set => b = value; + } +} + +/// +/// Raw 16-bit unsigned normalized three-channel format. 16 bits for Red, 16 bits for Green, 16 bits for Blue. +/// +[StructLayout(LayoutKind.Sequential)] +public struct ColorR16G16B16 : IColorRgb +{ + /// The 16-bit unsigned integer red value. + public ushort r; + /// The 16-bit unsigned integer green value. + public ushort g; + /// The 16-bit unsigned integer blue value. + public ushort b; + + /// Gets or sets the red component as a normalized floating-point value [0, 1]. + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm16ToFloat(r); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = FloatToUnorm16(value); + } + + /// Gets or sets the green component as a normalized floating-point value [0, 1]. + public float G + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm16ToFloat(g); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => g = FloatToUnorm16(value); + } + + /// Gets or sets the blue component as a normalized floating-point value [0, 1]. + public float B + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm16ToFloat(b); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => b = FloatToUnorm16(value); + } + + /// Gets or sets the raw 16-bit unsigned integer red value. + public ushort RawR { get => r; set => r = value; } + /// Gets or sets the raw 16-bit unsigned integer green value. + public ushort RawG { get => g; set => g = value; } + /// Gets or sets the raw 16-bit unsigned integer blue value. + public ushort RawB { get => b; set => b = value; } + + /// Creates a new ColorR16G16B16 with the specified raw values. + public ColorR16G16B16(ushort r, ushort g, ushort b) + { + this.r = r; + this.g = g; + this.b = b; + } + + /// Creates a new ColorR16G16B16 with the specified normalized values. + public ColorR16G16B16(float r, float g, float b) + { + this.r = FloatToUnorm16(r); + this.g = FloatToUnorm16(g); + this.b = FloatToUnorm16(b); + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() => new ColorRgbaFloat(R, G, B, 1); + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + B = color.b; + } + + /// + public override string ToString() => $"R16G16B16[R={R:F3}, G={G:F3}, B={B:F3}]"; + + /// + public override bool Equals(object? obj) => obj is ColorR16G16B16 other && Equals(other); + + /// + public bool Equals(ColorR16G16B16 other) => r == other.r && g == other.g && b == other.b; + + /// + public override int GetHashCode() => HashCode.Combine(r, g, b); + + /// Compares two for equality. + public static bool operator ==(ColorR16G16B16 left, ColorR16G16B16 right) => left.Equals(right); + + /// Compares two for inequality. + public static bool operator !=(ColorR16G16B16 left, ColorR16G16B16 right) => !left.Equals(right); +} + +/// +/// Raw 32-bit unsigned integer three-channel format. 32 bits for Red, 32 bits for Green, 32 bits for Blue. +/// Float accessors perform a direct integer-to-float cast with no normalization. +/// +[StructLayout(LayoutKind.Sequential)] +public struct ColorR32G32B32UI : IColorRgb +{ + /// The 32-bit unsigned integer red value. + public uint r; + /// The 32-bit unsigned integer green value. + public uint g; + /// The 32-bit unsigned integer blue value. + public uint b; + + /// Gets or sets the red component as a float (direct cast, not normalized). + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (float)r; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = (uint)value; + } + + /// Gets or sets the green component as a float (direct cast, not normalized). + public float G + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (float)g; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => g = (uint)value; + } + + /// Gets or sets the blue component as a float (direct cast, not normalized). + public float B + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (float)b; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => b = (uint)value; + } + + /// Gets or sets the raw 32-bit unsigned integer red value. + public uint RawR { get => r; set => r = value; } + /// Gets or sets the raw 32-bit unsigned integer green value. + public uint RawG { get => g; set => g = value; } + /// Gets or sets the raw 32-bit unsigned integer blue value. + public uint RawB { get => b; set => b = value; } + + /// Creates a new ColorR32G32B32UI with the specified raw values. + public ColorR32G32B32UI(uint r, uint g, uint b) + { + this.r = r; + this.g = g; + this.b = b; + } + + /// Creates a new ColorR32G32B32UI with the specified float values (cast to uint). + public ColorR32G32B32UI(float r, float g, float b) + { + this.r = (uint)r; + this.g = (uint)g; + this.b = (uint)b; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() => new ColorRgbaFloat((float)r, (float)g, (float)b, 1); + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + r = (uint)color.r; + g = (uint)color.g; + b = (uint)color.b; + } + + /// + public override string ToString() => $"R32G32B32UI[R={r}, G={g}, B={b}]"; + + /// + public override bool Equals(object? obj) => obj is ColorR32G32B32UI other && Equals(other); + + /// + public bool Equals(ColorR32G32B32UI other) => r == other.r && g == other.g && b == other.b; + + /// + public override int GetHashCode() => HashCode.Combine(r, g, b); + + /// Compares two for equality. + public static bool operator ==(ColorR32G32B32UI left, ColorR32G32B32UI right) => left.Equals(right); + + /// Compares two for inequality. + public static bool operator !=(ColorR32G32B32UI left, ColorR32G32B32UI right) => !left.Equals(right); +} + +public struct ColorRgbHalf : IColorRgb +{ + public Half r, g, b; + + public ColorRgbHalf(float r, float g, float b) + { + this.r = (Half)r; + this.g = (Half)g; + this.b = (Half)b; + } + + public ColorRgbHalf(Half r, Half g, Half b) + { + this.r = r; + this.g = g; + this.b = b; + } + + /// + public bool Equals(ColorRgbHalf other) + { + return r.Equals(other.r) && g.Equals(other.g) && b.Equals(other.b); + } + + /// + public override bool Equals(object obj) + { + return obj is ColorRgbHalf other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(r, g, b); + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat((float)r, (float)g, (float)b); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + color.ClampToHalf(); + r = (Half)(color.r); + g = (Half)(color.g); + b = (Half)(color.b); + } + + /// + /// Gets or sets the red component as a floating-point value. + /// + public float R + { + get => (float)r; + set => r = (Half)value; + } + + /// + /// Gets or sets the green component as a floating-point value. + /// + public float G + { + get => (float)g; + set => g = (Half)value; + } + + /// + /// Gets or sets the blue component as a floating-point value. + /// + public float B + { + get => (float)b; + set => b = (Half)value; + } + + /// + /// Gets or sets the raw red component value. + /// + public Half RawR + { + get => r; + set => r = value; + } + + /// + /// Gets or sets the raw green component value. + /// + public Half RawG + { + get => g; + set => g = value; + } + + /// + /// Gets or sets the raw blue component value. + /// + public Half RawB + { + get => b; + set => b = value; + } +} diff --git a/BCnEnc.Net/Shared/Colors/ColorsRGBA.cs b/BCnEnc.Net/Shared/Colors/ColorsRGBA.cs new file mode 100644 index 0000000..ed37370 --- /dev/null +++ b/BCnEnc.Net/Shared/Colors/ColorsRGBA.cs @@ -0,0 +1,1036 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace BCnEncoder.Shared.Colors; + + +using static BCnEncoder.Shared.Colors.ColorBitConversionHelpers; + +[StructLayout(LayoutKind.Sequential)] +public struct ColorRgba32 : IColorRgba +{ + public byte r, g, b, a; + + public ColorRgba32(byte r, byte g, byte b, byte a) + { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + public ColorRgba32(byte r, byte g, byte b) + { + this.r = r; + this.g = g; + this.b = b; + this.a = 255; + } + + public bool Equals(ColorRgba32 other) + { + return r == other.r && + g == other.g && + b == other.b && + a == other.a; + } + + public override bool Equals(object obj) + { + return obj is ColorRgba32 other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = r.GetHashCode(); + hashCode = (hashCode * 397) ^ g.GetHashCode(); + hashCode = (hashCode * 397) ^ b.GetHashCode(); + hashCode = (hashCode * 397) ^ a.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(ColorRgba32 left, ColorRgba32 right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorRgba32 left, ColorRgba32 right) + { + return !left.Equals(right); + } + + public static ColorRgba32 operator +(ColorRgba32 left, ColorRgba32 right) + { + return new ColorRgba32( + ByteHelper.ClampToByte(left.r + right.r), + ByteHelper.ClampToByte(left.g + right.g), + ByteHelper.ClampToByte(left.b + right.b), + ByteHelper.ClampToByte(left.a + right.a)); + } + + public static ColorRgba32 operator -(ColorRgba32 left, ColorRgba32 right) + { + return new ColorRgba32( + ByteHelper.ClampToByte(left.r - right.r), + ByteHelper.ClampToByte(left.g - right.g), + ByteHelper.ClampToByte(left.b - right.b), + ByteHelper.ClampToByte(left.a - right.a)); + } + + public static ColorRgba32 operator /(ColorRgba32 left, double right) + { + return new ColorRgba32( + ByteHelper.ClampToByte((int)(left.r / right)), + ByteHelper.ClampToByte((int)(left.g / right)), + ByteHelper.ClampToByte((int)(left.b / right)), + ByteHelper.ClampToByte((int)(left.a / right)) + ); + } + + public static ColorRgba32 operator *(ColorRgba32 left, double right) + { + return new ColorRgba32( + ByteHelper.ClampToByte((int)(left.r * right)), + ByteHelper.ClampToByte((int)(left.g * right)), + ByteHelper.ClampToByte((int)(left.b * right)), + ByteHelper.ClampToByte((int)(left.a * right)) + ); + } + + /// + /// Component-wise left shift + /// + public static ColorRgba32 operator <<(ColorRgba32 left, int right) + { + return new ColorRgba32( + ByteHelper.ClampToByte(left.r << right), + ByteHelper.ClampToByte(left.g << right), + ByteHelper.ClampToByte(left.b << right), + ByteHelper.ClampToByte(left.a << right) + ); + } + + /// + /// Component-wise right shift + /// + public static ColorRgba32 operator >> (ColorRgba32 left, int right) + { + return new ColorRgba32( + ByteHelper.ClampToByte(left.r >> right), + ByteHelper.ClampToByte(left.g >> right), + ByteHelper.ClampToByte(left.b >> right), + ByteHelper.ClampToByte(left.a >> right) + ); + } + + /// + /// Component-wise bitwise OR operation + /// + public static ColorRgba32 operator |(ColorRgba32 left, ColorRgba32 right) + { + return new ColorRgba32( + ByteHelper.ClampToByte(left.r | right.r), + ByteHelper.ClampToByte(left.g | right.g), + ByteHelper.ClampToByte(left.b | right.b), + ByteHelper.ClampToByte(left.a | right.a) + ); + } + + /// + /// Component-wise bitwise OR operation + /// + public static ColorRgba32 operator |(ColorRgba32 left, int right) + { + return new ColorRgba32( + ByteHelper.ClampToByte(left.r | right), + ByteHelper.ClampToByte(left.g | right), + ByteHelper.ClampToByte(left.b | right), + ByteHelper.ClampToByte(left.a | right) + ); + } + + /// + /// Component-wise bitwise AND operation + /// + public static ColorRgba32 operator &(ColorRgba32 left, ColorRgba32 right) + { + return new ColorRgba32( + ByteHelper.ClampToByte(left.r & right.r), + ByteHelper.ClampToByte(left.g & right.g), + ByteHelper.ClampToByte(left.b & right.b), + ByteHelper.ClampToByte(left.a & right.a) + ); + } + + /// + /// Component-wise bitwise AND operation + /// + public static ColorRgba32 operator &(ColorRgba32 left, int right) + { + return new ColorRgba32( + ByteHelper.ClampToByte(left.r & right), + ByteHelper.ClampToByte(left.g & right), + ByteHelper.ClampToByte(left.b & right), + ByteHelper.ClampToByte(left.a & right) + ); + } + + public override string ToString() + { + return $"r : {r} g : {g} b : {b} a : {a}"; + } + + public readonly ColorRgbFloat ToRgbFloat() + { + return new ColorRgbFloat(this); + } + + /// + public readonly ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat( + Unorm8ToFloat(r), + Unorm8ToFloat(g), + Unorm8ToFloat(b), + Unorm8ToFloat(a) + ); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + r = FloatToUnorm8(color.r); + g = FloatToUnorm8(color.g); + b = FloatToUnorm8(color.b); + a = FloatToUnorm8(color.a); + } + + /// + /// Gets or sets the red component as a floating-point value. + /// + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm8ToFloat(r); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = FloatToUnorm8(value); + } + + /// + /// Gets or sets the green component as a floating-point value. + /// + public float G + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm8ToFloat(g); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => g = FloatToUnorm8(value); + } + + /// + /// Gets or sets the blue component as a floating-point value. + /// + public float B + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm8ToFloat(b); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => b = FloatToUnorm8(value); + } + + /// + /// Gets or sets the alpha component as a floating-point value. + /// + public float A + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm8ToFloat(a); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => a = FloatToUnorm8(value); + } + + /// + /// Gets or sets the raw red component value. + /// + public byte RawR + { + get => r; + set => r = value; + } + + /// + /// Gets or sets the raw green component value. + /// + public byte RawG + { + get => g; + set => g = value; + } + + /// + /// Gets or sets the raw blue component value. + /// + public byte RawB + { + get => b; + set => b = value; + } + + /// + /// Gets or sets the raw alpha component value. + /// + public byte RawA + { + get => a; + set => a = value; + } +} + +[StructLayout(LayoutKind.Sequential)] +public struct ColorBgra32 : IColorRgba +{ + public byte b, g, r, a; + + public ColorBgra32(byte b, byte g, byte r) + { + this.b = b; + this.g = g; + this.r = r; + this.a = 255; + } + + public ColorBgra32(byte b, byte g, byte r, byte a) + { + this.b = b; + this.g = g; + this.r = r; + this.a = a; + } + + /// + public bool Equals(ColorBgra32 other) + { + return b == other.b && g == other.g && r == other.r && a == other.a; + } + + /// + public override bool Equals(object obj) + { + return obj is ColorBgra32 other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(b, g, r, a); + } + + /// + public readonly ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat( + Unorm8ToFloat(r), + Unorm8ToFloat(g), + Unorm8ToFloat(b), + Unorm8ToFloat(a) + ); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + r = FloatToUnorm8(color.r); + g = FloatToUnorm8(color.g); + b = FloatToUnorm8(color.b); + a = FloatToUnorm8(color.a); + } + + /// + /// Gets or sets the red component as a floating-point value. + /// + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm8ToFloat(r); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = FloatToUnorm8(value); + } + + /// + /// Gets or sets the green component as a floating-point value. + /// + public float G + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm8ToFloat(g); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => g = FloatToUnorm8(value); + } + + /// + /// Gets or sets the blue component as a floating-point value. + /// + public float B + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm8ToFloat(b); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => b = FloatToUnorm8(value); + } + + /// + /// Gets or sets the alpha component as a floating-point value. + /// + public float A + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm8ToFloat(a); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => a = FloatToUnorm8(value); + } + + /// + /// Gets or sets the raw red component value. + /// + public byte RawR + { + get => r; + set => r = value; + } + + /// + /// Gets or sets the raw green component value. + /// + public byte RawG + { + get => g; + set => g = value; + } + + /// + /// Gets or sets the raw blue component value. + /// + public byte RawB + { + get => b; + set => b = value; + } + + /// + /// Gets or sets the raw alpha component value. + /// + public byte RawA + { + get => a; + set => a = value; + } +} + +public struct ColorRgbaFloat : IColorRgba +{ + public float r, g, b, a; + + public ColorRgbaFloat(float r, float g, float b, float a) + { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + public ColorRgbaFloat(ColorRgbFloat other) + { + this.r = other.r; + this.g = other.g; + this.b = other.b; + this.a = 1; + } + + public ColorRgbaFloat(float r, float g, float b) + { + this.r = r; + this.g = g; + this.b = b; + this.a = 1; + } + + public ColorRgbaFloat(Vector4 other) + { + this.r = other.X; + this.g = other.Y; + this.b = other.Z; + this.a = other.W; + } + + public readonly bool Equals(ColorRgbaFloat other) + { + return r == other.r && g == other.g && b == other.b && a == other.a; + } + + public readonly override bool Equals(object obj) + { + return obj is ColorRgbaFloat other && Equals(other); + } + + public readonly override int GetHashCode() + { + unchecked + { + var hashCode = r.GetHashCode(); + hashCode = (hashCode * 397) ^ g.GetHashCode(); + hashCode = (hashCode * 397) ^ b.GetHashCode(); + hashCode = (hashCode * 397) ^ a.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(ColorRgbaFloat left, ColorRgbaFloat right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorRgbaFloat left, ColorRgbaFloat right) + { + return !left.Equals(right); + } + + public static ColorRgbaFloat operator +(ColorRgbaFloat left, ColorRgbaFloat right) + { + return new ColorRgbaFloat( + left.r + right.r, + left.g + right.g, + left.b + right.b, + left.a + right.a); + } + + public static ColorRgbaFloat operator -(ColorRgbaFloat left, ColorRgbaFloat right) + { + return new ColorRgbaFloat( + left.r - right.r, + left.g - right.g, + left.b - right.b, + left.a - right.a); + } + + public static ColorRgbaFloat operator /(ColorRgbaFloat left, float right) + { + return new ColorRgbaFloat( + left.r / right, + left.g / right, + left.b / right, + left.a / right + ); + } + + public static ColorRgbaFloat operator *(ColorRgbaFloat left, float right) + { + return new ColorRgbaFloat( + left.r * right, + left.g * right, + left.b * right, + left.a * right + ); + } + + public static ColorRgbaFloat operator *(float left, ColorRgbaFloat right) + { + return new ColorRgbaFloat( + right.r * left, + right.g * left, + right.b * left, + right.a * left + ); + } + + public readonly override string ToString() + { + return $"r : {r:0.00000} g : {g:0.00000} b : {b:0.00000} a : {a:0.00000}"; + } + + internal void ClampToHalf() + { + if (r < (float)Half.MinValue) r = (float)Half.MinValue; + else if (g > (float)Half.MaxValue) g = (float)Half.MaxValue; + if (b < (float)Half.MinValue) b = (float)Half.MinValue; + else if (r > (float)Half.MaxValue) r = (float)Half.MaxValue; + if (g < (float)Half.MinValue) g = (float)Half.MinValue; + else if (b > (float)Half.MaxValue) b = (float)Half.MaxValue; + if (a < (float)Half.MinValue) a = (float)Half.MinValue; + else if (a > (float)Half.MaxValue) a = (float)Half.MaxValue; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return this; + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + r = color.r; + g = color.g; + b = color.b; + a = color.a; + } + + public readonly Vector3 ToVector3() + { + return new Vector3(r, g, b); + } + + public readonly Vector4 ToVector4() + { + return new Vector4(r, g, b, a); + } + + public static implicit operator ColorRgbFloat(ColorRgbaFloat c) => new(c.r, c.g, c.b); + + /// + /// Gets or sets the red component as a floating-point value. + /// + public float R + { + readonly get => r; + set => r = value; + } + + /// + /// Gets or sets the green component as a floating-point value. + /// + public float G + { + readonly get => g; + set => g = value; + } + + /// + /// Gets or sets the blue component as a floating-point value. + /// + public float B + { + readonly get => b; + set => b = value; + } + + /// + /// Gets or sets the alpha component as a floating-point value. + /// + public float A + { + readonly get => a; + set => a = value; + } + + /// + /// Gets or sets the raw red component value. + /// + public float RawR + { + readonly get => r; + set => r = value; + } + + /// + /// Gets or sets the raw green component value. + /// + public float RawG + { + readonly get => g; + set => g = value; + } + + /// + /// Gets or sets the raw blue component value. + /// + public float RawB + { + readonly get => b; + set => b = value; + } + + /// + /// Gets or sets the raw alpha component value. + /// + public float RawA + { + readonly get => a; + set => a = value; + } +} + +public struct ColorRgbaHalf : IColorRgba +{ + public Half r, g, b, a; + + public ColorRgbaHalf(float r, float g, float b) + { + this.r = (Half)r; + this.g = (Half)g; + this.b = (Half)b; + a = (Half)1; + } + + public ColorRgbaHalf(float r, float g, float b, float a) + { + this.r = (Half)r; + this.g = (Half)g; + this.b = (Half)b; + this.a = (Half)a; + } + + public ColorRgbaHalf(Half r, Half g, Half b) + { + this.r = r; + this.g = g; + this.b = b; + a = (Half)1; + } + + public ColorRgbaHalf(Half r, Half g, Half b, Half a) + { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + return new ColorRgbaFloat((float)r, (float)g, (float)b, (float)a); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + color.ClampToHalf(); + r = (Half)color.r; + g = (Half)color.g; + b = (Half)color.b; + a = (Half)color.a; + } + + /// + public bool Equals(ColorRgbaHalf other) + { + return r.Equals(other.r) && g.Equals(other.g) && b.Equals(other.b) && a.Equals(other.a); + } + + /// + public override bool Equals(object obj) + { + return obj is ColorRgbaHalf other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(r, g, b, a); + } + + /// + /// Gets or sets the red component as a floating-point value. + /// + public float R + { + get => (float)r; + set => r = (Half)value; + } + + /// + /// Gets or sets the green component as a floating-point value. + /// + public float G + { + get => (float)g; + set => g = (Half)value; + } + + /// + /// Gets or sets the blue component as a floating-point value. + /// + public float B + { + get => (float)b; + set => b = (Half)value; + } + + /// + /// Gets or sets the alpha component as a floating-point value. + /// + public float A + { + get => (float)a; + set => a = (Half)value; + } + + /// + /// Gets or sets the raw red component value. + /// + public Half RawR + { + get => r; + set => r = value; + } + + /// + /// Gets or sets the raw green component value. + /// + public Half RawG + { + get => g; + set => g = value; + } + + /// + /// Gets or sets the raw blue component value. + /// + public Half RawB + { + get => b; + set => b = value; + } + + /// + /// Gets or sets the raw alpha component value. + /// + public Half RawA + { + get => a; + set => a = value; + } +} + +/// +/// Raw 16-bit unsigned normalized four-channel format. 16 bits each for Red, Green, Blue, Alpha. +/// +[StructLayout(LayoutKind.Sequential)] +public struct ColorR16G16B16A16 : IColorRgba +{ + /// The 16-bit unsigned integer red value. + public ushort r; + /// The 16-bit unsigned integer green value. + public ushort g; + /// The 16-bit unsigned integer blue value. + public ushort b; + /// The 16-bit unsigned integer alpha value. + public ushort a; + + /// Gets or sets the red component as a normalized floating-point value [0, 1]. + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm16ToFloat(r); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = FloatToUnorm16(value); + } + + /// Gets or sets the green component as a normalized floating-point value [0, 1]. + public float G + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm16ToFloat(g); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => g = FloatToUnorm16(value); + } + + /// Gets or sets the blue component as a normalized floating-point value [0, 1]. + public float B + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm16ToFloat(b); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => b = FloatToUnorm16(value); + } + + /// Gets or sets the alpha component as a normalized floating-point value [0, 1]. + public float A + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unorm16ToFloat(a); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => a = FloatToUnorm16(value); + } + + /// Gets or sets the raw 16-bit unsigned integer red value. + public ushort RawR { get => r; set => r = value; } + /// Gets or sets the raw 16-bit unsigned integer green value. + public ushort RawG { get => g; set => g = value; } + /// Gets or sets the raw 16-bit unsigned integer blue value. + public ushort RawB { get => b; set => b = value; } + /// Gets or sets the raw 16-bit unsigned integer alpha value. + public ushort RawA { get => a; set => a = value; } + + /// Creates a new ColorR16G16B16A16 with the specified raw values. + public ColorR16G16B16A16(ushort r, ushort g, ushort b, ushort a) + { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + /// Creates a new ColorR16G16B16A16 with the specified raw RGB values; alpha is set to max (65535). + public ColorR16G16B16A16(ushort r, ushort g, ushort b) + { + this.r = r; + this.g = g; + this.b = b; + this.a = ushort.MaxValue; + } + + /// Creates a new ColorR16G16B16A16 with the specified normalized values. + public ColorR16G16B16A16(float r, float g, float b, float a) + { + this.r = FloatToUnorm16(r); + this.g = FloatToUnorm16(g); + this.b = FloatToUnorm16(b); + this.a = FloatToUnorm16(a); + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() => new ColorRgbaFloat(R, G, B, A); + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + R = color.r; + G = color.g; + B = color.b; + A = color.a; + } + + /// + public override string ToString() => $"R16G16B16A16[R={R:F3}, G={G:F3}, B={B:F3}, A={A:F3}]"; + + /// + public override bool Equals(object? obj) => obj is ColorR16G16B16A16 other && Equals(other); + + /// + public bool Equals(ColorR16G16B16A16 other) => r == other.r && g == other.g && b == other.b && a == other.a; + + /// + public override int GetHashCode() => HashCode.Combine(r, g, b, a); + + /// Compares two for equality. + public static bool operator ==(ColorR16G16B16A16 left, ColorR16G16B16A16 right) => left.Equals(right); + + /// Compares two for inequality. + public static bool operator !=(ColorR16G16B16A16 left, ColorR16G16B16A16 right) => !left.Equals(right); +} + +/// +/// Raw 32-bit unsigned integer four-channel format. 32 bits each for Red, Green, Blue, Alpha. +/// Float accessors perform a direct integer-to-float cast with no normalization. +/// +[StructLayout(LayoutKind.Sequential)] +public struct ColorR32G32B32A32UI : IColorRgba +{ + /// The 32-bit unsigned integer red value. + public uint r; + /// The 32-bit unsigned integer green value. + public uint g; + /// The 32-bit unsigned integer blue value. + public uint b; + /// The 32-bit unsigned integer alpha value. + public uint a; + + /// Gets or sets the red component as a float (direct cast, not normalized). + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (float)r; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => r = (uint)value; + } + + /// Gets or sets the green component as a float (direct cast, not normalized). + public float G + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (float)g; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => g = (uint)value; + } + + /// Gets or sets the blue component as a float (direct cast, not normalized). + public float B + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (float)b; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => b = (uint)value; + } + + /// Gets or sets the alpha component as a float (direct cast, not normalized). + public float A + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (float)a; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => a = (uint)value; + } + + /// Gets or sets the raw 32-bit unsigned integer red value. + public uint RawR { get => r; set => r = value; } + /// Gets or sets the raw 32-bit unsigned integer green value. + public uint RawG { get => g; set => g = value; } + /// Gets or sets the raw 32-bit unsigned integer blue value. + public uint RawB { get => b; set => b = value; } + /// Gets or sets the raw 32-bit unsigned integer alpha value. + public uint RawA { get => a; set => a = value; } + + /// Creates a new ColorR32G32B32A32UI with the specified raw values. + public ColorR32G32B32A32UI(uint r, uint g, uint b, uint a) + { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + /// Creates a new ColorR32G32B32A32UI with the specified raw RGB values; alpha is set to max. + public ColorR32G32B32A32UI(uint r, uint g, uint b) + { + this.r = r; + this.g = g; + this.b = b; + this.a = uint.MaxValue; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() => new ColorRgbaFloat((float)r, (float)g, (float)b, (float)a); + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + r = (uint)color.r; + g = (uint)color.g; + b = (uint)color.b; + a = (uint)color.a; + } + + /// + public override string ToString() => $"R32G32B32A32UI[R={r}, G={g}, B={b}, A={a}]"; + + /// + public override bool Equals(object? obj) => obj is ColorR32G32B32A32UI other && Equals(other); + + /// + public bool Equals(ColorR32G32B32A32UI other) => r == other.r && g == other.g && b == other.b && a == other.a; + + /// + public override int GetHashCode() => HashCode.Combine(r, g, b, a); + + /// Compares two for equality. + public static bool operator ==(ColorR32G32B32A32UI left, ColorR32G32B32A32UI right) => left.Equals(right); + + /// Compares two for inequality. + public static bool operator !=(ColorR32G32B32A32UI left, ColorR32G32B32A32UI right) => !left.Equals(right); +} diff --git a/BCnEnc.Net/Shared/Colors/ColorsSharedExponent.cs b/BCnEnc.Net/Shared/Colors/ColorsSharedExponent.cs new file mode 100644 index 0000000..bdaa3bb --- /dev/null +++ b/BCnEnc.Net/Shared/Colors/ColorsSharedExponent.cs @@ -0,0 +1,296 @@ +using System; + +namespace BCnEncoder.Shared.Colors; + +public struct ColorRgbe : IColor +{ + public byte r; + public byte g; + public byte b; + public byte e; + + public ColorRgbe(byte r, byte g, byte b, byte e) + { + this.r = r; + this.g = g; + this.b = b; + this.e = e; + } + + public bool Equals(ColorRgbe other) + { + return r == other.r && g == other.g && b == other.b && e == other.e; + } + + public override bool Equals(object obj) + { + return obj is ColorRgbe other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = r.GetHashCode(); + hashCode = (hashCode * 397) ^ g.GetHashCode(); + hashCode = (hashCode * 397) ^ b.GetHashCode(); + hashCode = (hashCode * 397) ^ e.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(ColorRgbe left, ColorRgbe right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorRgbe left, ColorRgbe right) + { + return !left.Equals(right); + } + + public override string ToString() + { + return $"{nameof(r)}: {r}, {nameof(g)}: {g}, {nameof(b)}: {b}, {nameof(e)}: {e}"; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + if (e == 0) + { + return new ColorRgbaFloat(0, 0, 0); + } + else + { + var (rf, gf, bf) = ColorUtils.SharedExponentToRgb((r, g, b, e), 8, 128); + return new ColorRgbaFloat(rf, gf, bf); + } + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + var max = MathF.Max(color.b, MathF.Max(color.g, color.r)); + if (max <= 1e-32f) + { + r = g = b = e = 0; + } + else + { + var (ri, gi, bi, ei) = ColorUtils.RgbToSharedExponent( + red: color.r, + green:color.g, + blue:color.b, + mantissaBits: 8, + bias: 128, + exponentMax: 255); + + r = (byte)ri; + g = (byte)gi; + b = (byte)bi; + e = (byte)ei; + } + } +} + +public struct ColorXyze : IColor +{ + public byte x; + public byte y; + public byte z; + public byte e; + + public ColorXyze(byte x, byte y, byte z, byte e) + { + this.x = x; + this.y = y; + this.z = z; + this.e = e; + } + + public bool Equals(ColorXyze other) + { + return x == other.x && y == other.y && z == other.z && e == other.e; + } + + public override bool Equals(object obj) + { + return obj is ColorXyze other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = x.GetHashCode(); + hashCode = (hashCode * 397) ^ y.GetHashCode(); + hashCode = (hashCode * 397) ^ z.GetHashCode(); + hashCode = (hashCode * 397) ^ e.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(ColorXyze left, ColorXyze right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorXyze left, ColorXyze right) + { + return !left.Equals(right); + } + + public override string ToString() + { + return $"{nameof(x)}: {x}, {nameof(y)}: {y}, {nameof(z)}: {z}, {nameof(e)}: {e}"; + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + if (e == 0) + { + return new ColorRgbaFloat(0, 0, 0); + } + else + { + // First convert to XYZ using the shared exponent + var (xf, yf, zf) = ColorUtils.SharedExponentToRgb((x, y, z, e), 8, 128); + + // Then convert from XYZ to RGB + return new ColorXyz(xf, yf, zf).ToColorRgbaFloat(); + } + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + // First convert from RGB to XYZ + ColorXyz xyz = color.As(); + + // Handle zero case + if (xyz.x <= 1e-32f && xyz.y <= 1e-32f && xyz.z <= 1e-32f) + { + x = y = z = e = 0; + return; + } + + // Use shared exponent helper to convert from XYZ to shared exponent format + var (xi, yi, zi, ei) = ColorUtils.RgbToSharedExponent( + red: xyz.x, + green: xyz.y, + blue: xyz.z, + mantissaBits: 8, + bias: 128, + exponentMax: 255); + + x = (byte)xi; + y = (byte)yi; + z = (byte)zi; + e = (byte)ei; + } +} + +/// +/// Represents a packed R9G9B9E5 shared exponent color. +/// This format uses 9 bits each for red, green, and blue mantissas, with a shared 5-bit exponent. +/// +public struct ColorR9G9B9E5 : IColor +{ + // The packed 32-bit value containing all components + public uint packedValue; + + /// + /// Creates a new ColorR9G9B9E5 from a 32-bit packed value. + /// + public ColorR9G9B9E5(uint packedValue) + { + this.packedValue = packedValue; + } + + /// + /// Creates a new ColorR9G9B9E5 from individual floating point values. + /// + public ColorR9G9B9E5(float r, float g, float b) + { + // Extract mantissas and shared exponent using ColorUtils helper + var (rm, gm, bm, exp) = ColorUtils.RgbToSharedExponent( + red: r, + green: g, + blue: b, + mantissaBits: 9, + bias: 15, + exponentMax: 31); + + // Clamp mantissas to 9 bits + rm = Math.Min(rm, 0x1FF); + gm = Math.Min(gm, 0x1FF); + bm = Math.Min(bm, 0x1FF); + + // Pack into 32-bit value + packedValue = rm | (gm << 9) | (bm << 18) | (exp << 27); + } + + /// + public ColorRgbaFloat ToColorRgbaFloat() + { + // No need to convert if all values are zero + if (packedValue == 0) + { + return new ColorRgbaFloat(0, 0, 0); + } + + // Extract components + uint rm = packedValue & 0x1FF; + uint gm = (packedValue >> 9) & 0x1FF; + uint bm = (packedValue >> 18) & 0x1FF; + uint exp = (packedValue >> 27) & 0x1F; + + // Convert using ColorUtils helper + var (rf, gf, bf) = ColorUtils.SharedExponentToRgb((rm, gm, bm, exp), 9, 15); + return new ColorRgbaFloat(rf, gf, bf); + } + + /// + public void FromColorRgbaFloat(ColorRgbaFloat color) + { + // Use constructor that handles the conversion + ColorR9G9B9E5 newColor = new ColorR9G9B9E5(color.r, color.g, color.b); + packedValue = newColor.packedValue; + } + + /// + public bool Equals(ColorR9G9B9E5 other) + { + return packedValue == other.packedValue; + } + + /// + public override bool Equals(object obj) + { + return obj is ColorR9G9B9E5 other && Equals(other); + } + + /// + public override int GetHashCode() + { + return packedValue.GetHashCode(); + } + + public static bool operator ==(ColorR9G9B9E5 left, ColorR9G9B9E5 right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorR9G9B9E5 left, ColorR9G9B9E5 right) + { + return !left.Equals(right); + } + + public override string ToString() + { + ColorRgbaFloat rgbaFloat = ToColorRgbaFloat(); + return $"R: {rgbaFloat.r}, G: {rgbaFloat.g}, B: {rgbaFloat.b}"; + } +} diff --git a/BCnEnc.Net/Shared/Colors/IColor.cs b/BCnEnc.Net/Shared/Colors/IColor.cs new file mode 100644 index 0000000..5ad7650 --- /dev/null +++ b/BCnEnc.Net/Shared/Colors/IColor.cs @@ -0,0 +1,341 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using CommunityToolkit.HighPerformance; +using ArgumentException = System.ArgumentException; + +namespace BCnEncoder.Shared.Colors; + +public interface IColor +{ + ColorRgbaFloat ToColorRgbaFloat(); + void FromColorRgbaFloat(ColorRgbaFloat color); +} + +public interface IColor : IColor, IEquatable + where TColor : unmanaged, IColor +{ + +} + +internal enum ColorConversionMode +{ + None, + LinearToSrgb, + SrgbToLinear +} + +public static class ColorExtensions +{ + + /// + /// Converts one color type to another. + /// + /// The source color. + /// Reference to the target color. + /// The type of the source color. + /// The type of the target color. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void To(this TFrom from, ref TTo to) + where TFrom : unmanaged, IColor + where TTo : unmanaged, IColor + { + if (from is not ColorRgbaFloat floatColor) + floatColor = from.ToColorRgbaFloat(); + + to.FromColorRgbaFloat(floatColor); + } + + /// + /// Converts a color from linear RGB to sRGB. + /// + /// The source color in linear RGB. + /// Reference to the target color in sRGB. + /// The type of the source color. + /// The type of the target color. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void ToSrgb(this TFrom from, ref TTo to) + where TFrom : unmanaged, IColor + where TTo : unmanaged, IColor + { + if (from is not ColorRgbaFloat floatColor) + floatColor = from.ToColorRgbaFloat(); + + floatColor = ColorSpace.LrgbToSrgb(floatColor); + + to.FromColorRgbaFloat(floatColor); + } + + /// + /// Converts a color from sRGB to linear RGB. + /// + /// The source color in sRGB. + /// Reference to the target color in linear RGB. + /// The type of the source color. + /// The type of the target color. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void ToLinear(this TFrom from, ref TTo to) + where TFrom : unmanaged, IColor + where TTo : unmanaged, IColor + { + if (from is not ColorRgbaFloat floatColor) + floatColor = from.ToColorRgbaFloat(); + + floatColor = ColorSpace.SrgbToLrgb(floatColor); + + to.FromColorRgbaFloat(floatColor); + } + + /// + /// Converts one color type to another. + /// + /// The type to convert to. + /// The color to convert. + /// A new instance of , which is a copy of . + public static TTo As(this IColor fromColor) + where TTo : unmanaged, IColor + { + var floatColor = fromColor.ToColorRgbaFloat(); + var dest = new TTo(); + dest.FromColorRgbaFloat(floatColor); + return dest; + } + + internal static TTo[] ConvertTo(this ReadOnlyMemory fromColor, ColorConversionMode mode) + where TFrom : unmanaged, IColor + where TTo : unmanaged, IColor + { + var fromSpan = fromColor.Span; + var toArray = new TTo[fromColor.Length]; + + ConvertTo(fromColor, toArray, mode); + + return toArray; + } + + internal static byte[] ConvertToAsBytes(this ReadOnlyMemory fromColor, ColorConversionMode mode) + where TFrom : unmanaged, IColor + where TTo : unmanaged, IColor + { + var toSize = Unsafe.SizeOf(); + + int bufferLen = fromColor.Length * toSize; + byte[] toBytes = new byte[bufferLen]; + + ConvertToAsBytes(fromColor, toBytes, mode); + + return toBytes; + } + + internal static byte[] InternalConvertToAsBytesFromBytes(ReadOnlyMemory fromBytes, ColorConversionMode mode) + where TFrom : unmanaged, IColor + where TTo : unmanaged, IColor + { + var toSize = Unsafe.SizeOf(); + var fromSize = Unsafe.SizeOf(); + + int bufferLen = fromBytes.Length / fromSize * toSize; + byte[] toBytes = new byte[bufferLen]; + + InternalConvertToAsBytesFromBytes(fromBytes, toBytes, mode); + + return toBytes; + } + + internal static byte[] InternalConvertToAsBytesFromBytes(ReadOnlyMemory fromBytes, CompressionFormat inFormat, + CompressionFormat outFormat, ColorConversionMode colorConversionMode) + { + var inPixelSize = inFormat.GetBytesPerBlock(); + var outPixelSize = outFormat.GetBytesPerBlock(); + + var bufferLen = fromBytes.Length / inPixelSize * outPixelSize; + var toBytes = new byte[bufferLen]; + + InternalConvertToAsBytesFromBytes(fromBytes, toBytes, inFormat, outFormat, colorConversionMode); + + return toBytes; + } + + internal static void ConvertTo(this ReadOnlyMemory fromColor, Memory toColor, ColorConversionMode mode) + where TFrom : unmanaged, IColor + where TTo : unmanaged, IColor + { + var fromSpan = fromColor.Span; + var toSpan = toColor.Span; + + if (fromSpan.Length != toSpan.Length) + throw new ArgumentException($"Invalid buffer size. Expecting {fromSpan.Length} elements, got {toSpan.Length} elements.", nameof(toColor)); + + for (var i = 0; i < fromColor.Length; i++) + { + switch (mode) + { + case ColorConversionMode.SrgbToLinear: + fromSpan[i].ToLinear(ref toSpan[i]); break; + case ColorConversionMode.LinearToSrgb: + fromSpan[i].ToSrgb(ref toSpan[i]); break; + case ColorConversionMode.None: + fromSpan[i].To(ref toSpan[i]); break; + } + } + } + + internal static void ConvertToAsBytes(this ReadOnlyMemory fromColor, Memory toBytes, ColorConversionMode mode) + where TFrom : unmanaged, IColor + where TTo : unmanaged, IColor + { + var toSize = Unsafe.SizeOf(); + var fromSpan = fromColor.Span; + if (toBytes.Length != fromSpan.Length * toSize) + throw new ArgumentException($"Invalid buffer size. Expecting {fromSpan.Length * toSize} bytes, got {toBytes.Length} bytes.", nameof(toBytes)); + + var toSpan = toBytes.Span.Cast(); + + for (var i = 0; i < fromColor.Length; i++) + { + switch (mode) + { + case ColorConversionMode.SrgbToLinear: + fromSpan[i].ToLinear(ref toSpan[i]); break; + case ColorConversionMode.LinearToSrgb: + fromSpan[i].ToSrgb(ref toSpan[i]); break; + case ColorConversionMode.None: + fromSpan[i].To(ref toSpan[i]); break; + } + } + } + + internal static void InternalConvertToAsBytesFromBytes(ReadOnlyMemory fromBytes, Memory toBytes, ColorConversionMode mode) + where TFrom : unmanaged, IColor + where TTo : unmanaged, IColor + { + var toSize = Unsafe.SizeOf(); + var fromSpan = fromBytes.Cast().Span; + if (toBytes.Length != fromSpan.Length * toSize) + throw new ArgumentException($"Invalid buffer size. Expecting {fromSpan.Length * toSize} bytes, got {toBytes.Length} bytes.", nameof(toBytes)); + + var toSpan = toBytes.Span.Cast(); + + for (var i = 0; i < fromSpan.Length; i++) + { + switch (mode) + { + case ColorConversionMode.SrgbToLinear: + fromSpan[i].ToLinear(ref toSpan[i]); break; + case ColorConversionMode.LinearToSrgb: + fromSpan[i].ToSrgb(ref toSpan[i]); break; + case ColorConversionMode.None: + fromSpan[i].To(ref toSpan[i]); break; + } + } + } + + internal static void InternalConvertToAsBytesFromBytes(ReadOnlyMemory fromBytes, Memory toBytes, CompressionFormat inFormat, + CompressionFormat outFormat, ColorConversionMode colorConversionMode) + { + var inPixelType = inFormat.GetPixelType(); + var outPixelType = outFormat.GetPixelType(); + var method = typeof(ColorExtensions) + .GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static) + .First(x => x.Name == nameof(InternalConvertToAsBytesFromBytes) && + x.GetParameters().Length == 3); + + var genericMethod = method.MakeGenericMethod(inPixelType, outPixelType); + + genericMethod.Invoke(null, new object[] { fromBytes, toBytes, colorConversionMode }); + } + + public static byte[] CopyAsBytes(this ReadOnlyMemory fromColor) + where TFrom : unmanaged, IColor + { + var toSize = Unsafe.SizeOf(); + var returnArray = new byte[fromColor.Length * toSize]; + var toSpan = returnArray.AsSpan().Cast(); + fromColor.Span.CopyTo(toSpan); + return returnArray; + } + + public static byte[] CopyAsBytes(this ReadOnlyMemory2D fromColor) + where TFrom : unmanaged, IColor + { + var toSize = Unsafe.SizeOf(); + var returnArray = new byte[fromColor.Width * fromColor.Height * toSize]; + var toSpan = returnArray.AsSpan().Cast().AsSpan2D(fromColor.Height, fromColor.Width); + fromColor.Span.CopyTo(toSpan); + return returnArray; + } + + public static byte[] CopyAsBytes(this TFrom[] fromColor) + where TFrom : unmanaged, IColor + => CopyAsBytes(fromColor.AsMemory()); + + public static ReadOnlyMemory Flatten(this ReadOnlyMemory2D fromColor) + where TFrom : unmanaged, IColor + { + if (fromColor.TryGetMemory(out var returnMemory)) + { + return returnMemory; + } + + var returnArray = new TFrom[fromColor.Width * fromColor.Height]; + + fromColor.Span.CopyTo(returnArray.AsMemory().AsMemory2D(fromColor.Height, fromColor.Width).Span); + return returnArray; + } + + public static BCnTextureData AsBCnTextureData(this TFrom[] fromColor, int width, + int height, bool isSrgb) + where TFrom : unmanaged, IColor + => AsBCnTextureData(fromColor.AsMemory().AsMemory2D(height, width), isSrgb); + + public static BCnTextureData AsBCnTextureData(this ReadOnlyMemory fromColor, int width, + int height, bool isSrgb) + where TFrom : unmanaged, IColor + => AsBCnTextureData(fromColor.AsMemory2D(height, width), isSrgb); + + public static BCnTextureData AsBCnTextureData(this ReadOnlyMemory2D fromColor, bool isSrgb) + where TFrom : unmanaged, IColor + { + return BCnTextureData.FromSingle(FromColorType(typeof(TFrom), isSrgb), fromColor.Width, fromColor.Height, + fromColor.CopyAsBytes(), AlphaChannelHint.Unknown); + } + + private static CompressionFormat FromColorType(Type type, bool isSrgb) + { + if (type == typeof(ColorR8) && !isSrgb) + return CompressionFormat.R8; + if (type == typeof(ColorR8G8) && !isSrgb) + return CompressionFormat.R8G8; + if (type == typeof(ColorRgb24) && !isSrgb) + return CompressionFormat.Rgb24; + if (type == typeof(ColorRgb24) && isSrgb) + return CompressionFormat.Rgb24_sRGB; + if (type == typeof(ColorBgr24) && !isSrgb) + return CompressionFormat.Bgr24; + if (type == typeof(ColorBgr24) && isSrgb) + return CompressionFormat.Bgr24_sRGB; + if (type == typeof(ColorRgba32) && !isSrgb) + return CompressionFormat.Rgba32; + if (type == typeof(ColorRgba32) && isSrgb) + return CompressionFormat.Rgba32_sRGB; + if (type == typeof(ColorBgra32) && !isSrgb) + return CompressionFormat.Bgra32; + if (type == typeof(ColorBgra32) && isSrgb) + return CompressionFormat.Bgra32_sRGB; + if (type == typeof(ColorRgbaFloat) && !isSrgb) + return CompressionFormat.RgbaFloat; + if (type == typeof(ColorRgbaHalf) && !isSrgb) + return CompressionFormat.RgbaHalf; + if (type == typeof(ColorRgbFloat) && !isSrgb) + return CompressionFormat.RgbFloat; + if (type == typeof(ColorRgbHalf) && !isSrgb) + return CompressionFormat.RgbHalf; + if (type == typeof(ColorRgbe) && !isSrgb) + return CompressionFormat.Rgbe32; + if (type == typeof(ColorXyze) && !isSrgb) + return CompressionFormat.Xyze32; + + return CompressionFormat.Unknown; + } +} diff --git a/BCnEnc.Net/Shared/Colors/IColorChannels.cs b/BCnEnc.Net/Shared/Colors/IColorChannels.cs new file mode 100644 index 0000000..c6b09c0 --- /dev/null +++ b/BCnEnc.Net/Shared/Colors/IColorChannels.cs @@ -0,0 +1,87 @@ +using System.Numerics; + +namespace BCnEncoder.Shared.Colors; + + +/// +/// Interface for common operations on single-channel color formats for testing purposes. +/// +public interface IColorRed : IColor + where TColor : unmanaged, IColor + where TRaw : unmanaged, INumber +{ + /// + /// Gets or sets the red component as a floating-point value. + /// + float R { get; set; } + + /// + /// Gets or sets the red component as a raw value. + /// + TRaw RawR { get; set; } +} + +/// +/// Interface for common operations on dual-channel color formats for testing purposes. +/// +public interface IColorRedGreen : IColorRed + where TColor : unmanaged, IColor + where TRaw : unmanaged, INumber +{ + /// + /// Gets or sets the green component as a floating-point value. + /// + float G { get; set; } + + /// + /// Gets or sets the green component as a raw value. + /// + TRaw RawG { get; set; } +} + +/// +/// Interface for common operations on Rgb color formats for testing purposes. +/// +public interface IColorRgb : IColorRedGreen + where TColor : unmanaged, IColor + where TRaw : unmanaged, INumber +{ + /// + /// Gets or sets the blue component as a floating-point value. + /// + float B { get; set; } + + /// + /// Gets or sets the blue component as a raw value. + /// + TRaw RawB { get; set; } +} + +/// +/// Interface for common operations on Rgba color formats for testing purposes. +/// +public interface IColorRgba : IColorRgb + where TColor : unmanaged, IColor + where TRaw : unmanaged, INumber +{ + /// + /// Gets or sets the alpha component as a floating-point value. + /// + float A { get; set; } + + /// + /// Gets or sets the alpha component as a raw value. + /// + TRaw RawA { get; set; } +} + +/// +/// Interface for common operations on packed color formats for testing purposes. +/// +public interface IColorPacked +where TPacked : unmanaged, INumber, IBinaryInteger +{ + public static abstract PackedRgbaMask ChannelMask { get; } + + public TPacked Data { get; set; } +} diff --git a/BCnEnc.Net/Shared/Colors/PackedRgbaMask.cs b/BCnEnc.Net/Shared/Colors/PackedRgbaMask.cs new file mode 100644 index 0000000..e918d96 --- /dev/null +++ b/BCnEnc.Net/Shared/Colors/PackedRgbaMask.cs @@ -0,0 +1,353 @@ +using System; +using System.Collections.Generic; + +namespace BCnEncoder.Shared.Colors; + +using System.Numerics; + +/// +/// A color format mask, such as "B5G6R5" or "R10G10B10A2". +/// The order of components in the mask string is from least significant bit to most significant bit. +/// This is opposite to the Khronos VK_FORMAT_{component-format}_{numeric-format}_PACKxx convention. +/// For example, "B10G10R10A2" would place alpha in the most significant bits and blue in the least significant bits. +/// +public record PackedRgbaMask +{ + public int RedShift { get; init; } + public int GreenShift { get; init; } + public int BlueShift { get; init; } + public int AlphaShift { get; init; } + + public ulong RedMask { get; init; } + public ulong GreenMask { get; init; } + public ulong BlueMask { get; init; } + public ulong AlphaMask { get; init; } + + public ulong RedMaxValue { get; init; } + public ulong GreenMaxValue { get; init; } + public ulong BlueMaxValue { get; init; } + public ulong AlphaMaxValue { get; init; } + + public bool HasRed => RedMask != 0; + public bool HasGreen => GreenMask != 0; + public bool HasBlue => BlueMask != 0; + public bool HasAlpha => AlphaMask != 0; + + /// + /// Construct a RgbaMask from a mask string, such as "R8G8B8A8", or "R5G6B5A1". + /// The order and number of the components determines the shift and mask values. + /// Parsing is case-insensitive. + /// + /// A string in the format of component letter followed by bit count (e.g., "R8G8B8A8") + public PackedRgbaMask(string mask) + { + if (string.IsNullOrEmpty(mask)) + { + throw new ArgumentException("Mask string cannot be null or empty", nameof(mask)); + } + + RedMask = 0; + GreenMask = 0; + BlueMask = 0; + AlphaMask = 0; + RedShift = 0; + GreenShift = 0; + BlueShift = 0; + AlphaShift = 0; + RedMaxValue = 0; + GreenMaxValue = 0; + BlueMaxValue = 0; + AlphaMaxValue = 0; + + + // Now process the components from least significant to most significant bit + int currentShift = 0; + int i = 0; + + while (i < mask.Length) + { + char component = char.ToUpperInvariant(mask[i]); + i++; + + // Extract bit count + int bitCount = 0; + while (i < mask.Length && char.IsDigit(mask[i])) + { + bitCount = bitCount * 10 + (mask[i] - '0'); + i++; + } + + if (bitCount <= 0 || bitCount > 64) + { + throw new ArgumentException($"Invalid bit count {bitCount} for component {component}", nameof(mask)); + } + + // Calculate mask (2^bitCount - 1) + ulong componentMask = (1UL << bitCount) - 1; + ulong maxValue = componentMask; + + // Assign shift, mask, and max values based on component + switch (component) + { + case 'R': + RedShift = currentShift; + RedMask = componentMask << currentShift; + RedMaxValue = maxValue; + break; + case 'G': + GreenShift = currentShift; + GreenMask = componentMask << currentShift; + GreenMaxValue = maxValue; + break; + case 'B': + BlueShift = currentShift; + BlueMask = componentMask << currentShift; + BlueMaxValue = maxValue; + break; + case 'A': + AlphaShift = currentShift; + AlphaMask = componentMask << currentShift; + AlphaMaxValue = maxValue; + break; + } + + currentShift += bitCount; + } + } + + /// + /// Returns a string representation of the color mask, such as "R8G8B8A8" or "B10G10R10A2". + /// Components are returned in order from least significant bits to most significant bits. + /// + /// A string representation of the mask + public override string ToString() + { + var result = new System.Text.StringBuilder(); + + // Create a list of component information (component character, mask, shift) + var components = new List<(char Component, ulong Mask, int Shift)>(); + if (HasRed) components.Add(('R', RedMask, RedShift)); + if (HasGreen) components.Add(('G', GreenMask, GreenShift)); + if (HasBlue) components.Add(('B', BlueMask, BlueShift)); + if (HasAlpha) components.Add(('A', AlphaMask, AlphaShift)); + + // Sort by position in the packed value (lowest bit position first) + // For masks with contiguous bits, we can simply compare their numeric values + // as masks with higher bits set will have larger values + components.Sort((a, b) => a.Mask.CompareTo(b.Mask)); + + // Append components in sorted order + foreach (var (component, mask, shift) in components) + { + AppendComponentIfPresent(result, component, mask, shift); + } + + return result.ToString(); + } + + private void AppendComponentIfPresent(System.Text.StringBuilder builder, char component, ulong mask, int shift) + { + if (mask == 0) + { + return; + } + + // Calculate the bit count by finding the number of bits set in the mask after shifting back + ulong shiftedMask = mask >> shift; + int bitCount = 0; + while (shiftedMask > 0) + { + bitCount += (int)(shiftedMask & 1); + shiftedMask >>= 1; + } + + builder.Append(component); + builder.Append(bitCount); + } + + /// + /// Extracts the red component from a packed color value using this mask. + /// + /// The numeric type of the packed color + /// The packed color value to extract from + /// The extracted red component + public T GetRed(T packedColor) where T : INumber + { + if (!HasRed) + { + return T.Zero; + } + + ulong value = ulong.CreateChecked(packedColor); + ulong result = (value & RedMask) >> RedShift; + return T.CreateChecked(result); + } + + /// + /// Extracts the green component from a packed color value using this mask. + /// + /// The numeric type of the packed color + /// The packed color value to extract from + /// The extracted green component + public T GetGreen(T packedColor) where T : INumber + { + if (!HasGreen) + { + return T.Zero; + } + + ulong value = ulong.CreateChecked(packedColor); + ulong result = (value & GreenMask) >> GreenShift; + return T.CreateChecked(result); + } + + /// + /// Extracts the blue component from a packed color value using this mask. + /// + /// The numeric type of the packed color + /// The packed color value to extract from + /// The extracted blue component + public T GetBlue(T packedColor) where T : INumber + { + if (!HasBlue) + { + return T.Zero; + } + + ulong value = ulong.CreateChecked(packedColor); + ulong result = (value & BlueMask) >> BlueShift; + return T.CreateChecked(result); + } + + /// + /// Extracts the alpha component from a packed color value using this mask. + /// + /// The numeric type of the packed color + /// The packed color value to extract from + /// The extracted alpha component + public T GetAlpha(T packedColor) where T : INumber + { + if (!HasAlpha) + { + return T.Zero; + } + + ulong value = ulong.CreateChecked(packedColor); + ulong result = (value & AlphaMask) >> AlphaShift; + return T.CreateChecked(result); + } + + /// + /// Sets the red component in a packed color value using this mask. + /// + /// The numeric type of the packed color + /// The packed color value to modify + /// The red component value to set + public void SetRed(ref T packedColor, T red) where T : INumber + { + if (!HasRed) + { + return; + } + + ulong packedValue = ulong.CreateChecked(packedColor); + ulong componentValue = ulong.CreateChecked(red); + + // Clear the previous value for this component + packedValue &= ~RedMask; + + // Ensure component value is within mask range + componentValue = Math.Min(componentValue, RedMaxValue); + + // Set the new value + packedValue |= (componentValue << RedShift); + packedColor = T.CreateChecked(packedValue); + } + + /// + /// Sets the green component in a packed color value using this mask. + /// + /// The numeric type of the packed color + /// The packed color value to modify + /// The green component value to set + public void SetGreen(ref T packedColor, T green) where T : INumber + { + if (!HasGreen) + { + return; + } + + ulong packedValue = ulong.CreateChecked(packedColor); + ulong componentValue = ulong.CreateChecked(green); + + // Clear the previous value for this component + packedValue &= ~GreenMask; + + // Ensure component value is within mask range + componentValue = Math.Min(componentValue, GreenMaxValue); + + // Set the new value + packedValue |= (componentValue << GreenShift); + packedColor = T.CreateChecked(packedValue); + } + + /// + /// Sets the blue component in a packed color value using this mask. + /// + /// The numeric type of the packed color + /// The packed color value to modify + /// The blue component value to set + public void SetBlue(ref T packedColor, T blue) where T : INumber + { + if (!HasBlue) + { + return; + } + + ulong packedValue = ulong.CreateChecked(packedColor); + ulong componentValue = ulong.CreateChecked(blue); + + // Clear the previous value for this component + packedValue &= ~BlueMask; + + // Ensure component value is within mask range + componentValue = Math.Min(componentValue, BlueMaxValue); + + // Set the new value + packedValue |= (componentValue << BlueShift); + packedColor = T.CreateChecked(packedValue); + } + + /// + /// Sets the alpha component in a packed color value using this mask. + /// + /// The numeric type of the packed color + /// The packed color value to modify + /// The alpha component value to set + public void SetAlpha(ref T packedColor, T alpha) where T : INumber + { + if (!HasAlpha) + { + return; + } + + ulong packedValue = ulong.CreateChecked(packedColor); + ulong componentValue = ulong.CreateChecked(alpha); + + // Clear the previous value for this component + packedValue &= ~AlphaMask; + + // Ensure component value is within mask range + componentValue = Math.Min(componentValue, AlphaMaxValue); + + // Set the new value + packedValue |= (componentValue << AlphaShift); + packedColor = T.CreateChecked(packedValue); + } + + /// + /// Implicitly converts a string to a RgbaMask. + /// + /// A string in the format of component letter followed by bit count (e.g., "R8G8B8A8") + public static implicit operator PackedRgbaMask(string maskString) => new PackedRgbaMask(maskString); +} diff --git a/BCnEnc.Net/Shared/ComponentHelper.cs b/BCnEnc.Net/Shared/ComponentHelper.cs index 268778c..05a0a56 100644 --- a/BCnEnc.Net/Shared/ComponentHelper.cs +++ b/BCnEnc.Net/Shared/ComponentHelper.cs @@ -1,5 +1,6 @@ using System; using BCnEncoder.Encoder; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Shared { @@ -21,15 +22,45 @@ public static ColorRgba32 ComponentToColor(ColorComponent component, byte compon case ColorComponent.A: return new ColorRgba32(0, 0, 0, componentValue); - case ColorComponent.Luminance: + case ColorComponent.RGB: return new ColorRgba32(componentValue, componentValue, componentValue, 255); + case ColorComponent.None: + return new ColorRgba32(0, 0, 0, 0); + + default: + throw new InvalidOperationException("Unsupported component."); + } + } + + public static ColorRgbaFloat ComponentToColor(ColorComponent component, float componentValue) + { + switch (component) + { + case ColorComponent.R: + return new ColorRgbaFloat(componentValue, 0, 0, 1); + + case ColorComponent.G: + return new ColorRgbaFloat(0, componentValue, 0, 1); + + case ColorComponent.B: + return new ColorRgbaFloat(0, 0, componentValue, 1); + + case ColorComponent.A: + return new ColorRgbaFloat(0, 0, 0, componentValue); + + case ColorComponent.RGB: + return new ColorRgbaFloat(componentValue, componentValue, componentValue, 1); + + case ColorComponent.None: + return new ColorRgbaFloat(0, 0, 0, 0); + default: throw new InvalidOperationException("Unsupported component."); } } - public static ColorRgba32 ComponentToColor(ColorRgba32 existingColor, ColorComponent component, byte componentValue) + public static void ComponentToColor(ref ColorRgba32 existingColor, ColorComponent component, byte componentValue) { switch (component) { @@ -49,15 +80,52 @@ public static ColorRgba32 ComponentToColor(ColorRgba32 existingColor, ColorCompo existingColor.a = componentValue; break; - case ColorComponent.Luminance: - existingColor.r = existingColor.g = existingColor.b = componentValue; + case ColorComponent.RGB: + existingColor.r = componentValue; + existingColor.g = componentValue; + existingColor.b = componentValue; + break; + + case ColorComponent.None: break; default: throw new InvalidOperationException("Unsupported component."); } + } - return existingColor; + public static void ComponentToColor(ref ColorRgbaFloat existingColor, ColorComponent component, float componentValue) + { + switch (component) + { + case ColorComponent.R: + existingColor.r = componentValue; + break; + + case ColorComponent.G: + existingColor.g = componentValue; + break; + + case ColorComponent.B: + existingColor.b = componentValue; + break; + + case ColorComponent.A: + existingColor.a = componentValue; + break; + + case ColorComponent.RGB: + existingColor.r = componentValue; + existingColor.g = componentValue; + existingColor.b = componentValue; + break; + + case ColorComponent.None: + break; + + default: + throw new InvalidOperationException("Unsupported component."); + } } public static byte ColorToComponent(ColorRgba32 color, ColorComponent component) @@ -76,9 +144,6 @@ public static byte ColorToComponent(ColorRgba32 color, ColorComponent component) case ColorComponent.A: return color.a; - case ColorComponent.Luminance: - return (byte)(new ColorYCbCr(color).y * 255); - default: throw new InvalidOperationException("Unsupported component."); } diff --git a/BCnEnc.Net/Shared/CompressionFormat.cs b/BCnEnc.Net/Shared/CompressionFormat.cs index 9d7545d..2a8ed15 100644 --- a/BCnEnc.Net/Shared/CompressionFormat.cs +++ b/BCnEnc.Net/Shared/CompressionFormat.cs @@ -1,110 +1,733 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using BCnEncoder.Shared.Colors; + namespace BCnEncoder.Shared { - public enum CompressionFormat - { - /// - /// Raw unsigned byte 8-bit Luminance data - /// - R, - /// - /// Raw unsigned byte 16-bit RG data - /// - Rg, - /// - /// Raw unsigned byte 24-bit RGB data - /// - Rgb, - /// - /// Raw unsigned byte 32-bit RGBA data - /// - Rgba, - /// - /// Raw unsigned byte 32-bit BGRA data - /// - Bgra, - /// - /// BC1 / DXT1 with no alpha. Very widely supported and good compression ratio. + // ReSharper disable InconsistentNaming + /// + /// Identifies a texture compression or raw pixel format. + /// + /// Non-packed formats (e.g. , ) store each + /// component in its own byte. The component order in the name reflects memory layout: the first + /// component in the name is at the lowest memory address. + /// + /// Packed formats (e.g. , ) + /// pack multiple components into a single integer. The naming convention follows DirectX/DXGI: + /// the first component in the name occupies the least-significant bits of the integer + /// (LSB → MSB). This keeps packed and non-packed names consistent on little-endian hardware, + /// where the lowest-address byte is also the least-significant byte of any multi-byte value. + /// + /// Vulkan/OpenGL/KTX note: those APIs use the opposite convention for packed formats — + /// the first component in the name is the most-significant (MSB → LSB). When mapping + /// to or from Vulkan format names, the component order in the name will appear reversed. + /// For example, (R in bits [3:0], A in bits [15:12]) corresponds + /// to VK_FORMAT_A4B4G4R4_UNORM_PACK16. + /// + public enum CompressionFormat + { + /// + /// Raw unsigned byte 8-bit Luminance (or red) data. Pixel format . + /// + R8, + + /// + /// Raw signed byte 8-bit Luminance (or red) data. Pixel format . + /// + R8S, + + /// + /// Raw unsigned byte 16-bit RG data. Pixel format . + /// + R8G8, + + /// + /// Raw signed byte 16-bit RG data. Pixel format . + /// + R8G8S, + + /// + /// Raw unsigned byte 24-bit RGB data in linear colorspace. Pixel format . + /// + Rgb24, + + /// + /// Raw unsigned byte 24-bit RGB data in the sRGB colorspace. Pixel format . + /// + Rgb24_sRGB, + + /// + /// Raw unsigned byte 24-bit BGR data in linear colorspace. Pixel format . + /// Most texture formats do not support this format. Use instead. + /// + Bgr24, + + /// + /// Raw unsigned byte 24-bit BGR data in the sRGB colorspace. Pixel format . + /// Most texture formats do not support this format. Use instead. + /// + Bgr24_sRGB, + + /// + /// Raw unsigned byte 32-bit RGBA data in linear colorspace. Pixel format . + /// + Rgba32, + + /// + /// Raw unsigned byte 32-bit RGBA data in the sRGB colorspace. Pixel format . + /// + Rgba32_sRGB, + + /// + /// Raw unsigned byte 32-bit BGRA data in linear colorspace. Pixel format . + /// + Bgra32, + + /// + /// Raw unsigned byte 32-bit BGRA data in the sRGB colorspace. Pixel format . + /// + Bgra32_sRGB, + + /// + /// Raw unsigned byte 32-bit BGR data in linear colorspace. Pixel format . + /// 8 bits are unused. + /// + Bgr24x8, + + /// + /// Raw unsigned byte 32-bit BGR data in the sRGB colorspace. Pixel format . + /// 8 bits are unused. + /// + Bgr24x8_sRGB, + + /// + /// Raw floating point 32-bit-per-channel RGBA data in linear colorspace. Pixel format . + /// + RgbaFloat, + + /// + /// Raw floating point 16-bit-per-channel RGBA data in linear colorspace. Pixel format . + /// + RgbaHalf, + + /// + /// Raw floating point 32-bit-per-channel RGB data in linear colorspace. Pixel format . + /// + RgbFloat, + + /// + /// Raw floating point 16-bit-per-channel RGB data in linear colorspace. Pixel format . + /// + RgbHalf, + + /// + /// 32-bit-per-pixel Hdr data with 24-bits for RGB (in linear colorspace) and 8 bits for exponent. Pixel format . + /// + Rgbe32, + + /// + /// 32-bit-per-pixel Hdr data with 24-bits for RGB (in CIE_1931 XYZ colorspace) and 8 bits for exponent. Pixel format . + /// + Xyze32, + + /// + /// Raw 16-bit Unorm single channel format. 16 bits for Red channel. + /// + R16, + + /// + /// Raw 16-bit Snorm single channel format. 16 bits for Red channel. + /// + R16S, + + /// + /// Raw 16-bit Float single channel format. 16 bits for Red channel. + /// + R16F, + + /// + /// Raw 32-bit Float single channel format. 32 bits for Red channel. + /// + R32F, + + /// + /// Raw 16-bit-per-channel Unorm dual channel format. 16 bits each for Red and Green channels. + /// + R16G16, + + /// + /// Raw 16-bit-per-channel Snorm dual channel format. 16 bits each for Red and Green channels. + /// + R16G16S, + + /// + /// Raw 16-bit-per-channel Float dual channel format. 16 bits each for Red and Green channels. + /// + R16G16F, + + /// + /// Raw 32-bit-per-channel Float dual channel format. 32 bits each for Red and Green channels. + /// + R32G32F, + + /// + /// Raw 16-bit-per-channel Unorm triple channel format. 16 bits each for Red, Green, Blue channels. + /// Pixel format . + /// + R16G16B16, + + /// + /// Raw 32-bit-per-channel Uint triple channel format. 32 bits each for Red, Green, Blue channels. + /// Pixel format . + /// + R32G32B32UI, + + /// + /// Raw 16-bit-per-channel Unorm quad channel format. 16 bits each for Red, Green, Blue, Alpha channels. + /// Pixel format . + /// + R16G16B16A16, + + /// + /// Raw 32-bit-per-channel Uint quad channel format. 32 bits each for Red, Green, Blue, Alpha channels. + /// Pixel format . + /// + R32G32B32A32UI, + + + // ==== PACKED Formats ==== + + /// + /// 32-bit packed format with 10-bit Red, 10-bit Green, 10-bit Blue, and 2-bit Alpha components. + /// Bits ordered from MSB to LSB as: A[31:30] B[29:20] G[19:10] R[9:0]. + /// Equivalent to VK_FORMAT_A2B10G10R10_UNORM_PACK32 and DXGI_FORMAT_R10G10B10A2_UNORM. + /// + R10G10B10A2_Packed, + + /// + /// 32-bit packed format with 10-bit Blue, 10-bit Green, 10-bit Red, and 2-bit Alpha components. + /// Bits ordered from MSB to LSB as: A[31:30] R[29:20] G[19:10] B[9:0]. + /// Equivalent to VK_FORMAT_A2R10G10B10_UNORM_PACK32. + /// + B10G10R10A2_Packed, + + /// + /// 16-bit packed format with 5-bit Blue, 6-bit Green, and 5-bit Red components. + /// Bits ordered from MSB to LSB as: R[15:11] G[10:5] B[4:0]. + /// Equivalent to VK_FORMAT_R5G6B5_UNORM_PACK16 and DXGI_FORMAT_B5G6R5_UNORM. + /// + B5G6R5_Packed, + + /// + /// 16-bit packed format with 5-bit Red, 6-bit Green, and 5-bit Blue components. + /// Bits ordered from MSB to LSB as: B[15:11] G[10:5] R[4:0]. + /// Equivalent to VK_FORMAT_B5G6R5_UNORM_PACK16. + /// + R5G6B5_Packed, + + /// + /// 16-bit packed format with 1-bit Alpha, 5-bit Blue, 5-bit Green, and 5-bit Red components. + /// Bits ordered from MSB to LSB as: R[15:11] G[10:6] B[5:1] A[0]. + /// Equivalent to VK_FORMAT_R5G5B5A1_UNORM_PACK16. + /// + A1B5G5R5_Packed, + + /// + /// 16-bit packed format with 1-bit Alpha, 5-bit Red, 5-bit Green, and 5-bit Blue components. + /// Bits ordered from MSB to LSB as: B[15:11] G[10:6] R[5:1] A[0]. + /// Equivalent to VK_FORMAT_B5G5R5A1_UNORM_PACK16. + /// + A1R5G5B5_Packed, + + /// + /// 16-bit packed format with 5-bit Blue, 5-bit Green, 5-bit Red, and 1-bit Alpha components. + /// Bits ordered from MSB to LSB as: A[15] R[14:10] G[9:5] B[4:0]. + /// Equivalent to VK_FORMAT_A1R5G5B5_UNORM_PACK16 and DXGI_FORMAT_B5G5R5A1_UNORM. + /// + B5G5R5A1_Packed, + + /// + /// 16-bit packed format with 5-bit Red, 5-bit Green, 5-bit Blue, and 1-bit Alpha components. + /// Bits ordered from MSB to LSB as: A[15] B[14:10] G[9:5] R[4:0]. + /// Equivalent to VK_FORMAT_A1B5G5R5_UNORM_PACK16. + /// + R5G5B5A1_Packed, + + /// + /// 16-bit packed format with 4-bit Alpha, 4-bit Blue, 4-bit Green, and 4-bit Red components. + /// Bits ordered from MSB to LSB as: R[15:12] G[11:8] B[7:4] A[3:0]. + /// Equivalent to VK_FORMAT_R4G4B4A4_UNORM_PACK16 and DXGI_FORMAT_A4B4G4R4_UNORM. + /// + A4B4G4R4_Packed, + + /// + /// 16-bit packed format with 4-bit Alpha, 4-bit Red, 4-bit Green, and 4-bit Blue components. + /// Bits ordered from MSB to LSB as: B[15:12] G[11:8] R[7:4] A[3:0]. + /// Equivalent to VK_FORMAT_B4G4R4A4_UNORM_PACK16. + /// + A4R4G4B4_Packed, + + /// + /// 16-bit packed format with 4-bit Blue, 4-bit Green, 4-bit Red, and 4-bit Alpha components. + /// Bits ordered from MSB to LSB as: A[15:12] R[11:8] G[7:4] B[3:0]. + /// Equivalent to VK_FORMAT_A4R4G4B4_UNORM_PACK16 and DXGI_FORMAT_B4G4R4A4_UNORM. + /// + B4G4R4A4_Packed, + + /// + /// 16-bit packed format with 4-bit Red, 4-bit Green, 4-bit Blue, and 4-bit Alpha components. + /// Bits ordered from MSB to LSB as: A[15:12] B[11:8] G[7:4] R[3:0]. + /// Equivalent to VK_FORMAT_A4B4G4R4_UNORM_PACK16. + /// + R4G4B4A4_Packed, + + /// + /// 8-bit packed format with 4-bit Red and 4-bit Green components. + /// Bits ordered from MSB to LSB as: G[7:4] R[3:0]. + /// Equivalent to VK_FORMAT_R4G4_UNORM_PACK8. Vulkan-only format. + /// + R4G4_Packed, + + /// + /// 32-bit packed shared-exponent format with 9-bit Red, 9-bit Green, 9-bit Blue, and 5-bit Exponent components. + /// Bits ordered from MSB to LSB as: E[31:27] B[26:18] G[17:9] R[8:0]. + /// Equivalent to VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 and DXGI_FORMAT_R9G9B9E5_SHAREDEXP. + /// + R9G9B9E5_Packed, + + /// + /// 32-bit packed floating point format with 11-bit Red, 11-bit Green, and 10-bit Blue components. + /// Bits ordered from MSB to LSB as: B[31:22] G[21:11] R[10:0]. + /// Equivalent to VK_FORMAT_B10G11R11_UFLOAT_PACK32 and DXGI_FORMAT_R11G11B10_FLOAT. + /// + R11G11B10UF_Packed, + + + // ==== BCN Formats ==== + + /// + /// BC1 / DXT1 with no alpha in linear colorspace. Very widely supported and good compression ratio. /// Bc1, - /// - /// BC1 / DXT1 with 1-bit of alpha. - /// - Bc1WithAlpha, - /// - /// BC2 / DXT3 encoding with alpha. Good for sharp alpha transitions. - /// - Bc2, - /// - /// BC3 / DXT5 encoding with alpha. Good for smooth alpha transitions. - /// - Bc3, - /// - /// BC4 single-channel encoding. Only luminance is encoded. - /// - Bc4, - /// - /// BC5 dual-channel encoding. Only red and green channels are encoded. - /// - Bc5, - /// - /// BC6H / BPTC unsigned float encoding. Can compress HDR textures without alpha. Does not support negative values. - /// - Bc6U, - /// - /// BC6H / BPTC signed float encoding. Can compress HDR textures without alpha. Supports negative values. + + /// + /// BC1 / DXT1 with no alpha in the sRGB colorspace. + /// + Bc1_sRGB, + + /// + /// BC1 / DXT1 with 1-bit of alpha in linear colorspace. + /// + Bc1WithAlpha, + + /// + /// BC1 / DXT1 with 1-bit of alpha in the sRGB colorspace. + /// + Bc1WithAlpha_sRGB, + + /// + /// BC2 / DXT3 encoding with alpha in linear colorspace. Good for sharp alpha transitions. + /// + Bc2, + + /// + /// BC2 / DXT3 encoding with alpha in sRGB colorspace. Alpha channel is always linear. + /// + Bc2_sRGB, + + /// + /// BC3 / DXT5 encoding with alpha in linear colorspace. Good for smooth alpha transitions. + /// + Bc3, + + /// + /// BC3 / DXT5 encoding with alpha in the sRGB colorspace. Alpha channel is always linear. + /// + Bc3_sRGB, + + /// + /// BC4 single-channel encoding. Only luminance (or red channel) is encoded. Always linear. + /// + Bc4, + + /// + /// Signed BC4 single-channel encoding. Only luminance (or red channel) is encoded. Always linear. + /// + Bc4S, + + /// + /// BC5 dual-channel encoding. Only red and green channels are encoded. Mostly used for normal maps. Always linear. + /// + Bc5, + + /// + /// Signed BC5 dual-channel encoding. Only red and green channels are encoded. Mostly used for normal maps. Always linear. + /// + Bc5S, + + /// + /// BC6H / BPTC unsigned float encoding in linear colorspace. Can compress HDR textures without alpha. Does not support negative values. + /// + Bc6U, + + /// + /// BC6H / BPTC signed float encoding in linear colorspace. Can compress HDR textures without alpha. Supports negative values. /// Bc6S, + /// - /// BC7 / BPTC unorm encoding. Very high Quality rgba or rgb encoding. Also very slow. + /// BC7 / BPTC unorm encoding in linear colorspace. Very high Quality rgba or rgb encoding. Also very slow. /// Bc7, + + /// + /// BC7 / BPTC unorm encoding in the sRGB colorspace. Very high Quality rgba or rgb encoding. Also very slow. + /// + Bc7_sRGB, + /// - /// ATC / Adreno Texture Compression encoding. Derivative of BC1. + /// ATC / Adreno Texture Compression encoding in (assumed) sRGB colorspace. Derivative of BC1. /// Atc, + /// - /// ATC / Adreno Texture Compression encoding. Derivative of BC2. Good for sharp alpha transitions. + /// ATC / Adreno Texture Compression encoding in (assumed) sRGB colorspace. Derivative of BC2. Good for sharp alpha transitions. /// AtcExplicitAlpha, + /// - /// ATC / Adreno Texture Compression encoding. Derivative of BC3. Good for smooth alpha transitions. + /// ATC / Adreno Texture Compression encoding in (assumed) sRGB colorspace. Derivative of BC3. Good for smooth alpha transitions. /// AtcInterpolatedAlpha, + /// /// Unknown format /// Unknown } + // ReSharper restore InconsistentNaming + + public enum CompressionFormatType + { + Undefined, + RawUnorm, + RawUnormSrgb, + RawSnorm, + RawFloat, + RawUFloat, + RawUint, + RawSharedExponent, + BlockUnorm, + BlockUnormSrgb, + BlockSnorm, + BlockSFloat, + BlockUFloat + } + + public readonly struct CompressionFormatInfo + { + public CompressionFormat Format { get; } + public CompressionFormatType FormatType { get; } + public bool SupportsAlpha { get; } + public BlockPixelSize BlockSize { get; } + public Type TexelType { get; } + public int BytesPerTexel { get; } + + public CompressionFormatInfo(CompressionFormat format, CompressionFormatType formatType, bool supportsAlpha, BlockPixelSize blockSize, Type texelType) + { + Format = format; + FormatType = formatType; + SupportsAlpha = supportsAlpha; + BlockSize = blockSize; + TexelType = texelType; - public static class CompressionFormatExtensions - { - public static bool IsCompressedFormat(this CompressionFormat format) - { - switch (format) - { - case CompressionFormat.R: - case CompressionFormat.Rg: - case CompressionFormat.Rgb: - case CompressionFormat.Rgba: - case CompressionFormat.Bgra: - return false; - - default: - return true; - } - } + // Calculate bytes per texel + var method = typeof(Unsafe) + .GetMethods(BindingFlags.Public | BindingFlags.Static) + .First(x => x.Name == nameof(Unsafe.SizeOf) && + x.GetParameters().Length == 0); + + BytesPerTexel = method.MakeGenericMethod(texelType).Invoke(null, null) as int? ?? 0; + } + + public override string ToString() + { + return $"{nameof(Format)}: {Format}, {nameof(FormatType)}: {FormatType}, {nameof(SupportsAlpha)}: {SupportsAlpha}, {nameof(BlockSize)}: {BlockSize}, {nameof(TexelType)}: {TexelType}, {nameof(BytesPerTexel)}: {BytesPerTexel}"; + } + + public static IReadOnlyDictionary Infos { get; } = new Dictionary() + { + // Single-channel formats (R) + { CompressionFormat.R8, new CompressionFormatInfo(CompressionFormat.R8, CompressionFormatType.RawUnorm, false, BlockPixelSize.Size1x1x1, typeof(ColorR8))}, + { CompressionFormat.R8S, new CompressionFormatInfo(CompressionFormat.R8S, CompressionFormatType.RawSnorm, false, BlockPixelSize.Size1x1x1, typeof(ColorR8S))}, + { CompressionFormat.R16, new CompressionFormatInfo(CompressionFormat.R16, CompressionFormatType.RawUnorm, false, BlockPixelSize.Size1x1x1, typeof(ColorR16))}, + { CompressionFormat.R16S, new CompressionFormatInfo(CompressionFormat.R16S, CompressionFormatType.RawSnorm, false, BlockPixelSize.Size1x1x1, typeof(ColorR16S))}, + { CompressionFormat.R16F, new CompressionFormatInfo(CompressionFormat.R16F, CompressionFormatType.RawFloat, false, BlockPixelSize.Size1x1x1, typeof(ColorR16F))}, + { CompressionFormat.R32F, new CompressionFormatInfo(CompressionFormat.R32F, CompressionFormatType.RawFloat, false, BlockPixelSize.Size1x1x1, typeof(ColorR32F))}, + + // Dual-channel formats (RG) + { CompressionFormat.R8G8, new CompressionFormatInfo(CompressionFormat.R8G8, CompressionFormatType.RawUnorm, false, BlockPixelSize.Size1x1x1, typeof(ColorR8G8))}, + { CompressionFormat.R8G8S, new CompressionFormatInfo(CompressionFormat.R8G8S, CompressionFormatType.RawSnorm, false, BlockPixelSize.Size1x1x1, typeof(ColorR8G8S))}, + { CompressionFormat.R16G16, new CompressionFormatInfo(CompressionFormat.R16G16, CompressionFormatType.RawUnorm, false, BlockPixelSize.Size1x1x1, typeof(ColorR16G16))}, + { CompressionFormat.R16G16S, new CompressionFormatInfo(CompressionFormat.R16G16S, CompressionFormatType.RawSnorm, false, BlockPixelSize.Size1x1x1, typeof(ColorR16G16S))}, + { CompressionFormat.R16G16F, new CompressionFormatInfo(CompressionFormat.R16G16F, CompressionFormatType.RawFloat, false, BlockPixelSize.Size1x1x1, typeof(ColorR16G16F))}, + { CompressionFormat.R32G32F, new CompressionFormatInfo(CompressionFormat.R32G32F, CompressionFormatType.RawFloat, false, BlockPixelSize.Size1x1x1, typeof(ColorR32G32F))}, + + // Triple-channel formats (RGB) + { CompressionFormat.Rgb24, new CompressionFormatInfo(CompressionFormat.Rgb24, CompressionFormatType.RawUnorm, false, BlockPixelSize.Size1x1x1, typeof(ColorRgb24))}, + { CompressionFormat.Rgb24_sRGB, new CompressionFormatInfo(CompressionFormat.Rgb24_sRGB, CompressionFormatType.RawUnormSrgb, false, BlockPixelSize.Size1x1x1, typeof(ColorRgb24))}, + { CompressionFormat.Bgr24, new CompressionFormatInfo(CompressionFormat.Bgr24, CompressionFormatType.RawUnorm, false, BlockPixelSize.Size1x1x1, typeof(ColorBgr24))}, + { CompressionFormat.Bgr24_sRGB, new CompressionFormatInfo(CompressionFormat.Bgr24_sRGB, CompressionFormatType.RawUnormSrgb, false, BlockPixelSize.Size1x1x1, typeof(ColorBgr24))}, + { CompressionFormat.RgbFloat, new CompressionFormatInfo(CompressionFormat.RgbFloat, CompressionFormatType.RawFloat, false, BlockPixelSize.Size1x1x1, typeof(ColorRgbFloat))}, + { CompressionFormat.RgbHalf, new CompressionFormatInfo(CompressionFormat.RgbHalf, CompressionFormatType.RawFloat, false, BlockPixelSize.Size1x1x1, typeof(ColorRgbHalf))}, + { CompressionFormat.Bgr24x8, new CompressionFormatInfo(CompressionFormat.Bgr24x8, CompressionFormatType.RawUnorm, false, BlockPixelSize.Size1x1x1, typeof(ColorBgra32))}, + { CompressionFormat.Bgr24x8_sRGB, new CompressionFormatInfo(CompressionFormat.Bgr24x8_sRGB, CompressionFormatType.RawUnormSrgb, false, BlockPixelSize.Size1x1x1, typeof(ColorBgra32))}, + { CompressionFormat.R16G16B16, new CompressionFormatInfo(CompressionFormat.R16G16B16, CompressionFormatType.RawUnorm, false, BlockPixelSize.Size1x1x1, typeof(ColorR16G16B16))}, + { CompressionFormat.R32G32B32UI, new CompressionFormatInfo(CompressionFormat.R32G32B32UI, CompressionFormatType.RawUint, false, BlockPixelSize.Size1x1x1, typeof(ColorR32G32B32UI))}, + + // Quad-channel formats (RGBA) + { CompressionFormat.Rgba32, new CompressionFormatInfo(CompressionFormat.Rgba32, CompressionFormatType.RawUnorm, true, BlockPixelSize.Size1x1x1, typeof(ColorRgba32))}, + { CompressionFormat.Rgba32_sRGB, new CompressionFormatInfo(CompressionFormat.Rgba32_sRGB, CompressionFormatType.RawUnormSrgb, true, BlockPixelSize.Size1x1x1, typeof(ColorRgba32))}, + { CompressionFormat.Bgra32, new CompressionFormatInfo(CompressionFormat.Bgra32, CompressionFormatType.RawUnorm, true, BlockPixelSize.Size1x1x1, typeof(ColorBgra32))}, + { CompressionFormat.Bgra32_sRGB, new CompressionFormatInfo(CompressionFormat.Bgra32_sRGB, CompressionFormatType.RawUnormSrgb, true, BlockPixelSize.Size1x1x1, typeof(ColorBgra32))}, + { CompressionFormat.RgbaFloat, new CompressionFormatInfo(CompressionFormat.RgbaFloat, CompressionFormatType.RawFloat, true, BlockPixelSize.Size1x1x1, typeof(ColorRgbaFloat))}, + { CompressionFormat.RgbaHalf, new CompressionFormatInfo(CompressionFormat.RgbaHalf, CompressionFormatType.RawFloat, true, BlockPixelSize.Size1x1x1, typeof(ColorRgbaHalf))}, + { CompressionFormat.R16G16B16A16, new CompressionFormatInfo(CompressionFormat.R16G16B16A16, CompressionFormatType.RawUnorm, true, BlockPixelSize.Size1x1x1, typeof(ColorR16G16B16A16))}, + { CompressionFormat.R32G32B32A32UI, new CompressionFormatInfo(CompressionFormat.R32G32B32A32UI, CompressionFormatType.RawUint, true, BlockPixelSize.Size1x1x1, typeof(ColorR32G32B32A32UI))}, + + // Special packed formats + { CompressionFormat.R5G6B5_Packed, new CompressionFormatInfo(CompressionFormat.R5G6B5_Packed, CompressionFormatType.RawUnorm, false, BlockPixelSize.Size1x1x1, typeof(ColorR5G6B5Packed))}, + { CompressionFormat.B5G6R5_Packed, new CompressionFormatInfo(CompressionFormat.B5G6R5_Packed, CompressionFormatType.RawUnorm, false, BlockPixelSize.Size1x1x1, typeof(ColorB5G6R5Packed))}, + { CompressionFormat.R5G5B5A1_Packed, new CompressionFormatInfo(CompressionFormat.R5G5B5A1_Packed, CompressionFormatType.RawUnorm, true, BlockPixelSize.Size1x1x1, typeof(ColorR5G5B5A1Packed))}, + { CompressionFormat.B5G5R5A1_Packed, new CompressionFormatInfo(CompressionFormat.B5G5R5A1_Packed, CompressionFormatType.RawUnorm, true, BlockPixelSize.Size1x1x1, typeof(ColorB5G5R5A1Packed))}, + { CompressionFormat.A1B5G5R5_Packed, new CompressionFormatInfo(CompressionFormat.A1B5G5R5_Packed, CompressionFormatType.RawUnorm, true, BlockPixelSize.Size1x1x1, typeof(ColorA1B5G5R5Packed))}, + { CompressionFormat.A1R5G5B5_Packed, new CompressionFormatInfo(CompressionFormat.A1R5G5B5_Packed, CompressionFormatType.RawUnorm, true, BlockPixelSize.Size1x1x1, typeof(ColorA1R5G5B5Packed))}, + { CompressionFormat.R4G4B4A4_Packed, new CompressionFormatInfo(CompressionFormat.R4G4B4A4_Packed, CompressionFormatType.RawUnorm, true, BlockPixelSize.Size1x1x1, typeof(ColorR4G4B4A4Packed))}, + { CompressionFormat.R4G4_Packed, new CompressionFormatInfo(CompressionFormat.R4G4_Packed, CompressionFormatType.RawUnorm, false, BlockPixelSize.Size1x1x1, typeof(ColorR4G4Packed))}, + { CompressionFormat.B4G4R4A4_Packed, new CompressionFormatInfo(CompressionFormat.B4G4R4A4_Packed, CompressionFormatType.RawUnorm, true, BlockPixelSize.Size1x1x1, typeof(ColorB4G4R4A4Packed))}, + { CompressionFormat.A4B4G4R4_Packed, new CompressionFormatInfo(CompressionFormat.A4B4G4R4_Packed, CompressionFormatType.RawUnorm, true, BlockPixelSize.Size1x1x1, typeof(ColorA4B4G4R4Packed))}, + { CompressionFormat.A4R4G4B4_Packed, new CompressionFormatInfo(CompressionFormat.A4R4G4B4_Packed, CompressionFormatType.RawUnorm, true, BlockPixelSize.Size1x1x1, typeof(ColorA4R4G4B4Packed))}, + { CompressionFormat.R10G10B10A2_Packed, new CompressionFormatInfo(CompressionFormat.R10G10B10A2_Packed, CompressionFormatType.RawUnorm, true, BlockPixelSize.Size1x1x1, typeof(ColorR10G10B10A2Packed))}, + { CompressionFormat.B10G10R10A2_Packed, new CompressionFormatInfo(CompressionFormat.B10G10R10A2_Packed, CompressionFormatType.RawUnorm, true, BlockPixelSize.Size1x1x1, typeof(ColorB10G10R10A2Packed))}, + { CompressionFormat.R11G11B10UF_Packed, new CompressionFormatInfo(CompressionFormat.R11G11B10UF_Packed, CompressionFormatType.RawUFloat, false, BlockPixelSize.Size1x1x1, typeof(ColorR11G11B10PackedUFloat))}, + { CompressionFormat.R9G9B9E5_Packed, new CompressionFormatInfo(CompressionFormat.R9G9B9E5_Packed, CompressionFormatType.RawSharedExponent, false, BlockPixelSize.Size1x1x1, typeof(ColorR9G9B9E5))}, + + // Shared exponent formats + { CompressionFormat.Rgbe32, new CompressionFormatInfo(CompressionFormat.Rgbe32, CompressionFormatType.RawSharedExponent, false, BlockPixelSize.Size1x1x1, typeof(ColorRgbe))}, + { CompressionFormat.Xyze32, new CompressionFormatInfo(CompressionFormat.Xyze32, CompressionFormatType.RawSharedExponent, false, BlockPixelSize.Size1x1x1, typeof(ColorXyze))}, + + // Block Formats + { CompressionFormat.Bc1, new CompressionFormatInfo(CompressionFormat.Bc1, CompressionFormatType.BlockUnorm, false, BlockPixelSize.Size4x4x1, typeof(Bc1Block))}, + { CompressionFormat.Bc1_sRGB, new CompressionFormatInfo(CompressionFormat.Bc1_sRGB, CompressionFormatType.BlockUnormSrgb, false, BlockPixelSize.Size4x4x1, typeof(Bc1Block))}, + { CompressionFormat.Bc1WithAlpha, new CompressionFormatInfo(CompressionFormat.Bc1WithAlpha, CompressionFormatType.BlockUnorm, true, BlockPixelSize.Size4x4x1, typeof(Bc1Block))}, + { CompressionFormat.Bc1WithAlpha_sRGB, new CompressionFormatInfo(CompressionFormat.Bc1WithAlpha_sRGB, CompressionFormatType.BlockUnormSrgb, true, BlockPixelSize.Size4x4x1, typeof(Bc1Block))}, + { CompressionFormat.Bc2, new CompressionFormatInfo(CompressionFormat.Bc2, CompressionFormatType.BlockUnorm, true, BlockPixelSize.Size4x4x1, typeof(Bc2Block))}, + { CompressionFormat.Bc2_sRGB, new CompressionFormatInfo(CompressionFormat.Bc2_sRGB, CompressionFormatType.BlockUnormSrgb, true, BlockPixelSize.Size4x4x1, typeof(Bc2Block))}, + { CompressionFormat.Bc3, new CompressionFormatInfo(CompressionFormat.Bc3, CompressionFormatType.BlockUnorm, true, BlockPixelSize.Size4x4x1, typeof(Bc3Block))}, + { CompressionFormat.Bc3_sRGB, new CompressionFormatInfo(CompressionFormat.Bc3_sRGB, CompressionFormatType.BlockUnormSrgb, true, BlockPixelSize.Size4x4x1, typeof(Bc3Block))}, + { CompressionFormat.Bc4, new CompressionFormatInfo(CompressionFormat.Bc4, CompressionFormatType.BlockUnorm, false, BlockPixelSize.Size4x4x1, typeof(Bc4Block))}, + { CompressionFormat.Bc5, new CompressionFormatInfo(CompressionFormat.Bc5, CompressionFormatType.BlockUnorm, false, BlockPixelSize.Size4x4x1, typeof(Bc5Block))}, + { CompressionFormat.Bc7, new CompressionFormatInfo(CompressionFormat.Bc7, CompressionFormatType.BlockUnorm, true, BlockPixelSize.Size4x4x1, typeof(Bc7Block))}, + { CompressionFormat.Bc7_sRGB, new CompressionFormatInfo(CompressionFormat.Bc7_sRGB, CompressionFormatType.BlockUnormSrgb, true, BlockPixelSize.Size4x4x1, typeof(Bc7Block))}, + { CompressionFormat.Atc, new CompressionFormatInfo(CompressionFormat.Atc, CompressionFormatType.BlockUnorm, false, BlockPixelSize.Size4x4x1, typeof(AtcBlock))}, + { CompressionFormat.AtcExplicitAlpha, new CompressionFormatInfo(CompressionFormat.AtcExplicitAlpha, CompressionFormatType.BlockUnorm, true, BlockPixelSize.Size4x4x1, typeof(AtcExplicitAlphaBlock))}, + { CompressionFormat.AtcInterpolatedAlpha, new CompressionFormatInfo(CompressionFormat.AtcInterpolatedAlpha, CompressionFormatType.BlockUnorm, true, BlockPixelSize.Size4x4x1, typeof(AtcInterpolatedAlphaBlock))}, + + // Block Signed Formats + { CompressionFormat.Bc4S, new CompressionFormatInfo(CompressionFormat.Bc4S, CompressionFormatType.BlockSnorm, false, BlockPixelSize.Size4x4x1, typeof(Bc4Block))}, + { CompressionFormat.Bc5S, new CompressionFormatInfo(CompressionFormat.Bc5S, CompressionFormatType.BlockSnorm, false, BlockPixelSize.Size4x4x1, typeof(Bc5Block))}, + + // Block Float Formats + { CompressionFormat.Bc6U, new CompressionFormatInfo(CompressionFormat.Bc6U, CompressionFormatType.BlockUFloat, false, BlockPixelSize.Size4x4x1, typeof(Bc6Block))}, + { CompressionFormat.Bc6S, new CompressionFormatInfo(CompressionFormat.Bc6S, CompressionFormatType.BlockSFloat, false, BlockPixelSize.Size4x4x1, typeof(Bc6Block))}, + }; + } + + public static class CompressionFormatExtensions + { + public static CompressionFormatInfo GetInfo(this CompressionFormat format) + { + if (!CompressionFormatInfo.Infos.TryGetValue(format, out var info)) + { + throw new ArgumentOutOfRangeException(nameof(format), format, null); + } + + return info; + } + + public static bool IsBlockCompressedFormat(this CompressionFormat format) + { + CompressionFormatInfo info = format.GetInfo(); + + return info.FormatType == CompressionFormatType.BlockUnorm + || info.FormatType == CompressionFormatType.BlockUnormSrgb + || info.FormatType == CompressionFormatType.BlockSnorm + || info.FormatType == CompressionFormatType.BlockSFloat + || info.FormatType == CompressionFormatType.BlockUFloat; + } + + public static bool IsRawPixelFormat(this CompressionFormat format) + { + return !IsBlockCompressedFormat(format); + } + + /// + /// Return bytes per block (or per pixel if not block compressed format) + /// + public static int GetBytesPerBlock(this CompressionFormat format) + { + CompressionFormatInfo info = format.GetInfo(); + + return info.BytesPerTexel; + } public static bool IsHdrFormat(this CompressionFormat format) { - switch (format) + CompressionFormatInfo info = format.GetInfo(); + + return info.FormatType == CompressionFormatType.RawFloat + || info.FormatType == CompressionFormatType.RawUFloat + || info.FormatType == CompressionFormatType.BlockSFloat + || info.FormatType == CompressionFormatType.BlockUFloat + || info.FormatType == CompressionFormatType.RawSharedExponent; + } + + public static bool SupportsAlpha(this CompressionFormat format) + { + CompressionFormatInfo info = format.GetInfo(); + + return info.SupportsAlpha; + } + + public static bool IsSRGBFormat(this CompressionFormat format) + { + CompressionFormatInfo info = format.GetInfo(); + + return info.FormatType == CompressionFormatType.RawUnormSrgb + || info.FormatType == CompressionFormatType.BlockUnormSrgb; + } + + public static bool IsSignedFormat(this CompressionFormat format) + { + CompressionFormatInfo info = format.GetInfo(); + + return info.FormatType == CompressionFormatType.RawSnorm + || info.FormatType == CompressionFormatType.BlockSnorm + || info.FormatType == CompressionFormatType.RawFloat + || info.FormatType == CompressionFormatType.BlockSFloat; + } + + // Whether the format is a signed normalized format, i.e. values range from [-1, 1] + public static bool IsSNormFormat(this CompressionFormat format) + { + CompressionFormatInfo info = format.GetInfo(); + + return info.FormatType == CompressionFormatType.RawSnorm + || info.FormatType == CompressionFormatType.BlockSnorm; + } + + // Whether the format is a unsigned normalized format, i.e. values range from [0, 1] + public static bool IsUNormFormat(this CompressionFormat format) + { + CompressionFormatInfo info = format.GetInfo(); + + return info.FormatType == CompressionFormatType.RawUnorm + || info.FormatType == CompressionFormatType.RawUnormSrgb + || info.FormatType == CompressionFormatType.BlockUnorm + || info.FormatType == CompressionFormatType.BlockUnormSrgb; + } + + // Whether the format stores raw integer values (not normalized to [0, 1]) + public static bool IsIntegerFormat(this CompressionFormat format) + { + CompressionFormatInfo info = format.GetInfo(); + + return info.FormatType == CompressionFormatType.RawUint; + } + + public static long CalculateMipByteSize(this CompressionFormat format, int width, int height, int depth) + { + if (format.IsBlockCompressedFormat()) { - case CompressionFormat.Bc6S: - case CompressionFormat.Bc6U: - return true; + return ImageToBlocks.CalculateNumOfBlocks(format, width, height, depth) * (long)format.GetBytesPerBlock(); + } + return (long)format.GetBytesPerBlock() * width * height * depth; + } + + internal static System.Type GetPixelType(this CompressionFormat format) + { + CompressionFormatInfo info = format.GetInfo(); + + return info.TexelType; + } + + public static CompressionFormat ToNonSrgbFormat(this CompressionFormat format) + { + switch (format) + { + case CompressionFormat.Rgb24_sRGB: + return CompressionFormat.Rgb24; + case CompressionFormat.Bgr24_sRGB: + return CompressionFormat.Bgr24; + case CompressionFormat.Rgba32_sRGB: + return CompressionFormat.Rgba32; + case CompressionFormat.Bgra32_sRGB: + return CompressionFormat.Bgra32; + case CompressionFormat.Bc1_sRGB: + return CompressionFormat.Bc1; + case CompressionFormat.Bc1WithAlpha_sRGB: + return CompressionFormat.Bc1WithAlpha; + case CompressionFormat.Bc2_sRGB: + return CompressionFormat.Bc2; + case CompressionFormat.Bc3_sRGB: + return CompressionFormat.Bc3; + case CompressionFormat.Bc7_sRGB: + return CompressionFormat.Bc7; default: - return false; + return format; } } + + internal static ColorConversionMode GetColorConversionMode(this CompressionFormat sourceFormat, + CompressionFormat targetFormat) + { + var sourceIsSrgb = sourceFormat.IsSRGBFormat(); + var targetIsSrgb = targetFormat.IsSRGBFormat(); + + return (sourceIsSrgb, targetIsSrgb) switch + { + (false, false) or + (true, true) => ColorConversionMode.None, + (false, true) => ColorConversionMode.LinearToSrgb, + (true, false) => ColorConversionMode.SrgbToLinear + }; + } + + /// + /// Attempts to guess the alpha channel encoding type based on the compression format. + /// Block compressed formats typically use premultiplied alpha, while HDR formats use straight alpha. + /// + /// The compression format to analyze + /// Premultiplied for block compressed formats, Straight for HDR formats, Unknown otherwise + internal static AlphaChannelHint GuessAlphaChannelHint(this CompressionFormat format) + { + if (format.IsHdrFormat()) + return AlphaChannelHint.Straight; + if (format.IsBlockCompressedFormat()) + return AlphaChannelHint.Premultiplied; + + return AlphaChannelHint.Unknown; + } + + internal static BlockPixelSize GetBlockPixelSize(this CompressionFormat format) + { + CompressionFormatInfo info = format.GetInfo(); + + return info.BlockSize; + } } } diff --git a/BCnEnc.Net/Shared/Exceptions.cs b/BCnEnc.Net/Shared/Exceptions.cs new file mode 100644 index 0000000..052681a --- /dev/null +++ b/BCnEnc.Net/Shared/Exceptions.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BCnEncoder.Shared +{ + /// + /// Thrown by the BcEncoder when something exceptional has happened (bad inputs or settings and such) + /// + public class EncoderException : Exception + { + public EncoderException(string message) : base(message) { } + } + + /// + /// Thrown by the BcDecoder when something exceptional has happened (bad inputs or settings and such) + /// + public class DecoderException : Exception + { + public DecoderException(string message) : base(message) { } + } +} diff --git a/BCnEnc.Net/Shared/GLFormat.cs b/BCnEnc.Net/Shared/GLFormat.cs deleted file mode 100644 index 1a30b9c..0000000 --- a/BCnEnc.Net/Shared/GLFormat.cs +++ /dev/null @@ -1,199 +0,0 @@ -namespace BCnEncoder.Shared -{ - public enum GlFormat : uint - { - GlRed = 0x1903, - GlBgra = 0x80E1, - GlRgb = 0x1907, - GlRgba = 0x1908, - GlRg = 0x8227, - GlRedInteger = 0x8D94, - GlRgInteger = 0x8228, - GlRedSnorm = 0x8F90, - GlRgSnorm = 0x8F91, - GlRgbSnorm = 0x8F92, - GlRgbaSnorm = 0x8F93, - } - - public enum GlType : uint - { - GlByte = 5120, - GlUnsignedByte = 5121, - GlShort = 5122, - GlUnsignedShort = 5123, - GlInt = 5124, - GlUnsignedInt = 5125, - GlFloat = 5126, - GlHalfFloat = 5131, - GlUnsignedByte233Rev = 33634, - GlUnsignedByte332 = 32818, - GlUnsignedInt1010102 = 32822, - GlUnsignedInt2101010Rev = 33640, - GlUnsignedInt8888 = 32821, - GlUnsignedInt8888Rev = 33639, - GlUnsignedShort1555Rev = 33638, - GlUnsignedShort4444 = 32819, - GlUnsignedShort4444Rev = 33637, - GlUnsignedShort5551 = 32820, - GlUnsignedShort565 = 33635, - GlUnsignedShort565Rev = 33636 - - } - - public enum GlInternalFormat : uint - { - GlRgba4 = 0x8056, - GlRgb5 = 0x8050, - GlRgb565 = 0x8D62, - GlRgba8 = 0x8058, - GlRgb5A1 = 0x8057, - GlRgba16 = 0x805B, - GlDepthComponent16 = 0x81A5, - GlDepthComponent24 = 0x81A6, - GlDepthComponent32F = 36012, - GlStencilIndex8 = 36168, - GlDepth24Stencil8 = 0x88F0, - GlDepth32FStencil8 = 36013, - - GlR8 = 0x8229, - GlRg8 = 0x822B, - GlRg16 = 0x822C, - GlR16F = 0x822D, - GlR32F = 0x822E, - GlRg16F = 0x822F, - GlRg32F = 0x8230, - GlRgba32F = 0x8814, - GlRgba16F = 0x881A, - - GlR8Ui = 33330, - GlR8I = 33329, - GlR16 = 33322, - GlR16I = 33331, - GlR16Ui = 33332, - GlR32I = 33333, - GlR32Ui = 33334, - - - GlRg8I = 33335, - GlRg8Ui = 33336, - GlRg16I = 33337, - GlRg16Ui = 33338, - GlRg32I = 33339, - GlRg32Ui = 33340, - - GlRgb8 = 32849, - GlRgb8I = 36239, - GlRgb8Ui = 36221, - - GlRgba12 = 32858, - GlRgba2 = 32853, - GlRgba8I = 36238, - GlRgba8Ui = 36220, - - GlRgba16I = 36232, - GlRgba16Ui = 36214, - GlRgba32I = 36226, - GlRgba32Ui = 36208, - - - GlR8Snorm = 0x8F94, - GlRg8Snorm = 0x8F95, - GlRgb8Snorm = 0x8F96, - GlRgba8Snorm = 0x8F97, - GlR16Snorm = 0x8F98, - GlRg16Snorm = 0x8F99, - GlRgb16Snorm = 0x8F9A, - GlRgba16Snorm = 0x8F9B, - - GlRgb10A2 = 32857, - GlRgb10A2Ui = 36975, - - GlRgb16 = 32852, - GlRgb16F = 34843, - GlRgb16I = 36233, - GlRgb16Ui = 36215, - - GlRgb32F = 34837, - GlRgb32I = 36227, - GlRgb32Ui = 36209, - - //BC1 - GlCompressedRgbS3TcDxt1Ext = 0x83F0, - GlCompressedSrgbS3TcDxt1Ext = 0x8C4C, - GlCompressedRgbaS3TcDxt1Ext = 0x83F1, - GlCompressedSrgbAlphaS3TcDxt1Ext = 0x8C4D, - - //BC2 - GlCompressedRgbaS3TcDxt3Ext = 0x83F2, - GlCompressedSrgbAlphaS3TcDxt3Ext = 0x8C4E, - - //BC3 - GlCompressedRgbaS3TcDxt5Ext = 0x83F3, - GlCompressedSrgbAlphaS3TcDxt5Ext = 0x8C4F, - - //BC4 & BC5 - GlCompressedRedGreenRgtc2Ext = 36285, - GlCompressedRedRgtc1Ext = 36283, - GlCompressedSignedRedGreenRgtc2Ext = 36286, - GlCompressedSignedRedRgtc1Ext = 36284, - - //BC6 & BC7 - GlCompressedRgbBptcSignedFloatArb = 36494, - GlCompressedRgbBptcUnsignedFloatArb = 36495, - GlCompressedRgbaBptcUnormArb = 36492, - GlCompressedSrgbAlphaBptcUnormArb = 36493, - - GlCompressedRgbAtc = 0x8C92, - GlCompressedRgbaAtcExplicitAlpha = 0x8C93, - GlCompressedRgbaAtcInterpolatedAlpha = 0x87EE, - - // ETC1 & 2 - GlEtc1Rgb8Oes = 0x8D64, - - GlCompressedR11Eac = 0x9270, - GlCompressedSignedR11Eac = 0x9271, - GlCompressedRg11Eac = 0x9272, - GlCompressedSignedRg11Eac = 0x9273, - - GlCompressedRgb8Etc2 = 0x9274, - GlCompressedSrgb8Etc2 = 0x9275, - GlCompressedRgb8PunchthroughAlpha1Etc2 = 0x9276, - GlCompressedSrgb8PunchthroughAlpha1Etc2 = 0x9277, - GlCompressedRgba8Etc2Eac = 0x9278, - GlCompressedSrgb8Alpha8Etc2Eac = 0x9279, - - // Apple extension BGRA8 - GlBgra8Extension = 0x93A1, - - // ASTC - GlCompressedRgbaAstc4X4Khr = 0x93B0, - GlCompressedRgbaAstc5X4Khr = 0x93B1, - GlCompressedRgbaAstc5X5Khr = 0x93B2, - GlCompressedRgbaAstc6X5Khr = 0x93B3, - GlCompressedRgbaAstc6X6Khr = 0x93B4, - GlCompressedRgbaAstc8X5Khr = 0x93B5, - GlCompressedRgbaAstc8X6Khr = 0x93B6, - GlCompressedRgbaAstc8X8Khr = 0x93B7, - GlCompressedRgbaAstc10X5Khr = 0x93B8, - GlCompressedRgbaAstc10X6Khr = 0x93B9, - GlCompressedRgbaAstc10X8Khr = 0x93BA, - GlCompressedRgbaAstc10X10Khr = 0x93BB, - GlCompressedRgbaAstc12X10Khr = 0x93BC, - GlCompressedRgbaAstc12X12Khr = 0x93BD, - - GlCompressedSrgb8Alpha8Astc4X4Khr = 0x93D0, - GlCompressedSrgb8Alpha8Astc5X4Khr = 0x93D1, - GlCompressedSrgb8Alpha8Astc5X5Khr = 0x93D2, - GlCompressedSrgb8Alpha8Astc6X5Khr = 0x93D3, - GlCompressedSrgb8Alpha8Astc6X6Khr = 0x93D4, - GlCompressedSrgb8Alpha8Astc8X5Khr = 0x93D5, - GlCompressedSrgb8Alpha8Astc8X6Khr = 0x93D6, - GlCompressedSrgb8Alpha8Astc8X8Khr = 0x93D7, - GlCompressedSrgb8Alpha8Astc10X5Khr = 0x93D8, - GlCompressedSrgb8Alpha8Astc10X6Khr = 0x93D9, - GlCompressedSrgb8Alpha8Astc10X8Khr = 0x93DA, - GlCompressedSrgb8Alpha8Astc10X10Khr = 0x93DB, - GlCompressedSrgb8Alpha8Astc12X10Khr = 0x93DC, - GlCompressedSrgb8Alpha8Astc12X12Khr = 0x93DD - } -} diff --git a/BCnEnc.Net/Shared/Half.cs b/BCnEnc.Net/Shared/Half.cs deleted file mode 100644 index d1c0cdd..0000000 --- a/BCnEnc.Net/Shared/Half.cs +++ /dev/null @@ -1,1090 +0,0 @@ -/// ================ Half.cs ==================== -/// The code is free to use for any reason without any restrictions. -/// Ladislav Lang (2009), Joannes Vermorel (2017) - -using System; -using System.Diagnostics; -using System.Globalization; - -namespace BCnEncoder.Shared -{ - /// - /// Represents a half-precision floating point number. - /// - /// - /// Note: - /// Half is not fast enought and precision is also very bad, - /// so is should not be used for mathematical computation (use Single instead). - /// The main advantage of Half type is lower memory cost: two bytes per number. - /// Half is typically used in graphical applications. - /// - /// Note: - /// All functions, where is used conversion half->float/float->half, - /// are approx. ten times slower than float->double/double->float, i.e. ~3ns on 2GHz CPU. - /// - /// References: - /// - Code retrieved from http://sourceforge.net/p/csharp-half/code/HEAD/tree/ on 2015-12-04 - /// - Fast Half Float Conversions, Jeroen van der Zijp, link: http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf - /// - IEEE 754 revision, link: http://grouper.ieee.org/groups/754/ - /// - [Serializable] - public struct Half : IComparable, IFormattable, IConvertible, IComparable, IEquatable - { - /// - /// Internal representation of the half-precision floating-point number. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal ushort Value; - - #region Constants - /// - /// Represents the smallest positive System.Half value greater than zero. This field is constant. - /// - public static readonly Half Epsilon = ToHalf(0x0001); - /// - /// Represents the largest possible value of System.Half. This field is constant. - /// - public static readonly Half MaxValue = ToHalf(0x7bff); - /// - /// Represents the smallest possible value of System.Half. This field is constant. - /// - public static readonly Half MinValue = ToHalf(0xfbff); - /// - /// Represents not a number (NaN). This field is constant. - /// - public static readonly Half NaN = ToHalf(0xfe00); - /// - /// Represents negative infinity. This field is constant. - /// - public static readonly Half NegativeInfinity = ToHalf(0xfc00); - /// - /// Represents positive infinity. This field is constant. - /// - public static readonly Half PositiveInfinity = ToHalf(0x7c00); - #endregion - - #region Constructors - /// - /// Initializes a new instance of System.Half to the value of the specified single-precision floating-point number. - /// - /// The value to represent as a System.Half. - public Half(float value) { this = HalfHelper.SingleToHalf(value); } - /// - /// Initializes a new instance of System.Half to the value of the specified 32-bit signed integer. - /// - /// The value to represent as a System.Half. - public Half(int value) : this((float)value) { } - /// - /// Initializes a new instance of System.Half to the value of the specified 64-bit signed integer. - /// - /// The value to represent as a System.Half. - public Half(long value) : this((float)value) { } - /// - /// Initializes a new instance of System.Half to the value of the specified double-precision floating-point number. - /// - /// The value to represent as a System.Half. - public Half(double value) : this((float)value) { } - /// - /// Initializes a new instance of System.Half to the value of the specified decimal number. - /// - /// The value to represent as a System.Half. - public Half(decimal value) : this((float)value) { } - /// - /// Initializes a new instance of System.Half to the value of the specified 32-bit unsigned integer. - /// - /// The value to represent as a System.Half. - public Half(uint value) : this((float)value) { } - /// - /// Initializes a new instance of System.Half to the value of the specified 64-bit unsigned integer. - /// - /// The value to represent as a System.Half. - public Half(ulong value) : this((float)value) { } - #endregion - - #region Numeric operators - - /// - /// Returns the result of multiplying the specified System.Half value by negative one. - /// - /// A System.Half. - /// A System.Half with the value of half, but the opposite sign. -or- Zero, if half is zero. - public static Half Negate(Half half) { return -half; } - /// - /// Adds two specified System.Half values. - /// - /// A System.Half. - /// A System.Half. - /// A System.Half value that is the sum of half1 and half2. - public static Half Add(Half half1, Half half2) { return half1 + half2; } - /// - /// Subtracts one specified System.Half value from another. - /// - /// A System.Half (the minuend). - /// A System.Half (the subtrahend). - /// The System.Half result of subtracting half2 from half1. - public static Half Subtract(Half half1, Half half2) { return half1 - half2; } - /// - /// Multiplies two specified System.Half values. - /// - /// A System.Half (the multiplicand). - /// A System.Half (the multiplier). - /// A System.Half that is the result of multiplying half1 and half2. - public static Half Multiply(Half half1, Half half2) { return half1 * half2; } - /// - /// Divides two specified System.Half values. - /// - /// A System.Half (the dividend). - /// A System.Half (the divisor). - /// The System.Half that is the result of dividing half1 by half2. - /// half2 is zero. - public static Half Divide(Half half1, Half half2) { return half1 / half2; } - - /// - /// Returns the value of the System.Half operand (the sign of the operand is unchanged). - /// - /// The System.Half operand. - /// The value of the operand, half. - public static Half operator +(Half half) { return half; } - /// - /// Negates the value of the specified System.Half operand. - /// - /// The System.Half operand. - /// The result of half multiplied by negative one (-1). - public static Half operator -(Half half) { return HalfHelper.Negate(half); } - /// - /// Increments the System.Half operand by 1. - /// - /// The System.Half operand. - /// The value of half incremented by 1. - public static Half operator ++(Half half) { return (Half)(half + 1f); } - /// - /// Decrements the System.Half operand by one. - /// - /// The System.Half operand. - /// The value of half decremented by 1. - public static Half operator --(Half half) { return (Half)(half - 1f); } - /// - /// Adds two specified System.Half values. - /// - /// A System.Half. - /// A System.Half. - /// The System.Half result of adding half1 and half2. - public static Half operator +(Half half1, Half half2) { return (Half)(half1 + (float)half2); } - /// - /// Subtracts two specified System.Half values. - /// - /// A System.Half. - /// A System.Half. - /// The System.Half result of subtracting half1 and half2. - public static Half operator -(Half half1, Half half2) { return (Half)(half1 - (float)half2); } - /// - /// Multiplies two specified System.Half values. - /// - /// A System.Half. - /// A System.Half. - /// The System.Half result of multiplying half1 by half2. - public static Half operator *(Half half1, Half half2) { return (Half)(half1 * (float)half2); } - /// - /// Divides two specified System.Half values. - /// - /// A System.Half (the dividend). - /// A System.Half (the divisor). - /// The System.Half result of half1 by half2. - public static Half operator /(Half half1, Half half2) { return (Half)(half1 / (float)half2); } - /// - /// Returns a value indicating whether two instances of System.Half are equal. - /// - /// A System.Half. - /// A System.Half. - /// true if half1 and half2 are equal; otherwise, false. - public static bool operator ==(Half half1, Half half2) { return (!IsNaN(half1) && (half1.Value == half2.Value)); } - /// - /// Returns a value indicating whether two instances of System.Half are not equal. - /// - /// A System.Half. - /// A System.Half. - /// true if half1 and half2 are not equal; otherwise, false. - public static bool operator !=(Half half1, Half half2) { return half1.Value != half2.Value; } - /// - /// Returns a value indicating whether a specified System.Half is less than another specified System.Half. - /// - /// A System.Half. - /// A System.Half. - /// true if half1 is less than half1; otherwise, false. - public static bool operator <(Half half1, Half half2) { return half1 < (float)half2; } - /// - /// Returns a value indicating whether a specified System.Half is greater than another specified System.Half. - /// - /// A System.Half. - /// A System.Half. - /// true if half1 is greater than half2; otherwise, false. - public static bool operator >(Half half1, Half half2) { return half1 > (float)half2; } - /// - /// Returns a value indicating whether a specified System.Half is less than or equal to another specified System.Half. - /// - /// A System.Half. - /// A System.Half. - /// true if half1 is less than or equal to half2; otherwise, false. - public static bool operator <=(Half half1, Half half2) { return (half1 == half2) || (half1 < half2); } - /// - /// Returns a value indicating whether a specified System.Half is greater than or equal to another specified System.Half. - /// - /// A System.Half. - /// A System.Half. - /// true if half1 is greater than or equal to half2; otherwise, false. - public static bool operator >=(Half half1, Half half2) { return (half1 == half2) || (half1 > half2); } - #endregion - - #region Type casting operators - /// - /// Converts an 8-bit unsigned integer to a System.Half. - /// - /// An 8-bit unsigned integer. - /// A System.Half that represents the converted 8-bit unsigned integer. - public static implicit operator Half(byte value) { return new Half((float)value); } - /// - /// Converts a 16-bit signed integer to a System.Half. - /// - /// A 16-bit signed integer. - /// A System.Half that represents the converted 16-bit signed integer. - public static implicit operator Half(short value) { return new Half((float)value); } - /// - /// Converts a Unicode character to a System.Half. - /// - /// A Unicode character. - /// A System.Half that represents the converted Unicode character. - public static implicit operator Half(char value) { return new Half((float)value); } - /// - /// Converts a 32-bit signed integer to a System.Half. - /// - /// A 32-bit signed integer. - /// A System.Half that represents the converted 32-bit signed integer. - public static implicit operator Half(int value) { return new Half((float)value); } - /// - /// Converts a 64-bit signed integer to a System.Half. - /// - /// A 64-bit signed integer. - /// A System.Half that represents the converted 64-bit signed integer. - public static implicit operator Half(long value) { return new Half((float)value); } - /// - /// Converts a single-precision floating-point number to a System.Half. - /// - /// A single-precision floating-point number. - /// A System.Half that represents the converted single-precision floating point number. - public static explicit operator Half(float value) { return new Half(value); } - /// - /// Converts a double-precision floating-point number to a System.Half. - /// - /// A double-precision floating-point number. - /// A System.Half that represents the converted double-precision floating point number. - public static explicit operator Half(double value) { return new Half((float)value); } - /// - /// Converts a decimal number to a System.Half. - /// - /// decimal number - /// A System.Half that represents the converted decimal number. - public static explicit operator Half(decimal value) { return new Half((float)value); } - /// - /// Converts a System.Half to an 8-bit unsigned integer. - /// - /// A System.Half to convert. - /// An 8-bit unsigned integer that represents the converted System.Half. - public static explicit operator byte(Half value) { return (byte)(float)value; } - /// - /// Converts a System.Half to a Unicode character. - /// - /// A System.Half to convert. - /// A Unicode character that represents the converted System.Half. - public static explicit operator char(Half value) { return (char)(float)value; } - /// - /// Converts a System.Half to a 16-bit signed integer. - /// - /// A System.Half to convert. - /// A 16-bit signed integer that represents the converted System.Half. - public static explicit operator short(Half value) { return (short)(float)value; } - /// - /// Converts a System.Half to a 32-bit signed integer. - /// - /// A System.Half to convert. - /// A 32-bit signed integer that represents the converted System.Half. - public static explicit operator int(Half value) { return (int)(float)value; } - /// - /// Converts a System.Half to a 64-bit signed integer. - /// - /// A System.Half to convert. - /// A 64-bit signed integer that represents the converted System.Half. - public static explicit operator long(Half value) { return (long)(float)value; } - /// - /// Converts a System.Half to a single-precision floating-point number. - /// - /// A System.Half to convert. - /// A single-precision floating-point number that represents the converted System.Half. - public static implicit operator float(Half value) { return HalfHelper.HalfToSingle(value); } - /// - /// Converts a System.Half to a double-precision floating-point number. - /// - /// A System.Half to convert. - /// A double-precision floating-point number that represents the converted System.Half. - public static implicit operator double(Half value) { return (float)value; } - /// - /// Converts a System.Half to a decimal number. - /// - /// A System.Half to convert. - /// A decimal number that represents the converted System.Half. - public static explicit operator decimal(Half value) { return (decimal)(float)value; } - /// - /// Converts an 8-bit signed integer to a System.Half. - /// - /// An 8-bit signed integer. - /// A System.Half that represents the converted 8-bit signed integer. - public static implicit operator Half(sbyte value) { return new Half((float)value); } - /// - /// Converts a 16-bit unsigned integer to a System.Half. - /// - /// A 16-bit unsigned integer. - /// A System.Half that represents the converted 16-bit unsigned integer. - public static implicit operator Half(ushort value) { return new Half((float)value); } - /// - /// Converts a 32-bit unsigned integer to a System.Half. - /// - /// A 32-bit unsigned integer. - /// A System.Half that represents the converted 32-bit unsigned integer. - public static implicit operator Half(uint value) { return new Half((float)value); } - /// - /// Converts a 64-bit unsigned integer to a System.Half. - /// - /// A 64-bit unsigned integer. - /// A System.Half that represents the converted 64-bit unsigned integer. - public static implicit operator Half(ulong value) { return new Half((float)value); } - /// - /// Converts a System.Half to an 8-bit signed integer. - /// - /// A System.Half to convert. - /// An 8-bit signed integer that represents the converted System.Half. - public static explicit operator sbyte(Half value) { return (sbyte)(float)value; } - /// - /// Converts a System.Half to a 16-bit unsigned integer. - /// - /// A System.Half to convert. - /// A 16-bit unsigned integer that represents the converted System.Half. - public static explicit operator ushort(Half value) { return (ushort)(float)value; } - /// - /// Converts a System.Half to a 32-bit unsigned integer. - /// - /// A System.Half to convert. - /// A 32-bit unsigned integer that represents the converted System.Half. - public static explicit operator uint(Half value) { return (uint)(float)value; } - /// - /// Converts a System.Half to a 64-bit unsigned integer. - /// - /// A System.Half to convert. - /// A 64-bit unsigned integer that represents the converted System.Half. - public static explicit operator ulong(Half value) { return (ulong)(float)value; } - #endregion - - /// - /// Compares this instance to a specified System.Half object. - /// - /// A System.Half object. - /// - /// A signed number indicating the relative values of this instance and value. - /// Return Value Meaning Less than zero This instance is less than value. Zero - /// This instance is equal to value. Greater than zero This instance is greater than value. - /// - public int CompareTo(Half other) - { - var result = 0; - if (this < other) - { - result = -1; - } - else if (this > other) - { - result = 1; - } - else if (this != other) - { - if (!IsNaN(this)) - { - result = 1; - } - else if (!IsNaN(other)) - { - result = -1; - } - } - - return result; - } - /// - /// Compares this instance to a specified System.Object. - /// - /// An System.Object or null. - /// - /// A signed number indicating the relative values of this instance and value. - /// Return Value Meaning Less than zero This instance is less than value. Zero - /// This instance is equal to value. Greater than zero This instance is greater - /// than value. -or- value is null. - /// - /// value is not a System.Half - public int CompareTo(object obj) - { - var result = 0; - if (obj == null) - { - result = 1; - } - else - { - if (obj is Half) - { - result = CompareTo((Half)obj); - } - else - { - throw new ArgumentException("Object must be of type Half."); - } - } - - return result; - } - /// - /// Returns a value indicating whether this instance and a specified System.Half object represent the same value. - /// - /// A System.Half object to compare to this instance. - /// true if value is equal to this instance; otherwise, false. - public bool Equals(Half other) - { - return ((other == this) || (IsNaN(other) && IsNaN(this))); - } - /// - /// Returns a value indicating whether this instance and a specified System.Object - /// represent the same type and value. - /// - /// An System.Object. - /// true if value is a System.Half and equal to this instance; otherwise, false. - public override bool Equals(object obj) - { - var result = false; - if (obj is Half) - { - var half = (Half)obj; - if ((half == this) || (IsNaN(half) && IsNaN(this))) - { - result = true; - } - } - - return result; - } - /// - /// Returns the hash code for this instance. - /// - /// A 32-bit signed integer hash code. - public override int GetHashCode() - { - return Value.GetHashCode(); - } - /// - /// Returns the System.TypeCode for value type System.Half. - /// - /// The enumerated constant (TypeCode)255. - public TypeCode GetTypeCode() - { - return (TypeCode)255; - } - - #region BitConverter & Math methods for Half - /// - /// Returns the specified half-precision floating point value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 2. - public static byte[] GetBytes(Half value) - { - return BitConverter.GetBytes(value.Value); - } - /// - /// Converts the value of a specified instance of System.Half to its equivalent binary representation. - /// - /// A System.Half value. - /// A 16-bit unsigned integer that contain the binary representation of value. - public static ushort GetBits(Half value) - { - return value.Value; - } - /// - /// Returns a half-precision floating point number converted from two bytes - /// at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A half-precision floating point number formed by two bytes beginning at startIndex. - /// - /// startIndex is greater than or equal to the length of value minus 1, and is - /// less than or equal to the length of value minus 1. - /// - /// value is null. - /// startIndex is less than zero or greater than the length of value minus 1. - public static Half ToHalf(byte[] value, int startIndex) - { - return ToHalf((ushort)BitConverter.ToInt16(value, startIndex)); - } - /// - /// Returns a half-precision floating point number converted from its binary representation. - /// - /// Binary representation of System.Half value - /// A half-precision floating point number formed by its binary representation. - public static Half ToHalf(ushort bits) - { - return new Half { Value = bits }; - } - - /// - /// Returns a value indicating the sign of a half-precision floating-point number. - /// - /// A signed number. - /// - /// A number indicating the sign of value. Number Description -1 value is less - /// than zero. 0 value is equal to zero. 1 value is greater than zero. - /// - /// value is equal to System.Half.NaN. - public static int Sign(Half value) - { - if (value < 0) - { - return -1; - } - else if (value > 0) - { - return 1; - } - else - { - if (value != 0) - { - throw new ArithmeticException("Function does not accept floating point Not-a-Number values."); - } - } - - return 0; - } - /// - /// Returns the absolute value of a half-precision floating-point number. - /// - /// A number in the range System.Half.MinValue ≤ value ≤ System.Half.MaxValue. - /// A half-precision floating-point number, x, such that 0 ≤ x ≤System.Half.MaxValue. - public static Half Abs(Half value) - { - return HalfHelper.Abs(value); - } - /// - /// Returns the larger of two half-precision floating-point numbers. - /// - /// The first of two half-precision floating-point numbers to compare. - /// The second of two half-precision floating-point numbers to compare. - /// - /// Parameter value1 or value2, whichever is larger. If value1, or value2, or both val1 - /// and value2 are equal to System.Half.NaN, System.Half.NaN is returned. - /// - public static Half Max(Half value1, Half value2) - { - return (value1 < value2) ? value2 : value1; - } - /// - /// Returns the smaller of two half-precision floating-point numbers. - /// - /// The first of two half-precision floating-point numbers to compare. - /// The second of two half-precision floating-point numbers to compare. - /// - /// Parameter value1 or value2, whichever is smaller. If value1, or value2, or both val1 - /// and value2 are equal to System.Half.NaN, System.Half.NaN is returned. - /// - public static Half Min(Half value1, Half value2) - { - return (value1 < value2) ? value1 : value2; - } - #endregion - - /// - /// Returns a value indicating whether the specified number evaluates to not a number (System.Half.NaN). - /// - /// A half-precision floating-point number. - /// true if value evaluates to not a number (System.Half.NaN); otherwise, false. - public static bool IsNaN(Half half) - { - return HalfHelper.IsNaN(half); - } - /// - /// Returns a value indicating whether the specified number evaluates to negative or positive infinity. - /// - /// A half-precision floating-point number. - /// true if half evaluates to System.Half.PositiveInfinity or System.Half.NegativeInfinity; otherwise, false. - public static bool IsInfinity(Half half) - { - return HalfHelper.IsInfinity(half); - } - /// - /// Returns a value indicating whether the specified number evaluates to negative infinity. - /// - /// A half-precision floating-point number. - /// true if half evaluates to System.Half.NegativeInfinity; otherwise, false. - public static bool IsNegativeInfinity(Half half) - { - return HalfHelper.IsNegativeInfinity(half); - } - /// - /// Returns a value indicating whether the specified number evaluates to positive infinity. - /// - /// A half-precision floating-point number. - /// true if half evaluates to System.Half.PositiveInfinity; otherwise, false. - public static bool IsPositiveInfinity(Half half) - { - return HalfHelper.IsPositiveInfinity(half); - } - - #region String operations (Parse and ToString) - /// - /// Converts the string representation of a number to its System.Half equivalent. - /// - /// The string representation of the number to convert. - /// The System.Half number equivalent to the number contained in value. - /// value is null. - /// value is not in the correct format. - /// value represents a number less than System.Half.MinValue or greater than System.Half.MaxValue. - public static Half Parse(string value) - { - return (Half)float.Parse(value, CultureInfo.InvariantCulture); - } - /// - /// Converts the string representation of a number to its System.Half equivalent - /// using the specified culture-specific format information. - /// - /// The string representation of the number to convert. - /// An System.IFormatProvider that supplies culture-specific parsing information about value. - /// The System.Half number equivalent to the number contained in s as specified by provider. - /// value is null. - /// value is not in the correct format. - /// value represents a number less than System.Half.MinValue or greater than System.Half.MaxValue. - public static Half Parse(string value, IFormatProvider provider) - { - return (Half)float.Parse(value, provider); - } - /// - /// Converts the string representation of a number in a specified style to its System.Half equivalent. - /// - /// The string representation of the number to convert. - /// - /// A bitwise combination of System.Globalization.NumberStyles values that indicates - /// the style elements that can be present in value. A typical value to specify is - /// System.Globalization.NumberStyles.Number. - /// - /// The System.Half number equivalent to the number contained in s as specified by style. - /// value is null. - /// - /// style is not a System.Globalization.NumberStyles value. -or- style is the - /// System.Globalization.NumberStyles.AllowHexSpecifier value. - /// - /// value is not in the correct format. - /// value represents a number less than System.Half.MinValue or greater than System.Half.MaxValue. - public static Half Parse(string value, NumberStyles style) - { - return (Half)float.Parse(value, style, CultureInfo.InvariantCulture); - } - /// - /// Converts the string representation of a number to its System.Half equivalent - /// using the specified style and culture-specific format. - /// - /// The string representation of the number to convert. - /// - /// A bitwise combination of System.Globalization.NumberStyles values that indicates - /// the style elements that can be present in value. A typical value to specify is - /// System.Globalization.NumberStyles.Number. - /// - /// An System.IFormatProvider object that supplies culture-specific information about the format of value. - /// The System.Half number equivalent to the number contained in s as specified by style and provider. - /// value is null. - /// - /// style is not a System.Globalization.NumberStyles value. -or- style is the - /// System.Globalization.NumberStyles.AllowHexSpecifier value. - /// - /// value is not in the correct format. - /// value represents a number less than System.Half.MinValue or greater than System.Half.MaxValue. - public static Half Parse(string value, NumberStyles style, IFormatProvider provider) - { - return (Half)float.Parse(value, style, provider); - } - /// - /// Converts the string representation of a number to its System.Half equivalent. - /// A return value indicates whether the conversion succeeded or failed. - /// - /// The string representation of the number to convert. - /// - /// When this method returns, contains the System.Half number that is equivalent - /// to the numeric value contained in value, if the conversion succeeded, or is zero - /// if the conversion failed. The conversion fails if the s parameter is null, - /// is not a number in a valid format, or represents a number less than System.Half.MinValue - /// or greater than System.Half.MaxValue. This parameter is passed uninitialized. - /// - /// true if s was converted successfully; otherwise, false. - public static bool TryParse(string value, out Half result) - { - float f; - if (float.TryParse(value, out f)) - { - result = (Half)f; - return true; - } - - result = new Half(); - return false; - } - /// - /// Converts the string representation of a number to its System.Half equivalent - /// using the specified style and culture-specific format. A return value indicates - /// whether the conversion succeeded or failed. - /// - /// The string representation of the number to convert. - /// - /// A bitwise combination of System.Globalization.NumberStyles values that indicates - /// the permitted format of value. A typical value to specify is System.Globalization.NumberStyles.Number. - /// - /// An System.IFormatProvider object that supplies culture-specific parsing information about value. - /// - /// When this method returns, contains the System.Half number that is equivalent - /// to the numeric value contained in value, if the conversion succeeded, or is zero - /// if the conversion failed. The conversion fails if the s parameter is null, - /// is not in a format compliant with style, or represents a number less than - /// System.Half.MinValue or greater than System.Half.MaxValue. This parameter is passed uninitialized. - /// - /// true if s was converted successfully; otherwise, false. - /// - /// style is not a System.Globalization.NumberStyles value. -or- style - /// is the System.Globalization.NumberStyles.AllowHexSpecifier value. - /// - public static bool TryParse(string value, NumberStyles style, IFormatProvider provider, out Half result) - { - var parseResult = false; - float f; - if (float.TryParse(value, style, provider, out f)) - { - result = (Half)f; - parseResult = true; - } - else - { - result = new Half(); - } - - return parseResult; - } - /// - /// Converts the numeric value of this instance to its equivalent string representation. - /// - /// A string that represents the value of this instance. - public override string ToString() - { - return ((float)this).ToString(CultureInfo.InvariantCulture); - } - /// - /// Converts the numeric value of this instance to its equivalent string representation - /// using the specified culture-specific format information. - /// - /// An System.IFormatProvider that supplies culture-specific formatting information. - /// The string representation of the value of this instance as specified by provider. - public string ToString(IFormatProvider formatProvider) - { - return ((float)this).ToString(formatProvider); - } - /// - /// Converts the numeric value of this instance to its equivalent string representation, using the specified format. - /// - /// A numeric format string. - /// The string representation of the value of this instance as specified by format. - public string ToString(string format) - { - return ((float)this).ToString(format, CultureInfo.InvariantCulture); - } - /// - /// Converts the numeric value of this instance to its equivalent string representation - /// using the specified format and culture-specific format information. - /// - /// A numeric format string. - /// An System.IFormatProvider that supplies culture-specific formatting information. - /// The string representation of the value of this instance as specified by format and provider. - /// format is invalid. - public string ToString(string format, IFormatProvider formatProvider) - { - return ((float)this).ToString(format, formatProvider); - } - #endregion - - #region IConvertible Members - float IConvertible.ToSingle(IFormatProvider provider) - { - return this; - } - TypeCode IConvertible.GetTypeCode() - { - return GetTypeCode(); - } - bool IConvertible.ToBoolean(IFormatProvider provider) - { - return Convert.ToBoolean(this); - } - byte IConvertible.ToByte(IFormatProvider provider) - { - return Convert.ToByte(this); - } - char IConvertible.ToChar(IFormatProvider provider) - { - throw new InvalidCastException(string.Format(CultureInfo.CurrentCulture, "Invalid cast from '{0}' to '{1}'.", "Half", "Char")); - } - DateTime IConvertible.ToDateTime(IFormatProvider provider) - { - throw new InvalidCastException(string.Format(CultureInfo.CurrentCulture, "Invalid cast from '{0}' to '{1}'.", "Half", "DateTime")); - } - decimal IConvertible.ToDecimal(IFormatProvider provider) - { - return Convert.ToDecimal(this); - } - double IConvertible.ToDouble(IFormatProvider provider) - { - return Convert.ToDouble(this); - } - short IConvertible.ToInt16(IFormatProvider provider) - { - return Convert.ToInt16(this); - } - int IConvertible.ToInt32(IFormatProvider provider) - { - return Convert.ToInt32(this); - } - long IConvertible.ToInt64(IFormatProvider provider) - { - return Convert.ToInt64(this); - } - sbyte IConvertible.ToSByte(IFormatProvider provider) - { - return Convert.ToSByte(this); - } - string IConvertible.ToString(IFormatProvider provider) - { - return Convert.ToString(this, CultureInfo.InvariantCulture); - } - object IConvertible.ToType(Type conversionType, IFormatProvider provider) - { - return (((float)this) as IConvertible).ToType(conversionType, provider); - } - ushort IConvertible.ToUInt16(IFormatProvider provider) - { - return Convert.ToUInt16(this); - } - uint IConvertible.ToUInt32(IFormatProvider provider) - { - return Convert.ToUInt32(this); - } - ulong IConvertible.ToUInt64(IFormatProvider provider) - { - return Convert.ToUInt64(this); - } - #endregion - } -} - -namespace BCnEncoder.Shared -{ - /// - /// Helper class for Half conversions and some low level operations. - /// This class is internally used in the Half class. - /// - /// - /// References: - /// - Code retrieved from http://sourceforge.net/p/csharp-half/code/HEAD/tree/ on 2015-12-04 - /// - Fast Half Float Conversions, Jeroen van der Zijp, link: http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf - /// - internal static class HalfHelper - { - private static readonly uint[] MantissaTable = GenerateMantissaTable(); - private static readonly uint[] ExponentTable = GenerateExponentTable(); - private static readonly ushort[] OffsetTable = GenerateOffsetTable(); - private static readonly ushort[] BaseTable = GenerateBaseTable(); - private static readonly sbyte[] ShiftTable = GenerateShiftTable(); - - // Transforms the subnormal representation to a normalized one. - private static uint ConvertMantissa(int i) - { - var m = (uint)(i << 13); // Zero pad mantissa bits - uint e = 0; // Zero exponent - - // While not normalized - while ((m & 0x00800000) == 0) - { - e -= 0x00800000; // Decrement exponent (1<<23) - m <<= 1; // Shift mantissa - } - m &= unchecked((uint)~0x00800000); // Clear leading 1 bit - e += 0x38800000; // Adjust bias ((127-14)<<23) - return m | e; // Return combined number - } - - private static uint[] GenerateMantissaTable() - { - var mantissaTable = new uint[2048]; - mantissaTable[0] = 0; - for (var i = 1; i < 1024; i++) - { - mantissaTable[i] = ConvertMantissa(i); - } - for (var i = 1024; i < 2048; i++) - { - mantissaTable[i] = (uint)(0x38000000 + ((i - 1024) << 13)); - } - - return mantissaTable; - } - private static uint[] GenerateExponentTable() - { - var exponentTable = new uint[64]; - exponentTable[0] = 0; - for (var i = 1; i < 31; i++) - { - exponentTable[i] = (uint)(i << 23); - } - exponentTable[31] = 0x47800000; - exponentTable[32] = 0x80000000; - for (var i = 33; i < 63; i++) - { - exponentTable[i] = (uint)(0x80000000 + ((i - 32) << 23)); - } - exponentTable[63] = 0xc7800000; - - return exponentTable; - } - private static ushort[] GenerateOffsetTable() - { - var offsetTable = new ushort[64]; - offsetTable[0] = 0; - for (var i = 1; i < 32; i++) - { - offsetTable[i] = 1024; - } - offsetTable[32] = 0; - for (var i = 33; i < 64; i++) - { - offsetTable[i] = 1024; - } - - return offsetTable; - } - private static ushort[] GenerateBaseTable() - { - var baseTable = new ushort[512]; - for (var i = 0; i < 256; ++i) - { - var e = (sbyte)(127 - i); - if (e > 24) - { // Very small numbers map to zero - baseTable[i | 0x000] = 0x0000; - baseTable[i | 0x100] = 0x8000; - } - else if (e > 14) - { // Small numbers map to denorms - baseTable[i | 0x000] = (ushort)(0x0400 >> (18 + e)); - baseTable[i | 0x100] = (ushort)((0x0400 >> (18 + e)) | 0x8000); - } - else if (e >= -15) - { // Normal numbers just lose precision - baseTable[i | 0x000] = (ushort)((15 - e) << 10); - baseTable[i | 0x100] = (ushort)(((15 - e) << 10) | 0x8000); - } - else if (e > -128) - { // Large numbers map to Infinity - baseTable[i | 0x000] = 0x7c00; - baseTable[i | 0x100] = 0xfc00; - } - else - { // Infinity and NaN's stay Infinity and NaN's - baseTable[i | 0x000] = 0x7c00; - baseTable[i | 0x100] = 0xfc00; - } - } - - return baseTable; - } - private static sbyte[] GenerateShiftTable() - { - var shiftTable = new sbyte[512]; - for (var i = 0; i < 256; ++i) - { - var e = (sbyte)(127 - i); - if (e > 24) - { // Very small numbers map to zero - shiftTable[i | 0x000] = 24; - shiftTable[i | 0x100] = 24; - } - else if (e > 14) - { // Small numbers map to denorms - shiftTable[i | 0x000] = (sbyte)(e - 1); - shiftTable[i | 0x100] = (sbyte)(e - 1); - } - else if (e >= -15) - { // Normal numbers just lose precision - shiftTable[i | 0x000] = 13; - shiftTable[i | 0x100] = 13; - } - else if (e > -128) - { // Large numbers map to Infinity - shiftTable[i | 0x000] = 24; - shiftTable[i | 0x100] = 24; - } - else - { // Infinity and NaN's stay Infinity and NaN's - shiftTable[i | 0x000] = 13; - shiftTable[i | 0x100] = 13; - } - } - - return shiftTable; - } - - public static unsafe float HalfToSingle(Half half) - { - var result = MantissaTable[OffsetTable[half.Value >> 10] + (half.Value & 0x3ff)] + ExponentTable[half.Value >> 10]; - return *(float*)&result; - } - public static unsafe Half SingleToHalf(float single) - { - var value = *(uint*)&single; - - var result = (ushort)(BaseTable[(value >> 23) & 0x1ff] + ((value & 0x007fffff) >> ShiftTable[value >> 23])); - return Half.ToHalf(result); - } - - public static Half Negate(Half half) - { - return Half.ToHalf((ushort)(half.Value ^ 0x8000)); - } - public static Half Abs(Half half) - { - return Half.ToHalf((ushort)(half.Value & 0x7fff)); - } - - public static bool IsNaN(Half half) - { - return (half.Value & 0x7fff) > 0x7c00; - } - public static bool IsInfinity(Half half) - { - return (half.Value & 0x7fff) == 0x7c00; - } - public static bool IsPositiveInfinity(Half half) - { - return half.Value == 0x7c00; - } - public static bool IsNegativeInfinity(Half half) - { - return half.Value == 0xfc00; - } - } -} diff --git a/BCnEnc.Net/Shared/HdrImage.cs b/BCnEnc.Net/Shared/HdrImage.cs deleted file mode 100644 index cb899e2..0000000 --- a/BCnEnc.Net/Shared/HdrImage.cs +++ /dev/null @@ -1,344 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; -using Microsoft.Toolkit.HighPerformance; - -namespace BCnEncoder.Shared -{ - /// - /// Reads and writes .hdr RGBE/Radiance HDR files. File format by Gregory Ward. - /// This class is experimental, incomplete and probably going to be removed in a future version. - /// Use only if you don't have anything better. - /// - public class HdrImage - { - public enum ColorSpace - { - Rgbe, - Xyze - } - - public float exposure = -1; - public float gamma = -1; - public int width; - public int height; - - public ColorRgbFloat[] pixels; - - /// - /// Gets a span2D over the array. - /// - public Span2D PixelSpan => new Span2D(pixels, height, width); - /// - /// Gets a span2D over the array. - /// - public Memory2D PixelMemory => new Memory2D(pixels, height, width); - - public HdrImage() {} - - public HdrImage(int width, int height, float exposure = 0, float gamma = 0) - { - this.width = width; - this.height = height; - this.exposure = exposure; - this.gamma = gamma; - this.pixels = new ColorRgbFloat[width * height]; - } - - public HdrImage(Span2D pixels, float exposure = 0, float gamma = 0) - { - this.width = pixels.Width; - this.height = pixels.Height; - this.exposure = exposure; - this.gamma = gamma; - this.pixels = new ColorRgbFloat[width * height]; - pixels.CopyTo(this.pixels); - } - - // StreamReader class does not work. Have to use custom string reading. - private static string ReadFromStream(Stream stream) - { - var i = 0; - var buffer = new char[512]; - char c; - do - { - var b = stream.ReadByte(); - if (b == -1) - { - return null; - } - c = (char)b; - buffer[i++] = c; - } while (c != (char)10); - return new string(buffer.AsSpan().Slice(0, i)).Trim(); - } - - private static void WriteLineToStream(BinaryWriter br, string s) - { - foreach (var c in s) - { - var b = (byte) c; - br.Write(b); - } - br.Write((byte)10); - } - - /// - /// Read a Radiance HDR image by filename. - /// Just calls internally. - /// - /// The filename or path of the image - /// A new HdrImage with the data - public static HdrImage Read(string filename) - { - using var fs = File.OpenRead(filename); - return Read(fs); - } - - /// - /// Read a Radiance HDR image from a stream - /// - /// The stream to read from - /// A new HdrImage with the data - public static HdrImage Read(Stream stream) - { - var image = new HdrImage(); - - var line = ReadFromStream(stream); - - if (!(line == "#?RGBE" || line == "#?RADIANCE" || line == "#?AUTOPANO")) - { - throw new FileLoadException("Correct file type specifier was not found."); - } - - var colorSpace = ColorSpace.Rgbe; - - do - { - line = ReadFromStream(stream); - - if (line == null) - { - throw new FileLoadException("Reached end of stream."); - } - - line = line.Trim(); - - if (line == "") - { - break; - } - - if (line.StartsWith("#")) // Found comment - { - continue; - } - - if (line == "FORMAT=32-bit_rle_rgbe") - { - colorSpace = ColorSpace.Rgbe; - } - - else if (line == "FORMAT=32-bit_rle_xyze") - { - colorSpace = ColorSpace.Xyze; - } - - else if (line.StartsWith("EXPOSURE=")) - { - image.exposure = float.Parse(line.Replace("EXPOSURE=", "").Trim(), CultureInfo.InvariantCulture); - } - - else if (line.StartsWith("GAMMA=")) - { - image.gamma = float.Parse(line.Replace("GAMMA=", "").Trim(), CultureInfo.InvariantCulture); - } - - } while (true); - - if (image.exposure < 0.000001) - { - image.exposure = 1.0f; - } - - if (image.gamma < 0.000001) - { - image.gamma = 1.0f; - } - - var imgSize = ReadFromStream(stream).Split(' '); - - var yStr = imgSize[0]; - image.height = int.Parse(imgSize[1]); - var xStr = imgSize[2]; - image.width = int.Parse(imgSize[3]); - - ReadPixels(image, stream); - - if (colorSpace == ColorSpace.Xyze) - { - // Transform colorspace - var xyzColors = MemoryMarshal.Cast(image.pixels.AsSpan()); - for (var i = 0; i < xyzColors.Length; i++) - { - image.pixels[i] = xyzColors[i].ToColorRgbFloat(); - } - } - - return image; - } - - - private static void RleReadChannel(BinaryReader br, Span dest, int width) - { - var i = 0; - var data = new byte[2]; - while (i < width) - { - if (br.Read(data) == 0) - { - throw new FileLoadException("Not enough data in RLE"); - } - if (data[0] > 128) - { - // same byte is repeated many times - var len = data[0] - 128; - for (; len > 0; len--) - { - dest[i++] = data[1]; - } - } - else - { - // different byte sequence - dest[i++] = data[1]; - - var len = data[0] - 1; - if (len > 0) - { - if (br.Read(dest.Slice(i, len)) == 0) - { - throw new FileLoadException("Not enough data in RLE"); - } - i += len; - } - } - } - - if (i != width) - { - throw new FileLoadException("Scanline size was different from width"); - } - } - - - private static void ReadPixels(HdrImage destImage, Stream stream) - { - var height = destImage.height; - var width = destImage.width; - destImage.pixels = new ColorRgbFloat[destImage.height * destImage.width]; - Span bytes = new byte[destImage.width * 4]; - - using var br = new BinaryReader(stream, Encoding.ASCII, true); - - var header = new byte[4]; - - for (var y = 0; y < height; y++) - { - br.Read(header); - - var isRle = header[0] == 2 && header[1] == 2 && - (header[2] << 8) + header[3] == width; // whether the scanline is rle or not - - if (isRle) - { - // for each channel - for (var i = 0; i < 4; i++) - { - RleReadChannel(br, bytes.Slice(width * i, width), width); - } - - for (var x = 0; x < width; x++) - { - var color = new ColorRgbe( - bytes[x + width * 0], - bytes[x + width * 1], - bytes[x + width * 2], - bytes[x + width * 3] - ); - - destImage.pixels[y * width + x] = color.ToColorRgbFloat(destImage.exposure); - } - } - else - { - br.Read(bytes.Slice(4)); - header.CopyTo(bytes); - - for (var x = 0; x < width; x++) - { - var color = new ColorRgbe( - bytes[4 * x + 0], - bytes[4 * x + 1], - bytes[4 * x + 2], - bytes[4 * x + 3] - ); - - destImage.pixels[y * width + x] = color.ToColorRgbFloat(destImage.exposure); - } - } - } - } - - /// - /// Write this file to a stream. - /// - /// The stream to write it to. - public void Write(Stream stream) - { - using var br = new BinaryWriter(stream, Encoding.ASCII, true); - - WriteLineToStream(br, "#?RADIANCE"); - WriteLineToStream(br, "# BCnEncoder.Net HdrImage"); - WriteLineToStream(br, "FORMAT=32-bit_rle_rgbe"); - if (exposure > 0) - { - WriteLineToStream(br, "EXPOSURE=" + exposure.ToString(CultureInfo.InvariantCulture)); - } - if (gamma > 0) - { - WriteLineToStream(br, "GAMMA=" + gamma.ToString(CultureInfo.InvariantCulture)); - } - - WriteLineToStream(br, ""); // Start data with empty row - WriteLineToStream(br, $"-Y {height} +X {width}"); - - WritePixels(br); - } - - private void WritePixels(BinaryWriter br) - { - var buffer = new byte[4]; - var span = PixelSpan; - - for (var y = 0; y < height; y++) - { - for (var x = 0; x < width; x++) - { - var pixel = span[y, x]; - var rgbe = new ColorRgbe(pixel); - - buffer[0] = rgbe.r; - buffer[1] = rgbe.g; - buffer[2] = rgbe.b; - buffer[3] = rgbe.e; - - br.Write(buffer); - } - } - } - } -} diff --git a/BCnEnc.Net/Shared/ITextureFileFormat.cs b/BCnEnc.Net/Shared/ITextureFileFormat.cs new file mode 100644 index 0000000..16122fd --- /dev/null +++ b/BCnEnc.Net/Shared/ITextureFileFormat.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using BCnEncoder.Shared; + +namespace BCnEncoder.TextureFormats +{ + public interface ITextureFileFormat + { + bool SupportsLdr { get; } + bool SupportsHdr { get; } + bool SupportsCubeMap { get; } + bool SupportsMipMaps { get; } + bool SupportsArrays { get; } + + bool IsSupportedFormat(CompressionFormat format); + void FromTextureData(BCnTextureData textureData); + BCnTextureData ToTextureData(); + void ReadFromStream(Stream inputStream); + void WriteToStream(Stream outputStream); + } + public interface ITextureFileFormat : ITextureFileFormat where T : class, ITextureFileFormat, new() + { + public static T Read(Stream inputStream) + { + var tex = new T(); + tex.ReadFromStream(inputStream); + return tex; + } + } + + public static class TextureFileFormatExtensions + { + + } +} diff --git a/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs b/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs deleted file mode 100644 index a1b3172..0000000 --- a/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs +++ /dev/null @@ -1,1099 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; - -namespace BCnEncoder.Shared.ImageFiles -{ - public class DdsFile - { - public DdsHeader header; - public DdsHeaderDx10 dx10Header; - public List Faces { get; } = new List(); - - public DdsFile() { } - public DdsFile(DdsHeader header) - { - this.header = header; - } - - public DdsFile(DdsHeader header, DdsHeaderDx10 dx10Header) - { - this.header = header; - this.dx10Header = dx10Header; - } - - public static DdsFile Load(Stream s) - { - using (var br = new BinaryReader(s, Encoding.UTF8, true)) - { - var magic = br.ReadUInt32(); - if (magic != 0x20534444U) - { - throw new FormatException("The file does not contain a dds file."); - } - var header = br.ReadStruct(); - DdsHeaderDx10 dx10Header = default; - if (header.dwSize != 124) - { - throw new FormatException("The file header contains invalid dwSize."); - } - - var dx10Format = header.ddsPixelFormat.IsDxt10Format; - - DdsFile output; - - if (dx10Format) - { - dx10Header = br.ReadStruct(); - output = new DdsFile(header, dx10Header); - } - else - { - output = new DdsFile(header); - } - - var mipMapCount = (header.dwCaps & HeaderCaps.DdscapsMipmap) != 0 ? header.dwMipMapCount : 1; - var faceCount = (header.dwCaps2 & HeaderCaps2.Ddscaps2Cubemap) != 0 ? 6u : 1u; - var width = header.dwWidth; - var height = header.dwHeight; - - for (var face = 0; face < faceCount; face++) - { - var format = dx10Format ? dx10Header.dxgiFormat : header.ddsPixelFormat.DxgiFormat; - var sizeInBytes = GetSizeInBytes(format, width, height); - - output.Faces.Add(new DdsFace(width, height, sizeInBytes, (int)mipMapCount)); - - for (var mip = 0; mip < mipMapCount; mip++) - { - MipMapper.CalculateMipLevelSize( - (int)header.dwWidth, - (int)header.dwHeight, - mip, - out var mipWidth, - out var mipHeight); - - if (mip > 0) //Calculate new byteSize - { - sizeInBytes = GetSizeInBytes(format, (uint)mipWidth, (uint)mipHeight); - } - - var data = new byte[sizeInBytes]; - br.Read(data); - output.Faces[face].MipMaps[mip] = new DdsMipMap(data, (uint)mipWidth, (uint)mipHeight); - } - } - - return output; - } - } - - public void Write(Stream outputStream) - { - if (Faces.Count < 1 || Faces[0].MipMaps.Length < 1) - { - throw new InvalidOperationException("The DDS structure should have at least 1 mipmap level and 1 Face before writing to file."); - } - - header.dwFlags |= HeaderFlags.Required; - - header.dwMipMapCount = (uint)Faces[0].MipMaps.Length; - if (header.dwMipMapCount > 1) // MipMaps - { - header.dwCaps |= HeaderCaps.DdscapsMipmap | HeaderCaps.DdscapsComplex; - } - if (Faces.Count == 6) // CubeMap - { - header.dwCaps |= HeaderCaps.DdscapsComplex; - header.dwCaps2 |= HeaderCaps2.Ddscaps2Cubemap | - HeaderCaps2.Ddscaps2CubemapPositivex | - HeaderCaps2.Ddscaps2CubemapNegativex | - HeaderCaps2.Ddscaps2CubemapPositivey | - HeaderCaps2.Ddscaps2CubemapNegativey | - HeaderCaps2.Ddscaps2CubemapPositivez | - HeaderCaps2.Ddscaps2CubemapNegativez; - } - - header.dwWidth = Faces[0].Width; - header.dwHeight = Faces[0].Height; - - for (var i = 0; i < Faces.Count; i++) - { - if (Faces[i].Width != header.dwWidth || Faces[i].Height != header.dwHeight) - { - throw new InvalidOperationException("Faces with different sizes are not supported."); - } - } - - var faceCount = Faces.Count; - var mipCount = (int)header.dwMipMapCount; - - using (var bw = new BinaryWriter(outputStream, Encoding.UTF8, true)) - { - bw.Write(0x20534444U); // magic 'DDS ' - - bw.WriteStruct(header); - - if (header.ddsPixelFormat.IsDxt10Format) - { - bw.WriteStruct(dx10Header); - } - - for (var face = 0; face < faceCount; face++) - { - for (var mip = 0; mip < mipCount; mip++) - { - bw.Write(Faces[face].MipMaps[mip].Data); - } - } - } - } - - private static uint GetSizeInBytes(DxgiFormat format, uint width, uint height) - { - uint sizeInBytes; - if (format.IsCompressedFormat()) - { - sizeInBytes = (uint)ImageToBlocks.CalculateNumOfBlocks((int)width, (int)height); - sizeInBytes *= (uint)format.GetByteSize(); - } - else - { - sizeInBytes = width * height; - sizeInBytes = (uint)(sizeInBytes * format.GetByteSize()); - } - - return sizeInBytes; - } - } - - [StructLayout(LayoutKind.Sequential)] - public unsafe struct DdsHeader - { - /// - /// Has to be 124 - /// - public uint dwSize; - public HeaderFlags dwFlags; - public uint dwHeight; - public uint dwWidth; - public uint dwPitchOrLinearSize; - public uint dwDepth; - public uint dwMipMapCount; - public fixed uint dwReserved1[11]; - public DdsPixelFormat ddsPixelFormat; - public HeaderCaps dwCaps; - public HeaderCaps2 dwCaps2; - public uint dwCaps3; - public uint dwCaps4; - public uint dwReserved2; - - public static (DdsHeader, DdsHeaderDx10) InitializeCompressed(int width, int height, DxgiFormat format, bool preferDxt10Header) - { - var header = new DdsHeader(); - var dxt10Header = new DdsHeaderDx10(); - - header.dwSize = 124; - header.dwFlags = HeaderFlags.Required; - header.dwWidth = (uint)width; - header.dwHeight = (uint)height; - header.dwDepth = 1; - header.dwMipMapCount = 1; - header.dwCaps = HeaderCaps.DdscapsTexture; - - if (preferDxt10Header) - { - // ATC formats cannot be written to DXT10 header due to lack of a DxgiFormat enum - switch (format) - { - case DxgiFormat.DxgiFormatAtcExt: - header.ddsPixelFormat = new DdsPixelFormat - { - dwSize = 32, - dwFlags = PixelFormatFlags.DdpfFourcc, - dwFourCc = DdsPixelFormat.Atc - }; - break; - - case DxgiFormat.DxgiFormatAtcExplicitAlphaExt: - header.ddsPixelFormat = new DdsPixelFormat - { - dwSize = 32, - dwFlags = PixelFormatFlags.DdpfFourcc, - dwFourCc = DdsPixelFormat.Atci - }; - break; - - case DxgiFormat.DxgiFormatAtcInterpolatedAlphaExt: - header.ddsPixelFormat = new DdsPixelFormat - { - dwSize = 32, - dwFlags = PixelFormatFlags.DdpfFourcc, - dwFourCc = DdsPixelFormat.Atca - }; - break; - - default: - header.ddsPixelFormat = new DdsPixelFormat - { - dwSize = 32, - dwFlags = PixelFormatFlags.DdpfFourcc, - dwFourCc = DdsPixelFormat.Dx10 - }; - dxt10Header.arraySize = 1; - dxt10Header.dxgiFormat = format; - dxt10Header.resourceDimension = D3D10ResourceDimension.D3D10ResourceDimensionTexture2D; - break; - } - } - else - { - switch (format) - { - case DxgiFormat.DxgiFormatBc1Unorm: - header.ddsPixelFormat = new DdsPixelFormat - { - dwSize = 32, - dwFlags = PixelFormatFlags.DdpfFourcc, - dwFourCc = DdsPixelFormat.Dxt1 - }; - break; - - case DxgiFormat.DxgiFormatBc2Unorm: - header.ddsPixelFormat = new DdsPixelFormat - { - dwSize = 32, - dwFlags = PixelFormatFlags.DdpfFourcc, - dwFourCc = DdsPixelFormat.Dxt3 - }; - break; - - case DxgiFormat.DxgiFormatBc3Unorm: - header.ddsPixelFormat = new DdsPixelFormat - { - dwSize = 32, - dwFlags = PixelFormatFlags.DdpfFourcc, - dwFourCc = DdsPixelFormat.Dxt5 - }; - break; - - case DxgiFormat.DxgiFormatBc4Unorm: - header.ddsPixelFormat = new DdsPixelFormat - { - dwSize = 32, - dwFlags = PixelFormatFlags.DdpfFourcc, - dwFourCc = DdsPixelFormat.Bc4U - }; - break; - - case DxgiFormat.DxgiFormatBc5Unorm: - header.ddsPixelFormat = new DdsPixelFormat - { - dwSize = 32, - dwFlags = PixelFormatFlags.DdpfFourcc, - dwFourCc = DdsPixelFormat.Ati2 - }; - break; - - case DxgiFormat.DxgiFormatAtcExt: - header.ddsPixelFormat = new DdsPixelFormat - { - dwSize = 32, - dwFlags = PixelFormatFlags.DdpfFourcc, - dwFourCc = DdsPixelFormat.Atc - }; - break; - - case DxgiFormat.DxgiFormatAtcExplicitAlphaExt: - header.ddsPixelFormat = new DdsPixelFormat - { - dwSize = 32, - dwFlags = PixelFormatFlags.DdpfFourcc, - dwFourCc = DdsPixelFormat.Atci - }; - break; - - case DxgiFormat.DxgiFormatAtcInterpolatedAlphaExt: - header.ddsPixelFormat = new DdsPixelFormat - { - dwSize = 32, - dwFlags = PixelFormatFlags.DdpfFourcc, - dwFourCc = DdsPixelFormat.Atca - }; - break; - - default: - header.ddsPixelFormat = new DdsPixelFormat - { - dwSize = 32, - dwFlags = PixelFormatFlags.DdpfFourcc, - dwFourCc = DdsPixelFormat.Dx10 - }; - dxt10Header.arraySize = 1; - dxt10Header.dxgiFormat = format; - dxt10Header.resourceDimension = D3D10ResourceDimension.D3D10ResourceDimensionTexture2D; - break; - } - } - - return (header, dxt10Header); - } - - public static DdsHeader InitializeUncompressed(int width, int height, DxgiFormat format) - { - var header = new DdsHeader(); - - header.dwSize = 124; - header.dwFlags = HeaderFlags.Required | HeaderFlags.DdsdPitch; - header.dwWidth = (uint)width; - header.dwHeight = (uint)height; - header.dwDepth = 1; - header.dwMipMapCount = 1; - header.dwCaps = HeaderCaps.DdscapsTexture; - - if (format == DxgiFormat.DxgiFormatR8Unorm) - { - header.ddsPixelFormat = new DdsPixelFormat() - { - dwSize = 32, - dwFlags = PixelFormatFlags.DdpfLuminance, - dwRgbBitCount = 8, - dwRBitMask = 0xFF - }; - header.dwPitchOrLinearSize = (uint)((width * 8 + 7) / 8); - } - else if (format == DxgiFormat.DxgiFormatR8G8Unorm) - { - header.ddsPixelFormat = new DdsPixelFormat() - { - dwSize = 32, - dwFlags = PixelFormatFlags.DdpfLuminance | PixelFormatFlags.DdpfAlphaPixels, - dwRgbBitCount = 16, - dwRBitMask = 0xFF, - dwGBitMask = 0xFF00 - }; - header.dwPitchOrLinearSize = (uint)((width * 16 + 7) / 8); - } - else if (format == DxgiFormat.DxgiFormatR8G8B8A8Unorm) - { - header.ddsPixelFormat = new DdsPixelFormat() - { - dwSize = 32, - dwFlags = PixelFormatFlags.DdpfRgb | PixelFormatFlags.DdpfAlphaPixels, - dwRgbBitCount = 32, - dwRBitMask = 0xFF, - dwGBitMask = 0xFF00, - dwBBitMask = 0xFF0000, - dwABitMask = 0xFF000000, - }; - header.dwPitchOrLinearSize = (uint)((width * 32 + 7) / 8); - } - else if (format == DxgiFormat.DxgiFormatB8G8R8A8Unorm) - { - header.ddsPixelFormat = new DdsPixelFormat() - { - dwSize = 32, - dwFlags = PixelFormatFlags.DdpfRgb | PixelFormatFlags.DdpfAlphaPixels, - dwRgbBitCount = 32, - dwRBitMask = 0xFF0000, - dwGBitMask = 0xFF00, - dwBBitMask = 0xFF, - dwABitMask = 0xFF000000, - }; - header.dwPitchOrLinearSize = (uint)((width * 32 + 7) / 8); - } - else - { - throw new NotImplementedException("This Format is not implemented in this method"); - } - - return header; - } - } - - public struct DdsPixelFormat - { - public static readonly uint Dx10 = MakeFourCc('D', 'X', '1', '0'); - - public static readonly uint Dxt1 = MakeFourCc('D', 'X', 'T', '1'); - public static readonly uint Dxt2 = MakeFourCc('D', 'X', 'T', '2'); - public static readonly uint Dxt3 = MakeFourCc('D', 'X', 'T', '3'); - public static readonly uint Dxt4 = MakeFourCc('D', 'X', 'T', '4'); - public static readonly uint Dxt5 = MakeFourCc('D', 'X', 'T', '5'); - public static readonly uint Ati1 = MakeFourCc('A', 'T', 'I', '1'); - public static readonly uint Ati2 = MakeFourCc('A', 'T', 'I', '2'); - public static readonly uint Atc = MakeFourCc('A', 'T', 'C', ' '); - public static readonly uint Atci = MakeFourCc('A', 'T', 'C', 'I'); - public static readonly uint Atca = MakeFourCc('A', 'T', 'C', 'A'); - - public static readonly uint Bc4S = MakeFourCc('B', 'C', '4', 'S'); - public static readonly uint Bc4U = MakeFourCc('B', 'C', '4', 'U'); - public static readonly uint Bc5S = MakeFourCc('B', 'C', '5', 'S'); - public static readonly uint Bc5U = MakeFourCc('B', 'C', '5', 'U'); - - private static uint MakeFourCc(char c0, char c1, char c2, char c3) - { - uint result = c0; - result |= (uint)c1 << 8; - result |= (uint)c2 << 16; - result |= (uint)c3 << 24; - return result; - } - - public uint dwSize; - public PixelFormatFlags dwFlags; - public uint dwFourCc; - public uint dwRgbBitCount; - public uint dwRBitMask; - public uint dwGBitMask; - public uint dwBBitMask; - public uint dwABitMask; - - public DxgiFormat DxgiFormat - { - get - { - if (dwFlags.HasFlag(PixelFormatFlags.DdpfFourcc)) - { - if (dwFourCc == Dxt1) return DxgiFormat.DxgiFormatBc1Unorm; - if (dwFourCc == Dxt2 || dwFourCc == Dxt3) return DxgiFormat.DxgiFormatBc2Unorm; - if (dwFourCc == Dxt4 || dwFourCc == Dxt5) return DxgiFormat.DxgiFormatBc3Unorm; - if (dwFourCc == Ati1 || dwFourCc == Bc4S || dwFourCc == Bc4U) return DxgiFormat.DxgiFormatBc4Unorm; - if (dwFourCc == Ati2 || dwFourCc == Bc5S || dwFourCc == Bc5U) return DxgiFormat.DxgiFormatBc5Unorm; - if (dwFourCc == Atc) return DxgiFormat.DxgiFormatAtcExt; - if (dwFourCc == Atci) return DxgiFormat.DxgiFormatAtcExplicitAlphaExt; - if (dwFourCc == Atca) return DxgiFormat.DxgiFormatAtcInterpolatedAlphaExt; - } - else - { - if (dwFlags.HasFlag(PixelFormatFlags.DdpfRgb)) // RGB/A - { - if (dwFlags.HasFlag(PixelFormatFlags.DdpfAlphaPixels)) //RGBA - { - if (dwRgbBitCount == 32) - { - if (dwRBitMask == 0xff && dwGBitMask == 0xff00 && dwBBitMask == 0xff0000 && - dwABitMask == 0xff000000) - { - return DxgiFormat.DxgiFormatR8G8B8A8Unorm; - } - - if (dwRBitMask == 0xff0000 && dwGBitMask == 0xff00 && dwBBitMask == 0xff && - dwABitMask == 0xff000000) - { - return DxgiFormat.DxgiFormatB8G8R8A8Unorm; - } - } - } - else //RGB - { - if (dwRgbBitCount == 32) - { - if (dwRBitMask == 0xff0000 && dwGBitMask == 0xff00 && dwBBitMask == 0xff) - { - return DxgiFormat.DxgiFormatB8G8R8X8Unorm; - } - } - } - } - else if (dwFlags.HasFlag(PixelFormatFlags.DdpfLuminance)) // R/RG - { - if (dwFlags.HasFlag(PixelFormatFlags.DdpfAlphaPixels)) // RG - { - if (dwRgbBitCount == 16) - { - if (dwRBitMask == 0xff && dwGBitMask == 0xff00) - { - return DxgiFormat.DxgiFormatR8G8Unorm; - } - } - } - else // Luminance only - { - if (dwRgbBitCount == 8) - { - if (dwRBitMask == 0xff) - { - return DxgiFormat.DxgiFormatR8Unorm; - } - } - } - } - } - return DxgiFormat.DxgiFormatUnknown; - } - } - - public bool IsDxt10Format => (dwFlags & PixelFormatFlags.DdpfFourcc) == PixelFormatFlags.DdpfFourcc - && dwFourCc == Dx10; - } - - public struct DdsHeaderDx10 - { - public DxgiFormat dxgiFormat; - public D3D10ResourceDimension resourceDimension; - public uint miscFlag; - public uint arraySize; - public uint miscFlags2; - } - - public class DdsFace - { - public uint Width { get; set; } - public uint Height { get; set; } - public uint SizeInBytes { get; } - public DdsMipMap[] MipMaps { get; } - - public DdsFace(uint width, uint height, uint sizeInBytes, int numMipMaps) - { - Width = width; - Height = height; - SizeInBytes = sizeInBytes; - MipMaps = new DdsMipMap[numMipMaps]; - } - } - - public class DdsMipMap - { - public uint Width { get; set; } - public uint Height { get; set; } - public uint SizeInBytes { get; } - public byte[] Data { get; } - public DdsMipMap(byte[] data, uint width, uint height) - { - Width = width; - Height = height; - SizeInBytes = (uint)data.Length; - Data = data; - } - } - - /// - /// Flags to indicate which members contain valid data. - /// - [Flags] - public enum HeaderFlags : uint - { - /// - /// Required in every .dds file. - /// - DdsdCaps = 0x1, - /// - /// Required in every .dds file. - /// - DdsdHeight = 0x2, - /// - /// Required in every .dds file. - /// - DdsdWidth = 0x4, - /// - /// Required when pitch is provided for an uncompressed texture. - /// - DdsdPitch = 0x8, - /// - /// Required in every .dds file. - /// - DdsdPixelformat = 0x1000, - /// - /// Required in a mipmapped texture. - /// - DdsdMipmapcount = 0x20000, - /// - /// Required when pitch is provided for a compressed texture. - /// - DdsdLinearsize = 0x80000, - /// - /// Required in a depth texture. - /// - DdsdDepth = 0x800000, - - Required = DdsdCaps | DdsdHeight | DdsdWidth | DdsdPixelformat - } - - /// - /// Specifies the complexity of the surfaces stored. - /// - [Flags] - public enum HeaderCaps : uint - { - /// - /// Optional; must be used on any file that contains more than one surface (a mipmap, a cubic environment map, or mipmapped volume texture). - /// - DdscapsComplex = 0x8, - /// - /// Optional; should be used for a mipmap. - /// - DdscapsMipmap = 0x400000, - /// - /// Required - /// - DdscapsTexture = 0x1000 - } - - /// - /// Additional detail about the surfaces stored. - /// - [Flags] - public enum HeaderCaps2 : uint - { - /// - /// Required for a cube map. - /// - Ddscaps2Cubemap = 0x200, - /// - /// Required when these surfaces are stored in a cube map. - /// - Ddscaps2CubemapPositivex = 0x400, - /// - /// Required when these surfaces are stored in a cube map. - /// - Ddscaps2CubemapNegativex = 0x800, - /// - /// Required when these surfaces are stored in a cube map. - /// - Ddscaps2CubemapPositivey = 0x1000, - /// - /// Required when these surfaces are stored in a cube map. - /// - Ddscaps2CubemapNegativey = 0x2000, - /// - /// Required when these surfaces are stored in a cube map. - /// - Ddscaps2CubemapPositivez = 0x4000, - /// - /// Required when these surfaces are stored in a cube map. - /// - Ddscaps2CubemapNegativez = 0x8000, - /// - /// Required for a volume texture. - /// - Ddscaps2Volume = 0x200000 - } - - [Flags] - public enum PixelFormatFlags : uint - { - /// - /// Texture contains alpha data; dwRGBAlphaBitMask contains valid data. - /// - DdpfAlphaPixels = 0x1, - /// - /// Used in some older DDS files for alpha channel only uncompressed data (dwRGBBitCount contains the alpha channel bitcount; dwABitMask contains valid data) - /// - DdpfAlpha = 0x2, - /// - /// Texture contains compressed RGB data; dwFourCC contains valid data. - /// - DdpfFourcc = 0x4, - /// - /// Texture contains uncompressed RGB data; dwRGBBitCount and the RGB masks (dwRBitMask, dwGBitMask, dwBBitMask) contain valid data. - /// - DdpfRgb = 0x40, - /// - /// Used in some older DDS files for YUV uncompressed data (dwRGBBitCount contains the YUV bit count; dwRBitMask contains the Y mask, dwGBitMask contains the U mask, dwBBitMask contains the V mask) - /// - DdpfYuv = 0x200, - /// - /// Used in some older DDS files for single channel color uncompressed data (dwRGBBitCount contains the luminance channel bit count; dwRBitMask contains the channel mask). Can be combined with DDPF_ALPHAPIXELS for a two channel DDS file. - /// - DdpfLuminance = 0x20000 - } - - public enum D3D10ResourceDimension : uint - { - D3D10ResourceDimensionUnknown, - D3D10ResourceDimensionBuffer, - D3D10ResourceDimensionTexture1D, - D3D10ResourceDimensionTexture2D, - D3D10ResourceDimensionTexture3D - }; - - public enum DxgiFormat : uint - { - DxgiFormatUnknown, - DxgiFormatR32G32B32A32Typeless, - DxgiFormatR32G32B32A32Float, - DxgiFormatR32G32B32A32Uint, - DxgiFormatR32G32B32A32Sint, - DxgiFormatR32G32B32Typeless, - DxgiFormatR32G32B32Float, - DxgiFormatR32G32B32Uint, - DxgiFormatR32G32B32Sint, - DxgiFormatR16G16B16A16Typeless, - DxgiFormatR16G16B16A16Float, - DxgiFormatR16G16B16A16Unorm, - DxgiFormatR16G16B16A16Uint, - DxgiFormatR16G16B16A16Snorm, - DxgiFormatR16G16B16A16Sint, - DxgiFormatR32G32Typeless, - DxgiFormatR32G32Float, - DxgiFormatR32G32Uint, - DxgiFormatR32G32Sint, - DxgiFormatR32G8X24Typeless, - DxgiFormatD32FloatS8X24Uint, - DxgiFormatR32FloatX8X24Typeless, - DxgiFormatX32TypelessG8X24Uint, - DxgiFormatR10G10B10A2Typeless, - DxgiFormatR10G10B10A2Unorm, - DxgiFormatR10G10B10A2Uint, - DxgiFormatR11G11B10Float, - DxgiFormatR8G8B8A8Typeless, - DxgiFormatR8G8B8A8Unorm, - DxgiFormatR8G8B8A8UnormSrgb, - DxgiFormatR8G8B8A8Uint, - DxgiFormatR8G8B8A8Snorm, - DxgiFormatR8G8B8A8Sint, - DxgiFormatR16G16Typeless, - DxgiFormatR16G16Float, - DxgiFormatR16G16Unorm, - DxgiFormatR16G16Uint, - DxgiFormatR16G16Snorm, - DxgiFormatR16G16Sint, - DxgiFormatR32Typeless, - DxgiFormatD32Float, - DxgiFormatR32Float, - DxgiFormatR32Uint, - DxgiFormatR32Sint, - DxgiFormatR24G8Typeless, - DxgiFormatD24UnormS8Uint, - DxgiFormatR24UnormX8Typeless, - DxgiFormatX24TypelessG8Uint, - DxgiFormatR8G8Typeless, - DxgiFormatR8G8Unorm, - DxgiFormatR8G8Uint, - DxgiFormatR8G8Snorm, - DxgiFormatR8G8Sint, - DxgiFormatR16Typeless, - DxgiFormatR16Float, - DxgiFormatD16Unorm, - DxgiFormatR16Unorm, - DxgiFormatR16Uint, - DxgiFormatR16Snorm, - DxgiFormatR16Sint, - DxgiFormatR8Typeless, - DxgiFormatR8Unorm, - DxgiFormatR8Uint, - DxgiFormatR8Snorm, - DxgiFormatR8Sint, - DxgiFormatA8Unorm, - DxgiFormatR1Unorm, - DxgiFormatR9G9B9E5Sharedexp, - DxgiFormatR8G8B8G8Unorm, - DxgiFormatG8R8G8B8Unorm, - DxgiFormatBc1Typeless, - DxgiFormatBc1Unorm, - DxgiFormatBc1UnormSrgb, - DxgiFormatBc2Typeless, - DxgiFormatBc2Unorm, - DxgiFormatBc2UnormSrgb, - DxgiFormatBc3Typeless, - DxgiFormatBc3Unorm, - DxgiFormatBc3UnormSrgb, - DxgiFormatBc4Typeless, - DxgiFormatBc4Unorm, - DxgiFormatBc4Snorm, - DxgiFormatBc5Typeless, - DxgiFormatBc5Unorm, - DxgiFormatBc5Snorm, - DxgiFormatB5G6R5Unorm, - DxgiFormatB5G5R5A1Unorm, - DxgiFormatB8G8R8A8Unorm, - DxgiFormatB8G8R8X8Unorm, - DxgiFormatR10G10B10XrBiasA2Unorm, - DxgiFormatB8G8R8A8Typeless, - DxgiFormatB8G8R8A8UnormSrgb, - DxgiFormatB8G8R8X8Typeless, - DxgiFormatB8G8R8X8UnormSrgb, - DxgiFormatBc6HTypeless, - DxgiFormatBc6HUf16, - DxgiFormatBc6HSf16, - DxgiFormatBc7Typeless, - DxgiFormatBc7Unorm, - DxgiFormatBc7UnormSrgb, - DxgiFormatAyuv, - DxgiFormatY410, - DxgiFormatY416, - DxgiFormatNv12, - DxgiFormatP010, - DxgiFormatP016, - DxgiFormat420Opaque, - DxgiFormatYuy2, - DxgiFormatY210, - DxgiFormatY216, - DxgiFormatNv11, - DxgiFormatAi44, - DxgiFormatIa44, - DxgiFormatP8, - DxgiFormatA8P8, - DxgiFormatB4G4R4A4Unorm, - DxgiFormatP208, - DxgiFormatV208, - DxgiFormatV408, - DxgiFormatForceUint, - - // Added here due to lack of an official value - DxgiFormatAtcExt = 300, - DxgiFormatAtcExplicitAlphaExt, - DxgiFormatAtcInterpolatedAlphaExt - }; - - public static class DxgiFormatExtensions - { - public static int GetByteSize(this DxgiFormat format) - { - switch (format) - { - case DxgiFormat.DxgiFormatUnknown: - return 4; - case DxgiFormat.DxgiFormatR32G32B32A32Typeless: - return 4 * 4; - case DxgiFormat.DxgiFormatR32G32B32A32Float: - return 4 * 4; - case DxgiFormat.DxgiFormatR32G32B32A32Uint: - return 4 * 4; - case DxgiFormat.DxgiFormatR32G32B32A32Sint: - return 4 * 4; - case DxgiFormat.DxgiFormatR32G32B32Typeless: - return 4 * 3; - case DxgiFormat.DxgiFormatR32G32B32Float: - return 4 * 3; - case DxgiFormat.DxgiFormatR32G32B32Uint: - return 4 * 3; - case DxgiFormat.DxgiFormatR32G32B32Sint: - return 4 * 3; - case DxgiFormat.DxgiFormatR16G16B16A16Typeless: - return 4 * 2; - case DxgiFormat.DxgiFormatR16G16B16A16Float: - return 4 * 2; - case DxgiFormat.DxgiFormatR16G16B16A16Unorm: - return 4 * 2; - case DxgiFormat.DxgiFormatR16G16B16A16Uint: - return 4 * 2; - case DxgiFormat.DxgiFormatR16G16B16A16Snorm: - return 4 * 2; - case DxgiFormat.DxgiFormatR16G16B16A16Sint: - return 4 * 2; - case DxgiFormat.DxgiFormatR32G32Typeless: - return 4 * 2; - case DxgiFormat.DxgiFormatR32G32Float: - return 4 * 2; - case DxgiFormat.DxgiFormatR32G32Uint: - return 4 * 2; - case DxgiFormat.DxgiFormatR32G32Sint: - return 4 * 2; - case DxgiFormat.DxgiFormatR32G8X24Typeless: - return 4 * 2; - case DxgiFormat.DxgiFormatD32FloatS8X24Uint: - return 4; - case DxgiFormat.DxgiFormatR32FloatX8X24Typeless: - return 4; - case DxgiFormat.DxgiFormatX32TypelessG8X24Uint: - return 4; - case DxgiFormat.DxgiFormatR10G10B10A2Typeless: - return 4; - case DxgiFormat.DxgiFormatR10G10B10A2Unorm: - return 4; - case DxgiFormat.DxgiFormatR10G10B10A2Uint: - return 4; - case DxgiFormat.DxgiFormatR11G11B10Float: - return 4; - case DxgiFormat.DxgiFormatR8G8B8A8Typeless: - return 4; - case DxgiFormat.DxgiFormatR8G8B8A8Unorm: - return 4; - case DxgiFormat.DxgiFormatR8G8B8A8UnormSrgb: - return 4; - case DxgiFormat.DxgiFormatR8G8B8A8Uint: - return 4; - case DxgiFormat.DxgiFormatR8G8B8A8Snorm: - return 4; - case DxgiFormat.DxgiFormatR8G8B8A8Sint: - return 4; - case DxgiFormat.DxgiFormatR16G16Typeless: - return 4; - case DxgiFormat.DxgiFormatR16G16Float: - return 4; - case DxgiFormat.DxgiFormatR16G16Unorm: - return 4; - case DxgiFormat.DxgiFormatR16G16Uint: - return 4; - case DxgiFormat.DxgiFormatR16G16Snorm: - return 4; - case DxgiFormat.DxgiFormatR16G16Sint: - return 4; - case DxgiFormat.DxgiFormatR32Typeless: - return 4; - case DxgiFormat.DxgiFormatD32Float: - return 4; - case DxgiFormat.DxgiFormatR32Float: - return 4; - case DxgiFormat.DxgiFormatR32Uint: - return 4; - case DxgiFormat.DxgiFormatR32Sint: - return 4; - case DxgiFormat.DxgiFormatR24G8Typeless: - return 4; - case DxgiFormat.DxgiFormatD24UnormS8Uint: - return 4; - case DxgiFormat.DxgiFormatR24UnormX8Typeless: - return 4; - case DxgiFormat.DxgiFormatX24TypelessG8Uint: - return 4; - case DxgiFormat.DxgiFormatR8G8Typeless: - return 2; - case DxgiFormat.DxgiFormatR8G8Unorm: - return 2; - case DxgiFormat.DxgiFormatR8G8Uint: - return 2; - case DxgiFormat.DxgiFormatR8G8Snorm: - return 2; - case DxgiFormat.DxgiFormatR8G8Sint: - return 2; - case DxgiFormat.DxgiFormatR16Typeless: - return 2; - case DxgiFormat.DxgiFormatR16Float: - return 2; - case DxgiFormat.DxgiFormatD16Unorm: - return 2; - case DxgiFormat.DxgiFormatR16Unorm: - return 2; - case DxgiFormat.DxgiFormatR16Uint: - return 2; - case DxgiFormat.DxgiFormatR16Snorm: - return 2; - case DxgiFormat.DxgiFormatR16Sint: - return 2; - case DxgiFormat.DxgiFormatR8Typeless: - return 1; - case DxgiFormat.DxgiFormatR8Unorm: - return 1; - case DxgiFormat.DxgiFormatR8Uint: - return 1; - case DxgiFormat.DxgiFormatR8Snorm: - return 1; - case DxgiFormat.DxgiFormatR8Sint: - return 1; - case DxgiFormat.DxgiFormatA8Unorm: - return 1; - case DxgiFormat.DxgiFormatR1Unorm: - return 1; - case DxgiFormat.DxgiFormatR9G9B9E5Sharedexp: - return 4; - case DxgiFormat.DxgiFormatR8G8B8G8Unorm: - return 4; - case DxgiFormat.DxgiFormatG8R8G8B8Unorm: - return 4; - case DxgiFormat.DxgiFormatBc1Typeless: - return 8; - case DxgiFormat.DxgiFormatBc1Unorm: - return 8; - case DxgiFormat.DxgiFormatBc1UnormSrgb: - return 8; - case DxgiFormat.DxgiFormatBc2Typeless: - return 16; - case DxgiFormat.DxgiFormatBc2Unorm: - return 16; - case DxgiFormat.DxgiFormatBc2UnormSrgb: - return 16; - case DxgiFormat.DxgiFormatBc3Typeless: - return 16; - case DxgiFormat.DxgiFormatBc3Unorm: - return 16; - case DxgiFormat.DxgiFormatBc3UnormSrgb: - return 16; - case DxgiFormat.DxgiFormatBc4Typeless: - return 8; - case DxgiFormat.DxgiFormatBc4Unorm: - return 8; - case DxgiFormat.DxgiFormatBc4Snorm: - return 8; - case DxgiFormat.DxgiFormatBc5Typeless: - return 16; - case DxgiFormat.DxgiFormatBc5Unorm: - return 16; - case DxgiFormat.DxgiFormatBc5Snorm: - return 16; - case DxgiFormat.DxgiFormatB5G6R5Unorm: - return 2; - case DxgiFormat.DxgiFormatB5G5R5A1Unorm: - return 2; - case DxgiFormat.DxgiFormatB8G8R8A8Unorm: - return 4; - case DxgiFormat.DxgiFormatB8G8R8X8Unorm: - return 4; - case DxgiFormat.DxgiFormatR10G10B10XrBiasA2Unorm: - return 4; - case DxgiFormat.DxgiFormatB8G8R8A8Typeless: - return 4; - case DxgiFormat.DxgiFormatB8G8R8A8UnormSrgb: - return 4; - case DxgiFormat.DxgiFormatB8G8R8X8Typeless: - return 4; - case DxgiFormat.DxgiFormatB8G8R8X8UnormSrgb: - return 4; - case DxgiFormat.DxgiFormatBc6HTypeless: - return 16; - case DxgiFormat.DxgiFormatBc6HUf16: - return 16; - case DxgiFormat.DxgiFormatBc6HSf16: - return 16; - case DxgiFormat.DxgiFormatBc7Typeless: - return 16; - case DxgiFormat.DxgiFormatBc7Unorm: - return 16; - case DxgiFormat.DxgiFormatBc7UnormSrgb: - return 16; - case DxgiFormat.DxgiFormatP8: - return 1; - case DxgiFormat.DxgiFormatA8P8: - return 2; - case DxgiFormat.DxgiFormatB4G4R4A4Unorm: - return 2; - case DxgiFormat.DxgiFormatAtcExt: - return 8; - case DxgiFormat.DxgiFormatAtcExplicitAlphaExt: - return 16; - case DxgiFormat.DxgiFormatAtcInterpolatedAlphaExt: - return 16; - } - return 4; - } - - public static bool IsCompressedFormat(this DxgiFormat format) - { - switch (format) - { - case DxgiFormat.DxgiFormatBc1Typeless: - case DxgiFormat.DxgiFormatBc1Unorm: - case DxgiFormat.DxgiFormatBc1UnormSrgb: - case DxgiFormat.DxgiFormatBc2Typeless: - case DxgiFormat.DxgiFormatBc2Unorm: - case DxgiFormat.DxgiFormatBc2UnormSrgb: - case DxgiFormat.DxgiFormatBc3Typeless: - case DxgiFormat.DxgiFormatBc3Unorm: - case DxgiFormat.DxgiFormatBc3UnormSrgb: - case DxgiFormat.DxgiFormatBc4Typeless: - case DxgiFormat.DxgiFormatBc4Unorm: - case DxgiFormat.DxgiFormatBc4Snorm: - case DxgiFormat.DxgiFormatBc5Typeless: - case DxgiFormat.DxgiFormatBc5Unorm: - case DxgiFormat.DxgiFormatBc5Snorm: - case DxgiFormat.DxgiFormatBc6HTypeless: - case DxgiFormat.DxgiFormatBc6HUf16: - case DxgiFormat.DxgiFormatBc6HSf16: - case DxgiFormat.DxgiFormatBc7Typeless: - case DxgiFormat.DxgiFormatBc7Unorm: - case DxgiFormat.DxgiFormatBc7UnormSrgb: - case DxgiFormat.DxgiFormatAtcExt: - case DxgiFormat.DxgiFormatAtcExplicitAlphaExt: - case DxgiFormat.DxgiFormatAtcInterpolatedAlphaExt: - return true; - - default: - return false; - } - } - } -} diff --git a/BCnEnc.Net/Shared/ImageFiles/ImageFile.cs b/BCnEnc.Net/Shared/ImageFiles/ImageFile.cs deleted file mode 100644 index b3882d3..0000000 --- a/BCnEnc.Net/Shared/ImageFiles/ImageFile.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.IO; -using System.Linq; -using System.Text; - -namespace BCnEncoder.Shared.ImageFiles -{ - /// - /// The format identifier of an image file. - /// - public enum ImageFileFormat - { - /// - /// Represents the KTX image file format. - /// - Ktx, - - /// - /// Represents the DDS image file format. - /// - Dds, - - /// - /// Represents an unknown image file format. - /// - Unknown - } - - /// - /// Static helper class to determine the format of an image file. - /// - public static class ImageFile - { - private static readonly byte[] ktx1Identifier = { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; - - /// - /// Determines the image file format of the given stream. - /// - /// The stream of data to identify. - /// The format this image file may contain. - public static ImageFileFormat DetermineImageFormat(Stream stream) - { - if (IsDds(stream)) - { - return ImageFileFormat.Dds; - } - - if (IsKtx(stream)) - { - return ImageFileFormat.Ktx; - } - - return ImageFileFormat.Unknown; - } - - private static bool IsDds(Stream stream) - { - using var br = new BinaryReader(stream, Encoding.UTF8, true); - - var magic = br.ReadUInt32(); - stream.Position -= 4; - - return magic == 0x20534444U; - } - - private static bool IsKtx(Stream stream) - { - // Only checks for version 1 - using var br = new BinaryReader(stream, Encoding.ASCII, true); - - var identifier = br.ReadBytes(12); - stream.Position -= 12; - - return identifier.SequenceEqual(ktx1Identifier); - } - } -} diff --git a/BCnEnc.Net/Shared/ImageFiles/KtxFile.cs b/BCnEnc.Net/Shared/ImageFiles/KtxFile.cs deleted file mode 100644 index b090986..0000000 --- a/BCnEnc.Net/Shared/ImageFiles/KtxFile.cs +++ /dev/null @@ -1,374 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using static System.Text.Encoding; - -namespace BCnEncoder.Shared.ImageFiles -{ - /// - /// A representation of a ktx file. - /// This class handles loading and saving ktx files into streams. - /// The full spec can be found here: https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ - /// - public class KtxFile - { - - public KtxHeader header; - public List KeyValuePairs { get; } = new List(); - public List MipMaps { get; } = new List(); - - public KtxFile() { } - public KtxFile(KtxHeader header) - { - this.header = header; - } - - /// - /// Writes this ktx file into a stream. - /// - public void Write(Stream s) - { - if (MipMaps.Count < 1 || MipMaps[0].NumberOfFaces < 1) - { - throw new InvalidOperationException("The KTX structure should have at least 1 mipmap level and 1 Face before writing to file."); - } - - using (var bw = new BinaryWriter(s, UTF8, true)) - { - var bytesOfKeyValueData = (uint)KeyValuePairs.Sum(x => x.GetSizeWithPadding()); - - header.BytesOfKeyValueData = bytesOfKeyValueData; - header.NumberOfFaces = MipMaps[0].NumberOfFaces; - header.NumberOfMipmapLevels = (uint)MipMaps.Count; - header.NumberOfArrayElements = 0; - - if (!header.VerifyHeader()) - { - throw new InvalidOperationException("Please verify the header validity before writing to file."); - } - - bw.WriteStruct(header); - - foreach (var keyValuePair in KeyValuePairs) - { - KtxKeyValuePair.WriteKeyValuePair(bw, keyValuePair); - } - - for (var mip = 0; mip < header.NumberOfMipmapLevels; mip++) - { - var imageSize = MipMaps[mip].SizeInBytes; - bw.Write(imageSize); - var isCubemap = header.NumberOfFaces == 6 && header.NumberOfArrayElements == 0; - for (var f = 0; f < header.NumberOfFaces; f++) - { - bw.Write(MipMaps[mip].Faces[f].Data); - var cubePadding = 0u; - if (isCubemap) - { - cubePadding = 3 - (imageSize + 3) % 4; - } - bw.AddPadding(cubePadding); - } - - var mipPaddingBytes = 3 - (imageSize + 3) % 4; - bw.AddPadding(mipPaddingBytes); - } - - } - } - - /// - /// Loads a KTX file from a stream. - /// - public static KtxFile Load(Stream s) - { - - using (var br = new BinaryReader(s, UTF8, true)) - { - var header = br.ReadStruct(); - - if (header.NumberOfArrayElements > 0) - { - throw new NotSupportedException("KTX files with arrays are not supported."); - } - - var ktx = new KtxFile(header); - - var keyValuePairBytesRead = 0; - while (keyValuePairBytesRead < header.BytesOfKeyValueData) - { - var kvp = KtxKeyValuePair.ReadKeyValuePair(br, out var read); - keyValuePairBytesRead += read; - ktx.KeyValuePairs.Add(kvp); - } - - var numberOfFaces = Math.Max(1, header.NumberOfFaces); - ktx.MipMaps.Capacity = (int)header.NumberOfMipmapLevels; - for (uint mipLevel = 0; mipLevel < header.NumberOfMipmapLevels; mipLevel++) - { - var imageSize = br.ReadUInt32(); - var mipWidth = header.PixelWidth / (uint)Math.Pow(2, mipLevel); - var mipHeight = header.PixelHeight / (uint)Math.Pow(2, mipLevel); - - ktx.MipMaps.Add(new KtxMipmap(imageSize, mipWidth, mipHeight, numberOfFaces)); - - var cubemap = header.NumberOfFaces > 1 && header.NumberOfArrayElements == 0; - for (uint face = 0; face < numberOfFaces; face++) - { - var faceData = br.ReadBytes((int)imageSize); - ktx.MipMaps[(int)mipLevel].Faces[(int)face] = new KtxMipFace(faceData, mipWidth, mipHeight); - if (cubemap) - { - var cubePadding = 0u; - cubePadding = 3 - (imageSize + 3) % 4; - br.SkipPadding(cubePadding); - } - } - - var mipPaddingBytes = 3 - (imageSize + 3) % 4; - br.SkipPadding(mipPaddingBytes); - } - - return ktx; - } - } - - /// - /// Gets the total size of all mipmaps and faces. - /// - public ulong GetTotalSize() - { - ulong totalSize = 0; - - for (var mipLevel = 0; mipLevel < header.NumberOfMipmapLevels; mipLevel++) - { - for (var face = 0; face < header.NumberOfFaces; face++) - { - var ktxface = MipMaps[mipLevel].Faces[face]; - totalSize += ktxface.SizeInBytes; - } - } - - return totalSize; - } - - /// - /// Gets all texture data of the file in face-major order (face0_mip0 ... face0_mip1 ... face1_mip0 ...) - /// - public byte[] GetAllTextureDataFaceMajor() - { - var result = new byte[GetTotalSize()]; - uint start = 0; - for (var face = 0; face < header.NumberOfFaces; face++) - { - for (var mipLevel = 0; mipLevel < header.NumberOfMipmapLevels; mipLevel++) - { - var ktxMipFace = MipMaps[mipLevel].Faces[face]; - ktxMipFace.Data.CopyTo(result, (int)start); - start += ktxMipFace.SizeInBytes; - } - } - - return result; - } - - /// - /// Gets all texture data of the file in MipMap-major order (face0_mip0 ... face1_mip0 ... face0_mip1 ...) - /// - public byte[] GetAllTextureDataMipMajor() - { - var result = new byte[GetTotalSize()]; - uint start = 0; - for (var mipLevel = 0; mipLevel < header.NumberOfMipmapLevels; mipLevel++) - { - for (var face = 0; face < header.NumberOfFaces; face++) - { - var ktxMipFace = MipMaps[mipLevel].Faces[face]; - ktxMipFace.Data.CopyTo(result, (int)start); - start += ktxMipFace.SizeInBytes; - } - } - - return result; - } - } - - public class KtxKeyValuePair - { - public string Key { get; } - public byte[] Value { get; } - public KtxKeyValuePair(string key, byte[] value) - { - Key = key; - Value = value; - } - - public uint GetSizeWithPadding() - { - var keySpanLength = UTF8.GetByteCount(Key); - var totalSize = (uint)(keySpanLength + 1 + Value.Length); - var paddingBytes = (int)(3 - (totalSize + 3) % 4); - - return (uint)(totalSize + paddingBytes); - } - - public static KtxKeyValuePair ReadKeyValuePair(BinaryReader br, out int bytesRead) - { - var totalSize = br.ReadUInt32(); - Span keyValueBytes = stackalloc byte[(int)totalSize]; - br.Read(keyValueBytes); - - // Find the key's null terminator - int i; - for (i = 0; i < totalSize; i++) - { - if (keyValueBytes[i] == 0) - { - break; - } - - if (i >= totalSize) - { - throw new InvalidDataException(); - } - } - - - var keySize = i; - var key = UTF8.GetString(keyValueBytes.Slice(0, keySize)); - - var valueSize = (int)(totalSize - keySize - 1); - var valueBytes = keyValueBytes.Slice(i + 1, valueSize); - var value = new byte[valueSize]; - valueBytes.CopyTo(value); - - var paddingBytes = (int)(3 - (totalSize + 3) % 4); - br.SkipPadding(paddingBytes); - - bytesRead = (int)(totalSize + paddingBytes + sizeof(uint)); - return new KtxKeyValuePair(key, value); - } - - public static uint WriteKeyValuePair(BinaryWriter bw, KtxKeyValuePair pair) - { - var keySpanLength = UTF8.GetByteCount(pair.Key); - Span keySpan = stackalloc byte[keySpanLength]; - Span valueSpan = pair.Value; - - var totalSize = (uint)(keySpan.Length + 1 + valueSpan.Length); - var paddingBytes = (int)(3 - (totalSize + 3) % 4); - - bw.Write(totalSize); - bw.Write(keySpan); - bw.Write((byte)0); - bw.Write(valueSpan); - - return (uint)(totalSize + paddingBytes); - } - } - - [StructLayout(LayoutKind.Sequential)] - public unsafe struct KtxHeader - { - public fixed byte Identifier[12]; - public uint Endianness; - public GlType GlType; - public uint GlTypeSize; - public GlFormat GlFormat; - public GlInternalFormat GlInternalFormat; - public GlFormat GlBaseInternalFormat; - public uint PixelWidth; - public uint PixelHeight; - - public uint PixelDepth; - public uint NumberOfArrayElements; - public uint NumberOfFaces; - public uint NumberOfMipmapLevels; - public uint BytesOfKeyValueData; - - public bool VerifyHeader() - { - Span id = stackalloc byte[] { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; - for (var i = 0; i < id.Length; i++) - { - if (Identifier[i] != id[i]) return false; - } - return true; - } - - public static KtxHeader InitializeCompressed(int width, int height, GlInternalFormat internalFormat, GlFormat baseInternalFormat) - { - var header = new KtxHeader(); - Span id = stackalloc byte[] { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; - for (var i = 0; i < id.Length; i++) - { - header.Identifier[i] = id[i]; - } - header.Endianness = 0x04030201; - header.PixelWidth = (uint)width; - header.PixelHeight = (uint)height; - header.GlType = 0; - header.GlTypeSize = 1; - header.GlFormat = 0; - header.GlInternalFormat = internalFormat; - header.GlBaseInternalFormat = baseInternalFormat; - - return header; - } - - public static KtxHeader InitializeUncompressed(int width, int height, GlType type, GlFormat format, uint glTypeSize, GlInternalFormat internalFormat, GlFormat baseInternalFormat) - { - var header = new KtxHeader(); - Span id = stackalloc byte[] { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; - for (var i = 0; i < id.Length; i++) - { - header.Identifier[i] = id[i]; - } - header.Endianness = 0x04030201; - header.PixelWidth = (uint)width; - header.PixelHeight = (uint)height; - header.GlType = type; - header.GlTypeSize = glTypeSize; - header.GlFormat = format; - header.GlInternalFormat = internalFormat; - header.GlBaseInternalFormat = baseInternalFormat; - - return header; - } - } - - public class KtxMipFace - { - public uint Width { get; set; } - public uint Height { get; set; } - public uint SizeInBytes { get; } - public byte[] Data { get; } - - public KtxMipFace(byte[] data, uint width, uint height) - { - Width = width; - Height = height; - SizeInBytes = (uint)data.Length; - Data = data; - } - } - - public class KtxMipmap - { - public uint SizeInBytes { get; } - public uint Width { get; } - public uint Height { get; } - public uint NumberOfFaces { get; } - public KtxMipFace[] Faces { get; } - public KtxMipmap(uint sizeInBytes, uint width, uint height, uint numberOfFaces) - { - SizeInBytes = sizeInBytes; - Width = Math.Max(1, width); - Height = Math.Max(1, height); - NumberOfFaces = numberOfFaces; - Faces = new KtxMipFace[numberOfFaces]; - } - } -} diff --git a/BCnEnc.Net/Shared/ImageQuality.cs b/BCnEnc.Net/Shared/ImageQuality.cs deleted file mode 100644 index b97fb55..0000000 --- a/BCnEnc.Net/Shared/ImageQuality.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; - -namespace BCnEncoder.Shared -{ - public class ImageQuality - { - public static float PeakSignalToNoiseRatio(ReadOnlySpan original, ReadOnlySpan other, bool countAlpha = true) { - if (original.Length != other.Length) { - throw new ArgumentException("Both spans should be the same length"); - } - float error = 0; - for (var i = 0; i < original.Length; i++) { - var o = new ColorYCbCr(original[i]); - var c = new ColorYCbCr(other[i]); - error += (o.y - c.y) * (o.y - c.y); - error += (o.cb - c.cb) * (o.cb - c.cb); - error += (o.cr - c.cr) * (o.cr - c.cr); - if (countAlpha) { - error += (original[i].a - other[i].a) / 255.0f * ((original[i].a - other[i].a) / 255.0f); - } - - } - if (error < float.Epsilon) { - return 100; - } - if (countAlpha) { - error /= original.Length * 4; - } - else - { - error /= original.Length * 3; - } - - return 20 * MathF.Log10(1 / MathF.Sqrt(error)); - } - - public static float CalculateLogRMSE(ReadOnlySpan original, ReadOnlySpan other) - { - if (original.Length != other.Length) - { - throw new ArgumentException("Both spans should be the same length"); - } - float error = 0; - for (var i = 0; i < original.Length; i++) - { - //var dr = MathF.Log(other[i].r + 1.0f) - MathF.Log(original[i].r + 1.0f); - //var dg = MathF.Log(other[i].g + 1.0f) - MathF.Log(original[i].g + 1.0f); - //var db = MathF.Log(other[i].b + 1.0f) - MathF.Log(original[i].b + 1.0f); - var dr = Math.Sign(other[i].r) * MathF.Log(1 + MathF.Abs(other[i].r)) - Math.Sign(original[i].r) * MathF.Log(1 + MathF.Abs(original[i].r)); - var dg = Math.Sign(other[i].g) * MathF.Log(1 + MathF.Abs(other[i].g)) - Math.Sign(original[i].g) * MathF.Log(1 + MathF.Abs(original[i].g)); - var db = Math.Sign(other[i].b) * MathF.Log(1 + MathF.Abs(other[i].b)) - Math.Sign(original[i].b) * MathF.Log(1 + MathF.Abs(original[i].b)); - - error += dr * dr; - error += dg * dg; - error += db * db; - - } - return MathF.Sqrt(error / (3.0f * original.Length)); - } - - public static float PeakSignalToNoiseRatioLuminance(ReadOnlySpan original, ReadOnlySpan other, bool countAlpha = true) { - if (original.Length != other.Length) { - throw new ArgumentException("Both spans should be the same length"); - } - float error = 0; - for (var i = 0; i < original.Length; i++) { - var o = new ColorYCbCr(original[i]); - var c = new ColorYCbCr(other[i]); - error += (o.y - c.y) * (o.y - c.y); - if (countAlpha) { - error += (original[i].a - other[i].a) / 255.0f * ((original[i].a - other[i].a) / 255.0f); - } - - } - if (error < float.Epsilon) { - return 100; - } - if (countAlpha) { - error /= original.Length * 2; - } - else - { - error /= original.Length; - } - - return 20 * MathF.Log10(1 / MathF.Sqrt(error)); - } - } -} diff --git a/BCnEnc.Net/Shared/ImageToBlocks.cs b/BCnEnc.Net/Shared/ImageToBlocks.cs deleted file mode 100644 index f3ba021..0000000 --- a/BCnEnc.Net/Shared/ImageToBlocks.cs +++ /dev/null @@ -1,220 +0,0 @@ -using Microsoft.Toolkit.HighPerformance; - -namespace BCnEncoder.Shared -{ - internal static class ImageToBlocks - { - #region Blocks to colors - - internal static ColorRgba32[] ColorsFromRawBlocks(RawBlock4X4Rgba32[,] blocks, int pixelWidth, int pixelHeight) - { - var output = new ColorRgba32[pixelWidth * pixelHeight]; - - for (var y = 0; y < pixelHeight; y++) - { - for (var x = 0; x < pixelWidth; x++) - { - var blockIndexX = x >> 2; - var blockIndexY = y >> 2; - var blockInternalIndexX = x & 3; - var blockInternalIndexY = y & 3; - - output[x + y * pixelWidth] = blocks[blockIndexX, blockIndexY][blockInternalIndexX, blockInternalIndexY]; - } - } - - return output; - } - - internal static ColorRgba32[] ColorsFromRawBlocks(RawBlock4X4Rgba32[] blocks, int pixelWidth, int pixelHeight) - { - var blocksWidth = ((pixelWidth + 3) & ~3) >> 2; - var output = new ColorRgba32[pixelWidth * pixelHeight]; - - for (var y = 0; y < pixelHeight; y++) - { - for (var x = 0; x < pixelWidth; x++) - { - var blockIndexX = x >> 2; - var blockIndexY = y >> 2; - var blockInternalIndexX = x & 3; - var blockInternalIndexY = y & 3; - - var blockIndex = blockIndexX + blockIndexY * blocksWidth; - - output[x + y * pixelWidth] = blocks[blockIndex][blockInternalIndexX, blockInternalIndexY]; - } - } - - return output; - } - - internal static ColorRgbFloat[] ColorsFromRawBlocks(RawBlock4X4RgbFloat[] blocks, int pixelWidth, int pixelHeight) - { - var blocksWidth = ((pixelWidth + 3) & ~3) >> 2; - var output = new ColorRgbFloat[pixelWidth * pixelHeight]; - - for (var y = 0; y < pixelHeight; y++) - { - for (var x = 0; x < pixelWidth; x++) - { - var blockIndexX = x >> 2; - var blockIndexY = y >> 2; - var blockInternalIndexX = x & 3; - var blockInternalIndexY = y & 3; - - var blockIndex = blockIndexX + blockIndexY * blocksWidth; - - output[x + y * pixelWidth] = blocks[blockIndex][blockInternalIndexX, blockInternalIndexY]; - } - } - - return output; - } - - #endregion - - #region Image to blocks - - internal static RawBlock4X4Rgba32[] ImageTo4X4(ReadOnlyMemory2D image, out int blocksWidth, out int blocksHeight) - { - blocksWidth = ((image.Width + 3) & ~3) >> 2; - blocksHeight = ((image.Height + 3) & ~3) >> 2; - - var output = new RawBlock4X4Rgba32[blocksWidth * blocksHeight]; - - var span = image.Span; - - for (var y = 0; y < image.Height; y++) - { - for (var x = 0; x < image.Width; x++) - { - var color = span[y, x]; - - var blockIndexX = x >> 2; - var blockIndexY = y >> 2; - var blockInternalIndexX = x & 3; - var blockInternalIndexY = y & 3; - - output[blockIndexX + blockIndexY * blocksWidth][blockInternalIndexX, blockInternalIndexY] = color; - } - } - - // Fill in block y with edge color - if ((image.Height & 3) != 0) - { - var yPaddingStart = image.Height & 3; - for (var i = 0; i < blocksWidth; i++) - { - var lastBlock = output[i + blocksWidth * (blocksHeight - 1)]; - for (var y = yPaddingStart; y < 4; y++) - { - for (var x = 0; x < 4; x++) - { - lastBlock[x, y] = lastBlock[x, y - 1]; - } - } - output[i + blocksWidth * (blocksHeight - 1)] = lastBlock; - } - } - - // Fill in block x with edge color - if ((image.Width & 3) != 0) - { - var xPaddingStart = image.Width & 3; - for (var i = 0; i < blocksHeight; i++) - { - var lastBlock = output[blocksWidth - 1 + i * blocksWidth]; - for (var x = xPaddingStart; x < 4; x++) - { - for (var y = 0; y < 4; y++) - { - lastBlock[x, y] = lastBlock[x - 1, y]; - } - } - output[blocksWidth - 1 + i * blocksWidth] = lastBlock; - } - } - - return output; - } - - internal static RawBlock4X4RgbFloat[] ImageTo4X4(ReadOnlyMemory2D image, out int blocksWidth, out int blocksHeight) - { - blocksWidth = ((image.Width + 3) & ~3) >> 2; - blocksHeight = ((image.Height + 3) & ~3) >> 2; - - var output = new RawBlock4X4RgbFloat[blocksWidth * blocksHeight]; - - var span = image.Span; - - for (var y = 0; y < image.Height; y++) - { - for (var x = 0; x < image.Width; x++) - { - var color = span[y, x]; - - var blockIndexX = x >> 2; - var blockIndexY = y >> 2; - var blockInternalIndexX = x & 3; - var blockInternalIndexY = y & 3; - - output[blockIndexX + blockIndexY * blocksWidth][blockInternalIndexX, blockInternalIndexY] = color; - } - } - - // Fill in block y with edge color - if ((image.Height & 3) != 0) - { - var yPaddingStart = image.Height & 3; - for (var i = 0; i < blocksWidth; i++) - { - var lastBlock = output[i + blocksWidth * (blocksHeight - 1)]; - for (var y = yPaddingStart; y < 4; y++) - { - for (var x = 0; x < 4; x++) - { - lastBlock[x, y] = lastBlock[x, y - 1]; - } - } - output[i + blocksWidth * (blocksHeight - 1)] = lastBlock; - } - } - - // Fill in block x with edge color - if ((image.Width & 3) != 0) - { - var xPaddingStart = image.Width & 3; - for (var i = 0; i < blocksHeight; i++) - { - var lastBlock = output[blocksWidth - 1 + i * blocksWidth]; - for (var x = xPaddingStart; x < 4; x++) - { - for (var y = 0; y < 4; y++) - { - lastBlock[x, y] = lastBlock[x - 1, y]; - } - } - output[blocksWidth - 1 + i * blocksWidth] = lastBlock; - } - } - - return output; - } - - #endregion - - public static int CalculateNumOfBlocks(int pixelWidth, int pixelHeight) - { - var blocksWidth = ((pixelWidth + 3) & ~3) >> 2; - var blocksHeight = ((pixelHeight + 3) & ~3) >> 2; - - return blocksWidth * blocksHeight; - } - public static void CalculateNumOfBlocks(int pixelWidth, int pixelHeight, out int blocksWidth, out int blocksHeight) - { - blocksWidth = ((pixelWidth + 3) & ~3) >> 2; - blocksHeight = ((pixelHeight + 3) & ~3) >> 2; - } - } -} diff --git a/BCnEnc.Net/Shared/Interpolation.cs b/BCnEnc.Net/Shared/Interpolation.cs index 8f09723..6ee82fa 100644 --- a/BCnEnc.Net/Shared/Interpolation.cs +++ b/BCnEnc.Net/Shared/Interpolation.cs @@ -1,3 +1,6 @@ +using System.Numerics; +using BCnEncoder.Shared.Colors; + namespace BCnEncoder.Shared { internal static class Interpolation @@ -8,7 +11,30 @@ internal static class Interpolation /// The first color endpoint. /// The second color endpoint. /// The interpolated color. - public static ColorRgb24 InterpolateHalf(this ColorRgb24 c0, ColorRgb24 c1) => + public static Vector4 InterpolateHalf(this Vector4 c0, Vector4 c1) + { + return (c0 + c1) * 0.5f; + } + + /// + /// Interpolates two colors by third. + /// + /// The first color endpoint. + /// The second color endpoint. + /// The dividend in the third. + /// The interpolated color. + public static Vector4 InterpolateThird(this Vector4 c0, Vector4 c1, int num) + { + return ((3 - num) * c0 + num * c1) / 3; + } + + /// + /// Interpolates two colors by half. + /// + /// The first color endpoint. + /// The second color endpoint. + /// The interpolated color. + public static ColorRgbaFloat InterpolateHalf(this ColorRgbaFloat c0, ColorRgbaFloat c1) => InterpolateColor(c0, c1, 1, 2); /// @@ -18,7 +44,7 @@ public static ColorRgb24 InterpolateHalf(this ColorRgb24 c0, ColorRgb24 c1) => /// The second color endpoint. /// The dividend in the third. /// The interpolated color. - public static ColorRgb24 InterpolateThird(this ColorRgb24 c0, ColorRgb24 c1, int num) => + public static ColorRgbaFloat InterpolateThird(this ColorRgbaFloat c0, ColorRgbaFloat c1, int num) => InterpolateColor(c0, c1, num, 3); /// @@ -28,7 +54,7 @@ public static ColorRgb24 InterpolateThird(this ColorRgb24 c0, ColorRgb24 c1, int /// The second color endpoint. /// The dividend in the fourth. /// The interpolated color. - public static ColorRgb24 InterpolateFourthAtc(this ColorRgb24 c0, ColorRgb24 c1, int num) => + public static ColorRgbaFloat InterpolateFourthAtc(this ColorRgbaFloat c0, ColorRgbaFloat c1, int num) => InterpolateColorAtc(c0, c1, num, 4); /// @@ -39,7 +65,7 @@ public static ColorRgb24 InterpolateFourthAtc(this ColorRgb24 c0, ColorRgb24 c1, /// The dividend in the fifth. /// The interpolated component. public static byte InterpolateFifth(this byte a0, byte a1, int num) => - (byte)Interpolate(a0, a1, num, 5, 2); + (byte)InterpolateInt(a0, a1, num, 5, 2); /// /// Interpolates two colors by seventh. @@ -49,7 +75,126 @@ public static byte InterpolateFifth(this byte a0, byte a1, int num) => /// The dividend in the seventh. /// The interpolated component. public static byte InterpolateSeventh(this byte a0, byte a1, int num) => - (byte)Interpolate(a0, a1, num, 7, 3); + (byte)InterpolateInt(a0, a1, num, 7, 3); + + public static float InterpolateFifth(this float a0, float a1, int num) => + ((5 - num) * a0 + num * a1) / 5f; + + public static float InterpolateSeventh(this float a0, float a1, int num) => + ((7 - num) * a0 + num * a1) / 7f; + + /// + /// Interpolates two colors. + /// + /// The first color. + /// The second color. + /// The dividend on each color component. + /// The divisor on each color component. + /// The interpolated color. + private static ColorRgbaFloat InterpolateColor(ColorRgbaFloat c0, ColorRgbaFloat c1, int num, int den) => new ColorRgbaFloat( + Interpolate(c0.r, c1.r, num, den), + Interpolate(c0.g, c1.g, num, den), + Interpolate(c0.b, c1.b, num, den), + Interpolate(c0.a, c1.a, num, den) + ); + + public static ColorRgbaFloat InterpolateColor(ColorRgbaFloat c0, ColorRgbaFloat c1, float s) => new ColorRgbaFloat( + Interpolate(c0.r, c1.r, s), + Interpolate(c0.g, c1.g, s), + Interpolate(c0.b, c1.b, s), + Interpolate(c0.a, c1.a, s) + ); + + /// + /// Interpolates two colors with the ATC interpolation. + /// + /// The first color. + /// The second color. + /// The dividend on each color component. + /// The divisor on each color component. + /// The interpolated color. + private static ColorRgbaFloat InterpolateColorAtc(ColorRgbaFloat c0, ColorRgbaFloat c1, int num, int den) => new ColorRgbaFloat( + InterpolateAtc(c0.r, c1.r, num, den), + InterpolateAtc(c0.g, c1.g, num, den), + InterpolateAtc(c0.b, c1.b, num, den), + 1); + + /// + /// Interpolates two components. + /// + /// The first component. + /// The second component. + /// The dividend. + /// The divisor. + /// The interpolated component. + private static float Interpolate(float a, float b, int num, int den) => + ((den - num) * a + num * b) / den; + + private static float Interpolate(float a, float b, float s) => + a + s * (b - a); + + /// + /// Interpolates two components with the ATC interpolation. + /// + /// The first component. + /// The second component. + /// The dividend. + /// The divisor. + /// The interpolated component. + private static float InterpolateAtc(float a, float b, int num, int den) => + a - num / (float)den * b; + + /// + /// Interpolates two components. + /// + /// The first component. + /// The second component. + /// The dividend. + /// The divisor. + /// A correction value for increased accuracy when working with integer interpolated values. + /// The interpolated component. + private static int InterpolateInt(int a, int b, int num, int den, int correction) => + (int)(((den - num) * a + num * b + correction) / (float)den); + + /// + /// Interpolates two components with the ATC interpolation. + /// + /// The first component. + /// The second component. + /// The dividend. + /// The divisor. + /// The interpolated component. + private static int InterpolateIntAtc(int a, int b, int num, int den) => + (int)(a - num / (float)den * b); + + /// + /// Interpolates two colors by half. + /// + /// The first color endpoint. + /// The second color endpoint. + /// The interpolated color. + public static ColorRgb24 InterpolateHalf(this ColorRgb24 c0, ColorRgb24 c1) => + InterpolateColor(c0, c1, 1, 2, 1); + + /// + /// Interpolates two colors by third. + /// + /// The first color endpoint. + /// The second color endpoint. + /// The dividend in the third. + /// The interpolated color. + public static ColorRgb24 InterpolateThird(this ColorRgb24 c0, ColorRgb24 c1, int num) => + InterpolateColor(c0, c1, num, 3, 1); + + /// + /// Interpolates two colors by fourth with ATC interpolation. + /// + /// The first color endpoint. + /// The second color endpoint. + /// The dividend in the fourth. + /// The interpolated color. + public static ColorRgb24 InterpolateFourthAtc(this ColorRgb24 c0, ColorRgb24 c1, int num) => + InterpolateColorAtc(c0, c1, num, 4); /// /// Interpolates two colors. @@ -59,10 +204,10 @@ public static byte InterpolateSeventh(this byte a0, byte a1, int num) => /// The dividend on each color component. /// The divisor on each color component. /// The interpolated color. - private static ColorRgb24 InterpolateColor(ColorRgb24 c0, ColorRgb24 c1, int num, int den) => new ColorRgb24( - (byte)Interpolate(c0.r, c1.r, num, den), - (byte)Interpolate(c0.g, c1.g, num, den), - (byte)Interpolate(c0.b, c1.b, num, den)); + private static ColorRgb24 InterpolateColor(ColorRgb24 c0, ColorRgb24 c1, int num, int den, int correction) => new ColorRgb24( + (byte)Interpolate(c0.r, c1.r, num, den, correction), + (byte)Interpolate(c0.g, c1.g, num, den, correction), + (byte)Interpolate(c0.b, c1.b, num, den, correction)); /// /// Interpolates two colors with the ATC interpolation. diff --git a/BCnEnc.Net/Shared/LinearClustering.cs b/BCnEnc.Net/Shared/LinearClustering.cs index baf10aa..6737d9f 100644 --- a/BCnEnc.Net/Shared/LinearClustering.cs +++ b/BCnEnc.Net/Shared/LinearClustering.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Shared { @@ -120,90 +121,7 @@ public readonly float Distance(ClusterCenter other, float m, float s) /// the more spatial proximity is emphasized and the more compact the cluster, /// M should be in range of 1 to 20. /// - public static int[] ClusterPixels(ReadOnlySpan pixels, int width, int height, - int clusters, float m = 10, int maxIterations = 10, bool enforceConnectivity = true) - { - - var floats = new ColorRgbFloat[pixels.Length]; - for (var i = 0; i < pixels.Length; i++) - { - floats[i] = pixels[i].ToRgbFloat(); - } - - return ClusterPixels(floats, width, height, clusters, m, maxIterations, enforceConnectivity); - - //Grid interval S - var s = MathF.Sqrt(pixels.Length / (float)clusters); - var clusterIndices = new int[pixels.Length]; - - var labXys = ConvertToLabXy(pixels, width, height); - - - Span clusterCenters = InitialClusterCenters(width, height, clusters, s, labXys); - Span previousCenters = new ClusterCenter[clusters]; - - float error = 999; - const float threshold = 0.1f; - var iter = 0; - while (error > threshold) - { - if (maxIterations > 0 && iter >= maxIterations) - { - break; - } - iter++; - - clusterCenters.CopyTo(previousCenters); - - Array.Fill(clusterIndices, -1); - - // Find closest cluster for pixels - for (var j = 0; j < clusters; j++) - { - var xL = Math.Max(0, (int)(clusterCenters[j].x - s)); - var xH = Math.Min(width, (int)(clusterCenters[j].x + s)); - var yL = Math.Max(0, (int)(clusterCenters[j].y - s)); - var yH = Math.Min(height, (int)(clusterCenters[j].y + s)); - - for (var x = xL; x < xH; x++) - { - for (var y = yL; y < yH; y++) - { - var i = x + y * width; - - if (clusterIndices[i] == -1) - { - clusterIndices[i] = j; - } - else - { - var prevDistance = clusterCenters[clusterIndices[i]].Distance(labXys[i], m, s); - var distance = clusterCenters[j].Distance(labXys[i], m, s); - if (distance < prevDistance) - { - clusterIndices[i] = j; - } - } - } - } - } - - error = RecalculateCenters(clusters, m, labXys, clusterIndices, previousCenters, s, ref clusterCenters); - } - - if (enforceConnectivity) { - clusterIndices = EnforceConnectivity(clusterIndices, width, height, clusters); - } - - return clusterIndices; - } - - /// - /// The greater the value of M, - /// the more spatial proximity is emphasized and the more compact the cluster, - /// M should be in range of 1 to 20. - /// - public static int[] ClusterPixels(ReadOnlySpan pixels, int width, int height, + public static int[] ClusterPixels(ReadOnlySpan pixels, int width, int height, int clusters, float m = 10, int maxIterations = 10, bool enforceConnectivity = true) { @@ -384,7 +302,7 @@ private static LabXy[] ConvertToLabXy(ReadOnlySpan pixels, int widt for (var y = 0; y < height; y++) { var i = x + y * width; - var lab = new ColorLab(pixels[i]); + var lab = pixels[i].As(); labXys[i] = new LabXy { l = lab.l, @@ -399,7 +317,7 @@ private static LabXy[] ConvertToLabXy(ReadOnlySpan pixels, int widt return labXys; } - private static LabXy[] ConvertToLabXy(ReadOnlySpan pixels, int width, int height) + private static LabXy[] ConvertToLabXy(ReadOnlySpan pixels, int width, int height) { var labXys = new LabXy[pixels.Length]; //Convert pixels to LabXy @@ -408,7 +326,7 @@ private static LabXy[] ConvertToLabXy(ReadOnlySpan pixels, int wi for (var y = 0; y < height; y++) { var i = x + y * width; - var lab = new ColorLab(pixels[i]); + var lab = pixels[i].As(); labXys[i] = new LabXy { l = lab.l, @@ -427,7 +345,7 @@ private static int[] EnforceConnectivity(int[] oldLabels, int width, int height, { ReadOnlySpan neighborX = new[] { -1, 0, 1, 0 }; ReadOnlySpan neighborY = new[] { 0, -1, 0, 1 }; - + var sSquared = width * height / clusters; var clusterX = new List(sSquared); diff --git a/BCnEnc.Net/Shared/MathHelper.cs b/BCnEnc.Net/Shared/MathHelper.cs index d5a47d9..d09512d 100644 --- a/BCnEnc.Net/Shared/MathHelper.cs +++ b/BCnEnc.Net/Shared/MathHelper.cs @@ -23,9 +23,9 @@ public static unsafe class MathHelper /// Breaks down the floating-point value x into a component m for the normalized fraction component and another term n for the exponent, such that the absolute value of m is greater than or equal to 0.5 and less than 1.0 or equal to 0, and x = m * 2n. The function stores the integer exponent n at the location to which expptr points. /// /// - /// + /// /// Returns the normalized fraction m. If x is 0, the function returns 0 for both the fraction and exponent. The fraction has the same sign as the argument x. The result of the function cannot have a range error. - public static double FrExp(double x, out int eptr) + public static double FrExp(double x, out int exponent) { unchecked { @@ -33,7 +33,7 @@ public static double FrExp(double x, out int eptr) hx = *(1 + (int*) &x); ix = 0x7fffffff & hx; lx = *(int*) &x; - eptr = 0; + exponent = 0; if (ix >= 0x7ff00000 || ((ix | lx) == 0)) return x; /* 0,inf,nan */ if (ix < 0x00100000) { @@ -41,15 +41,36 @@ public static double FrExp(double x, out int eptr) x *= two54; hx = *(1 + (int*) &x); ix = hx & 0x7fffffff; - eptr = -54; + exponent = -54; } - eptr += (ix >> 20) - 1022; + exponent += (ix >> 20) - 1022; hx = (hx & (int) 0x800fffff) | (int) 0x3fe00000; *(1 + (int*) &x) = hx; return x; } } + + /// + /// Breaks down the floating-point value x into a component m for the normalized fraction component and another term n for the exponent, such that the absolute value of m is greater than or equal to 0.5 and less than 1.0 or equal to 0, and x = m * 2n. The function stores the integer exponent n at the location to which exponent points. + /// + /// + /// + /// Returns the normalized fraction m. If x is 0, the function returns 0 for both the fraction and exponent. The fraction has the same sign as the argument x. The result of the function cannot have a range error. + public static float FrExpF(float x, out int exponent) + { + uint fl; + + *((float*)&fl) = x; + /* Find the exponent (power of 2) */ + long exp = (fl >> 23) & 0x000000ff; + exp -= 0x7e; + exponent = (int)exp; + fl &= 0x807fffff; /* strip all exponent bits */ + fl |= 0x3f000000; /* mantissa between 0.5 and 1 */ + return *((float*)&fl); + } + /// /// Multiplies a floating point value arg by the number 2 raised to the exp power. @@ -57,9 +78,20 @@ public static double FrExp(double x, out int eptr) /// /// /// - public static float LdExp(float arg, int exp) + public static float LdExpF(float arg, int exp) { return arg * MathF.Pow(2, exp); } + + /// + /// Multiplies a floating point value arg by the number 2 raised to the exp power. + /// + /// + /// + /// + public static double LdExp(double arg, int exp) + { + return arg * Math.Pow(2, exp); + } } } diff --git a/BCnEnc.Net/Shared/MipMapper.cs b/BCnEnc.Net/Shared/MipMapper.cs index 765bc48..9a77ec3 100644 --- a/BCnEnc.Net/Shared/MipMapper.cs +++ b/BCnEnc.Net/Shared/MipMapper.cs @@ -1,10 +1,53 @@ using System; -using Microsoft.Toolkit.HighPerformance; +using BCnEncoder.Shared.Colors; +using CommunityToolkit.HighPerformance; namespace BCnEncoder.Shared { internal static class MipMapper { + /// + /// Generate a chain of elements. + /// Output texture format will be + /// + /// The original image to scale down. + /// The number of mipmaps to generate. + /// If is 0 or smaller, will generate as many mipmaps as possible until a mipmap of 1x1 is reached. + public static BCnTextureData GenerateMipChain(BCnTextureData input, ref int numMipMaps) + { + numMipMaps = CalculateMipChainLength(input.Width, input.Height, input.Depth, numMipMaps); + + input = input.ConvertTo(CompressionFormat.RgbaFloat); + + if (input.Mips.Length == numMipMaps) + { + return input; + } + + if (input.Depth != 1) + throw new ArgumentException("Only 2D textures are supported!"); + + var newData = new BCnTextureData(CompressionFormat.RgbaFloat, input.Width, input.Height, input.Depth, numMipMaps, + input.NumArrayElements, input.IsCubeMap, true, input.AlphaChannelHint); + + for (var f = 0; f < newData.NumFaces; f++) + { + for (var a = 0; a < newData.NumArrayElements; a++) + { + var face = (CubeMapFaceDirection)f; + var rgbaFloatMemory = input.Mips[0][face, a].AsMemory2D(); + var chain = GenerateMipChain(rgbaFloatMemory, ref numMipMaps); + + for (var i = 0; i < numMipMaps; i++) + { + chain[i].CopyTo(newData.Mips[i][face, a].AsMemory2D()); + } + } + } + + return newData; + } + /// /// Generate a chain of elements. /// @@ -12,8 +55,8 @@ internal static class MipMapper /// The original image width. /// The original image height. /// The number of mipmaps to generate. - /// Will generate as many mipmaps as possible until a mipmap of 1x1 is reached for 0 or smaller. - public static ReadOnlyMemory2D[] GenerateMipChain(ReadOnlyMemory input, int width, int height, ref int numMipMaps) + /// If is 0 or smaller, will generate as many mipmaps as possible until a mipmap of 1x1 is reached. + public static ReadOnlyMemory2D[] GenerateMipChain(ReadOnlyMemory input, int width, int height, ref int numMipMaps) { return GenerateMipChain(input.AsMemory2D(height, width), ref numMipMaps); } @@ -24,13 +67,13 @@ public static ReadOnlyMemory2D[] GenerateMipChain(ReadOnlyMemoryThe original image to scale down. /// The number of mipmaps to generate. /// Will generate as many mipmaps as possible until a mipmap of 1x1 is reached for 0 or smaller. - public static ReadOnlyMemory2D[] GenerateMipChain(ReadOnlyMemory2D pixels, ref int numMipMaps) + public static ReadOnlyMemory2D[] GenerateMipChain(ReadOnlyMemory2D pixels, ref int numMipMaps) { var width = pixels.Width; var height = pixels.Height; - var mipChainLength = CalculateMipChainLength(width, height, numMipMaps); + var mipChainLength = CalculateMipChainLength(width, height, 1, numMipMaps); - var result = new ReadOnlyMemory2D[mipChainLength]; + var result = new ReadOnlyMemory2D[mipChainLength]; result[0] = pixels; // If only one mipmap was requested, return original image only @@ -50,7 +93,7 @@ public static ReadOnlyMemory2D[] GenerateMipChain(ReadOnlyMemory2D< { var mipWidth = Math.Max(1, width >> mipLevel); var mipHeight = Math.Max(1, height >> mipLevel); - + var newMip = ResizeToHalf(result[mipLevel - 1].Span); result[mipLevel] = newMip; @@ -66,66 +109,46 @@ public static ReadOnlyMemory2D[] GenerateMipChain(ReadOnlyMemory2D< } /// - /// Generate a chain of elements. - /// - /// The original image to scale down. - /// The original image width. - /// The original image height. - /// The number of mipmaps to generate. - /// Will generate as many mipmaps as possible until a mipmap of 1x1 is reached for 0 or smaller. - public static ReadOnlyMemory2D[] GenerateMipChain(ReadOnlyMemory input, int width, int height, ref int numMipMaps) - { - return GenerateMipChain(input.AsMemory2D(height, width), ref numMipMaps); - } - - /// - /// Generate a chain of elements. + /// Generate a single mipmap of the specified mipLevel. /// /// The original image to scale down. - /// The number of mipmaps to generate. - /// Will generate as many mipmaps as possible until a mipmap of 1x1 is reached for 0 or smaller. - public static ReadOnlyMemory2D[] GenerateMipChain(ReadOnlyMemory2D pixels, ref int numMipMaps) + /// The mipLevel to generate. + public static Memory2D GenerateSingleMip(Memory2D pixels, int mipLevel) { var width = pixels.Width; var height = pixels.Height; - var mipChainLength = CalculateMipChainLength(width, height, numMipMaps); + var mipChainLength = CalculateMipChainLength(width, height, 1,-1); - var result = new ReadOnlyMemory2D[mipChainLength]; - result[0] = pixels; - - // If only one mipmap was requested, return original image only - if (numMipMaps == 1) + if (mipLevel >= mipChainLength) { - return result; + throw new InvalidOperationException("Requested mipLevel too high."); } - // If number of mipmaps is "marked as boundless", do as many mipmaps as it takes to reach a size of 1x1 - if (numMipMaps <= 0) + // If only one mipmap was requested, return original image only + if (mipLevel == 0) { - numMipMaps = int.MaxValue; + return pixels; } - // Generate mipmaps - for (var mipLevel = 1; mipLevel < numMipMaps; mipLevel++) + // Generate mipmap + for (var level = 1; level <= mipLevel; level++) { - var mipWidth = Math.Max(1, width >> mipLevel); - var mipHeight = Math.Max(1, height >> mipLevel); + var mipWidth = Math.Max(1, width >> level); + var mipHeight = Math.Max(1, height >> level); - var newMip = ResizeToHalf(result[mipLevel - 1].Span); - result[mipLevel] = newMip; + pixels = ResizeToHalf(pixels.Span); // Stop generating if last generated mipmap was of size 1x1 if (mipWidth == 1 && mipHeight == 1) { - numMipMaps = mipLevel + 1; break; } } - return result; + return pixels; } - public static int CalculateMipChainLength(int width, int height, int maxNumMipMaps) + public static int CalculateMipChainLength(int width, int height, int depth, int maxNumMipMaps) { if (maxNumMipMaps == 1) { @@ -142,13 +165,14 @@ public static int CalculateMipChainLength(int width, int height, int maxNumMipMa { var mipWidth = Math.Max(1, width >> mipLevel); var mipHeight = Math.Max(1, height >> mipLevel); + var mipDepth = Math.Max(1, depth >> mipLevel); if (mipLevel == maxNumMipMaps) { return maxNumMipMaps; } - if (mipWidth == 1 && mipHeight == 1) + if (mipWidth == 1 && mipHeight == 1 && mipDepth == 1) { output = mipLevel + 1; break; @@ -158,42 +182,14 @@ public static int CalculateMipChainLength(int width, int height, int maxNumMipMa return output; } - public static void CalculateMipLevelSize(int width, int height, int mipIdx, out int mipWidth, out int mipHeight) + public static void CalculateMipLevelSize(int width, int height, int depth, int mipIdx, out int mipWidth, out int mipHeight, out int mipDepth) { mipWidth = Math.Max(1, width >> mipIdx); mipHeight = Math.Max(1, height >> mipIdx); + mipDepth = Math.Max(1, depth >> mipIdx); } - private static ColorRgba32[,] ResizeToHalf(ReadOnlySpan2D pixelsRgba) - { - - var oldWidth = pixelsRgba.Width; - var oldHeight = pixelsRgba.Height; - var newWidth = Math.Max(1, oldWidth >> 1); - var newHeight = Math.Max(1, oldHeight >> 1); - - var result = new ColorRgba32[newHeight, newWidth]; - - int ClampW(int x) => Math.Max(0, Math.Min(oldWidth - 1, x)); - int ClampH(int y) => Math.Max(0, Math.Min(oldHeight - 1, y)); - - for (var y2 = 0; y2 < newHeight; y2++) - { - for (var x2 = 0; x2 < newWidth; x2++) - { - var ul = pixelsRgba[ClampH(y2 * 2), ClampW(x2 * 2)].ToFloat(); - var ur = pixelsRgba[ClampH(y2 * 2), ClampW(x2 * 2 + 1)].ToFloat(); - var ll = pixelsRgba[ClampH(y2 * 2 + 1), ClampW(x2 * 2)].ToFloat(); - var lr = pixelsRgba[ClampH(y2 * 2 + 1), ClampW(x2 * 2 + 1)].ToFloat(); - - result[y2, x2] = ((ul + ur + ll + lr) / 4).ToRgba32(); - } - } - - return result; - } - - private static ColorRgbFloat[,] ResizeToHalf(ReadOnlySpan2D pixelsRgba) + private static ColorRgbaFloat[,] ResizeToHalf(ReadOnlySpan2D pixelsRgba) { var oldWidth = pixelsRgba.Width; @@ -201,7 +197,7 @@ public static void CalculateMipLevelSize(int width, int height, int mipIdx, out var newWidth = Math.Max(1, oldWidth >> 1); var newHeight = Math.Max(1, oldHeight >> 1); - var result = new ColorRgbFloat[newHeight, newWidth]; + var result = new ColorRgbaFloat[newHeight, newWidth]; int ClampW(int x) => Math.Max(0, Math.Min(oldWidth - 1, x)); int ClampH(int y) => Math.Max(0, Math.Min(oldHeight - 1, y)); diff --git a/BCnEnc.Net/Shared/OperationContext.cs b/BCnEnc.Net/Shared/OperationContext.cs index bef999e..ed13842 100644 --- a/BCnEnc.Net/Shared/OperationContext.cs +++ b/BCnEnc.Net/Shared/OperationContext.cs @@ -1,12 +1,14 @@ using System; using System.Threading; +using BCnEncoder.Encoder; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Shared { /// /// The operation context. /// - public class OperationContext + internal class OperationContext { /// /// Whether the blocks should be decoded in parallel. @@ -27,5 +29,25 @@ public class OperationContext /// The progress context for the operation. /// public OperationProgress Progress { get; set; } + + /// + /// The compression quality to use for the operation. + /// + public CompressionQuality Quality { get; set; } + + /// + /// The color conversion mode for the operation. + /// + public ColorConversionMode ColorConversionMode { get; set; } + + /// + /// The Rgb weights to use for the operation, affects error calculation and PCA. + /// + public RgbWeights Weights { get; set; } + + /// + /// The alpha cutoff to use when converting to 1bit alpha. + /// + public float AlphaThreshold { get; set; } = .5f; } } diff --git a/BCnEnc.Net/Shared/OperationProgress.cs b/BCnEnc.Net/Shared/OperationProgress.cs index 7d9e87e..87391fd 100644 --- a/BCnEnc.Net/Shared/OperationProgress.cs +++ b/BCnEnc.Net/Shared/OperationProgress.cs @@ -2,26 +2,22 @@ namespace BCnEncoder.Shared { - public class OperationProgress + internal class OperationProgress { - private readonly IProgress progress; - private readonly int totalBlocks; - private int processedBlocks; + internal readonly IProgress progress; + internal readonly long totalBlocks; + internal long processedBlocks; - public OperationProgress(IProgress progress, int totalBlocks) + public OperationProgress(IProgress progress, long totalBlocks) { this.progress = progress; this.totalBlocks = totalBlocks; } - public void SetProcessedBlocks(int processedBlocks) + public void Report(long blocksSinceLastReport) { - this.processedBlocks = processedBlocks; - } - - public void Report(int currentBlock) - { - progress?.Report(new ProgressElement(processedBlocks + currentBlock, totalBlocks)); + processedBlocks += blocksSinceLastReport; + progress?.Report(new ProgressElement(processedBlocks, totalBlocks)); } } } diff --git a/BCnEnc.Net/Shared/OutputFileFormat.cs b/BCnEnc.Net/Shared/OutputFileFormat.cs deleted file mode 100644 index cc63b8d..0000000 --- a/BCnEnc.Net/Shared/OutputFileFormat.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace BCnEncoder.Shared -{ - public enum OutputFileFormat - { - /// - /// Khronos texture Format https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ - /// - Ktx, - /// - /// Direct draw surface Format https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds - /// - Dds - } -} diff --git a/BCnEnc.Net/Shared/PcaVectors.cs b/BCnEnc.Net/Shared/PcaVectors.cs index 63c2685..f2ed2d9 100644 --- a/BCnEnc.Net/Shared/PcaVectors.cs +++ b/BCnEnc.Net/Shared/PcaVectors.cs @@ -1,32 +1,24 @@ using System; using System.Numerics; +using BCnEncoder.Encoder; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Shared { internal static class PcaVectors { - private const int C565_5Mask = 0xF8; - private const int C565_6Mask = 0xFC; - - private static void ConvertToVector4(ReadOnlySpan colors, Span vectors) - { - for (var i = 0; i < colors.Length; i++) - { - vectors[i].X += colors[i].r / 255f; - vectors[i].Y += colors[i].g / 255f; - vectors[i].Z += colors[i].b / 255f; - vectors[i].W += colors[i].a / 255f; - } - } - - private static void ConvertToVector4(ReadOnlySpan colors, Span vectors) + /// + /// Converts colors to Vector4 format with perceptual weights applied. + /// Weights green more heavily than red and blue to match human perception of luma. + /// + /// Input colors + /// Output vectors + /// Weight for the rgb channels + private static void ConvertToVector4(ReadOnlySpan colors, Span vectors) { for (var i = 0; i < colors.Length; i++) { - vectors[i].X += colors[i].r; - vectors[i].Y += colors[i].g; - vectors[i].Z += colors[i].b; - vectors[i].W = 0; + vectors[i] = colors[i].ToVector4(); } } @@ -61,32 +53,35 @@ internal static Matrix4x4 CalculateCovariance(Span values, out Vector4 values[i] -= mean; } - //4x4 matrix var mat = new Matrix4x4(); + if (values.Length < 2) + return mat; + for (var i = 0; i < values.Length; i++) { + // Diagonal elements mat.M11 += values[i].X * values[i].X; + mat.M22 += values[i].Y * values[i].Y; + mat.M33 += values[i].Z * values[i].Z; + mat.M44 += values[i].W * values[i].W; + + // Upper triangle elements mat.M12 += values[i].X * values[i].Y; mat.M13 += values[i].X * values[i].Z; mat.M14 += values[i].X * values[i].W; - - mat.M22 += values[i].Y * values[i].Y; mat.M23 += values[i].Y * values[i].Z; mat.M24 += values[i].Y * values[i].W; - - mat.M33 += values[i].Z * values[i].Z; mat.M34 += values[i].Z * values[i].W; - - mat.M44 += values[i].W * values[i].W; } mat = Matrix4x4.Multiply(mat, 1f / (values.Length - 1)); - + + // Fill in the lower triangle from the upper triangle mat.M21 = mat.M12; mat.M31 = mat.M13; - mat.M32 = mat.M23; mat.M41 = mat.M14; + mat.M32 = mat.M23; mat.M42 = mat.M24; mat.M43 = mat.M34; @@ -97,83 +92,75 @@ internal static Matrix4x4 CalculateCovariance(Span values, out Vector4 /// Calculate principal axis with the power-method /// /// - /// + /// The normalized principal axis vector internal static Vector4 CalculatePrincipalAxis(Matrix4x4 covarianceMatrix) { - var lastDa = Vector4.UnitY; + // Start with a combination of basis vectors for better robustness + var lastDa = Vector4.Normalize(new Vector4(1, 1, 1, 1)); for (var i = 0; i < 30; i++) { var dA = Vector4.Transform(lastDa, covarianceMatrix); - if(dA.LengthSquared() == 0) { - break; + // Check if the vector has zero length + float lengthSquared = dA.LengthSquared(); + if (lengthSquared < 1e-10f) { + // If we get a zero vector, we've hit a null space + // Try a different direction + if (i == 0) { + lastDa = Vector4.UnitX; + continue; + } else { + break; + } } dA = Vector4.Normalize(dA); - if (Vector4.Dot(lastDa, dA) > 0.999999) { - lastDa = dA; - break; - } - else { - lastDa = dA; - } - } - return lastDa; - } - public static void Create(Span colors, out Vector3 mean, out Vector3 principalAxis) - { - Span vectors = stackalloc Vector4[colors.Length]; - ConvertToVector4(colors, vectors); - - - var cov = CalculateCovariance(vectors, out var v4Mean); - mean = new Vector3(v4Mean.X, v4Mean.Y, v4Mean.Z); + // Check for convergence - note that we also check for sign flips + float dotProduct = Vector4.Dot(lastDa, dA); + if (Math.Abs(dotProduct) > 0.9999f) { + // If dot product is negative, flip the direction + if (dotProduct < 0) { + dA = -dA; + } + return dA; + } - var pa = CalculatePrincipalAxis(cov); - principalAxis = new Vector3(pa.X, pa.Y, pa.Z); - if (principalAxis.LengthSquared() == 0) { - principalAxis = Vector3.UnitY; - } - else { - principalAxis = Vector3.Normalize(principalAxis); + lastDa = dA; } + // If we didn't converge within iterations, return the last approximation + return lastDa; } - public static void Create(Span colors, out Vector3 mean, out Vector3 principalAxis) + /// + /// Creates PCA vectors with color weighting + /// + public static void Create(Span colors, out Vector4 mean, out Vector4 principalAxis, out Vector4 min, out Vector4 max) { Span vectors = stackalloc Vector4[colors.Length]; ConvertToVector4(colors, vectors); - var cov = CalculateCovariance(vectors, out var v4Mean); - mean = new Vector3(v4Mean.X, v4Mean.Y, v4Mean.Z); + + mean = new Vector4(v4Mean.X, v4Mean.Y, v4Mean.Z, v4Mean.W); var pa = CalculatePrincipalAxis(cov); - principalAxis = new Vector3(pa.X, pa.Y, pa.Z); + + principalAxis = new Vector4(pa.X, pa.Y, pa.Z, pa.W); if (principalAxis.LengthSquared() == 0) { - principalAxis = Vector3.UnitY; + principalAxis = Vector4.UnitY; } else { - principalAxis = Vector3.Normalize(principalAxis); + principalAxis = Vector4.Normalize(principalAxis); } + GetExtremePoints(colors, mean, principalAxis, out min, out max); } - public static void CreateWithAlpha(Span colors, out Vector4 mean, out Vector4 principalAxis) - { - Span vectors = stackalloc Vector4[colors.Length]; - ConvertToVector4(colors, vectors); - - var cov = CalculateCovariance(vectors, out mean); - principalAxis = CalculatePrincipalAxis(cov); - } - - - public static void GetExtremePoints(Span colors, Vector3 mean, Vector3 principalAxis, out ColorRgb24 min, - out ColorRgb24 max) + public static void GetExtremePoints(Span colors, Vector4 mean, Vector4 principalAxis, out Vector4 min, + out Vector4 max) { float minD = 0; @@ -181,132 +168,45 @@ public static void GetExtremePoints(Span colors, Vector3 mean, Vect for (var i = 0; i < colors.Length; i++) { - var colorVec = new Vector3(colors[i].r / 255f, colors[i].g / 255f, colors[i].b / 255f); + var colorVec = new Vector4(colors[i].r, colors[i].g, colors[i].b, colors[i].a); var v = colorVec - mean; - var d = Vector3.Dot(v, principalAxis); + var d = Vector4.Dot(v, principalAxis); if (d < minD) minD = d; if (d > maxD) maxD = d; } - var minVec = mean + principalAxis * minD; - var maxVec = mean + principalAxis * maxD; - - var minR = (int) (minVec.X * 255); - var minG = (int) (minVec.Y * 255); - var minB = (int) (minVec.Z * 255); - - var maxR = (int) (maxVec.X * 255); - var maxG = (int) (maxVec.Y * 255); - var maxB = (int) (maxVec.Z * 255); - - minR = minR >= 0 ? minR : 0; - minG = minG >= 0 ? minG : 0; - minB = minB >= 0 ? minB : 0; + Vector4 minPoint = mean + principalAxis * minD; + Vector4 maxPoint = mean + principalAxis * maxD; - maxR = maxR <= 255 ? maxR : 255; - maxG = maxG <= 255 ? maxG : 255; - maxB = maxB <= 255 ? maxB : 255; - - min = new ColorRgb24((byte)minR, (byte)minG, (byte)minB); - max = new ColorRgb24((byte)maxR, (byte)maxG, (byte)maxB); - } - - public static void GetMinMaxColor565(Span colors, Vector3 mean, Vector3 principalAxis, - out ColorRgb565 min, out ColorRgb565 max) - { - - float minD = 0; - float maxD = 0; + int nearMinCount = 0; + int nearMaxCount = 0; + float thresholdDist = 0.05f; + float percentageSkipInset = 0.6f; for (var i = 0; i < colors.Length; i++) { - var colorVec = new Vector3(colors[i].r / 255f, colors[i].g / 255f, colors[i].b / 255f); - - var v = colorVec - mean; - var d = Vector3.Dot(v, principalAxis); - if (d < minD) minD = d; - if (d > maxD) maxD = d; - } - - //Inset - minD *= 15 / 16f; - maxD *= 15 / 16f; + var colorVec = colors[i].ToVector4(); - var minVec = mean + principalAxis * minD; - var maxVec = mean + principalAxis * maxD; - - var minR = (int) (minVec.X * 255); - var minG = (int) (minVec.Y * 255); - var minB = (int) (minVec.Z * 255); - - var maxR = (int) (maxVec.X * 255); - var maxG = (int) (maxVec.Y * 255); - var maxB = (int) (maxVec.Z * 255); - - minR = minR >= 0 ? minR : 0; - minG = minG >= 0 ? minG : 0; - minB = minB >= 0 ? minB : 0; - - maxR = maxR <= 255 ? maxR : 255; - maxG = maxG <= 255 ? maxG : 255; - maxB = maxB <= 255 ? maxB : 255; - - // Optimal round - minR = (minR & C565_5Mask) | (minR >> 5); - minG = (minG & C565_6Mask) | (minG >> 6); - minB = (minB & C565_5Mask) | (minB >> 5); - - maxR = (maxR & C565_5Mask) | (maxR >> 5); - maxG = (maxG & C565_6Mask) | (maxG >> 6); - maxB = (maxB & C565_5Mask) | (maxB >> 5); - - min = new ColorRgb565((byte)minR, (byte)minG, (byte)minB); - max = new ColorRgb565((byte)maxR, (byte)maxG, (byte)maxB); - - } - - public static void GetExtremePointsWithAlpha(Span colors, Vector4 mean, Vector4 principalAxis, out Vector4 min, - out Vector4 max) - { + float distToMin = Vector4.Distance(colorVec, minPoint); + float distToMax = Vector4.Distance(colorVec, maxPoint); - float minD = 0; - float maxD = 0; - - for (var i = 0; i < colors.Length; i++) - { - var colorVec = new Vector4(colors[i].r / 255f, colors[i].g / 255f, colors[i].b / 255f, colors[i].a / 255f); - - var v = colorVec - mean; - var d = Vector4.Dot(v, principalAxis); - if (d < minD) minD = d; - if (d > maxD) maxD = d; + if (distToMin < thresholdDist) + nearMinCount++; + else if (distToMax < thresholdDist) + nearMaxCount++; } - min = mean + principalAxis * minD; - max = mean + principalAxis * maxD; - } - - public static void GetExtremePoints(Span colors, Vector3 mean, Vector3 principalAxis, out Vector3 min, - out Vector3 max) - { - - float minD = 0; - float maxD = 0; + // Skip inset if a significant percentage of colors are near the endpoints + bool skipInset = (nearMinCount + nearMaxCount) > (colors.Length * percentageSkipInset); - for (var i = 0; i < colors.Length; i++) + // Inset + if (!skipInset) { - var colorVec = new Vector3(colors[i].r, colors[i].g, colors[i].b); - - var v = colorVec - mean; - var d = Vector3.Dot(v, principalAxis); - if (d < minD) minD = d; - if (d > maxD) maxD = d; + minD *= 15 / 16f; + maxD *= 15 / 16f; } - minD *= 15 / 16f; - maxD *= 15 / 16f; - min = mean + principalAxis * minD; max = mean + principalAxis * maxD; } diff --git a/BCnEnc.Net/Shared/PcaVectors2.cs b/BCnEnc.Net/Shared/PcaVectors2.cs new file mode 100644 index 0000000..36e5a2e --- /dev/null +++ b/BCnEnc.Net/Shared/PcaVectors2.cs @@ -0,0 +1,340 @@ +using System; +using System.Numerics; +using BCnEncoder.Encoder; +using BCnEncoder.Shared.Colors; + +namespace BCnEncoder.Shared; + +internal class PcaVectors2 +{ + /// + /// Converts colors to Vector4 format with perceptual weights applied. + /// + /// Input colors + /// Output vectors + /// Weight for the rgb channels + private static void ConvertToVector4Weighted(ReadOnlySpan colors, Span vectors, + RgbWeights weights) + { + for (var i = 0; i < colors.Length; i++) + { + vectors[i] = weights.TransformPca(colors[i]); + } + } + + private static Vector4 CalculateMean(Span colors) + { + float r = 0; + float g = 0; + float b = 0; + float a = 0; + + for (var i = 0; i < colors.Length; i++) + { + r += colors[i].X; + g += colors[i].Y; + b += colors[i].Z; + a += colors[i].W; + } + + return new Vector4( + r / colors.Length, + g / colors.Length, + b / colors.Length, + a / colors.Length + ); + } + + internal static Matrix4x4 CalculateCovariance(Span values, out Vector4 mean) + { + mean = CalculateMean(values); + for (var i = 0; i < values.Length; i++) + { + values[i] -= mean; + } + + var mat = new Matrix4x4(); + + if (values.Length < 2) + return mat; + + for (var i = 0; i < values.Length; i++) + { + // Diagonal elements + mat.M11 += values[i].X * values[i].X; + mat.M22 += values[i].Y * values[i].Y; + mat.M33 += values[i].Z * values[i].Z; + mat.M44 += values[i].W * values[i].W; + + // Upper triangle elements + mat.M12 += values[i].X * values[i].Y; + mat.M13 += values[i].X * values[i].Z; + mat.M14 += values[i].X * values[i].W; + mat.M23 += values[i].Y * values[i].Z; + mat.M24 += values[i].Y * values[i].W; + mat.M34 += values[i].Z * values[i].W; + } + + mat = Matrix4x4.Multiply(mat, 1f / (values.Length - 1)); + + // Fill in the lower triangle from the upper triangle + mat.M21 = mat.M12; + mat.M31 = mat.M13; + mat.M41 = mat.M14; + mat.M32 = mat.M23; + mat.M42 = mat.M24; + mat.M43 = mat.M34; + + return mat; + } + + /// + /// Calculate principal axis with the power-method + /// + /// + /// The normalized principal axis vector + internal static Vector4 CalculatePrincipalAxis(Matrix4x4 covarianceMatrix) + { + // Start with a combination of basis vectors for better robustness + var lastDa = Vector4.Normalize(new Vector4(1, 1, 1, 1)); + + for (var i = 0; i < 30; i++) + { + var dA = Vector4.Transform(lastDa, covarianceMatrix); + + // Check if the vector has zero length + float lengthSquared = dA.LengthSquared(); + if (lengthSquared < 1e-10f) + { + // If we get a zero vector, we've hit a null space + // Try a different direction + if (i == 0) + { + lastDa = Vector4.UnitX; + continue; + } + else + { + break; + } + } + + dA = Vector4.Normalize(dA); + + // Check for convergence - note that we also check for sign flips + float dotProduct = Vector4.Dot(lastDa, dA); + if (Math.Abs(dotProduct) > 0.9999f) + { + // If dot product is negative, flip the direction + if (dotProduct < 0) + { + dA = -dA; + } + + return dA; + } + + lastDa = dA; + } + + // If we didn't converge within iterations, return the last approximation + return lastDa; + } + + /// + /// Creates PCA vectors with color weighting + /// + public static void Create(ReadOnlySpan colorsPerceptual, RgbWeights weights, out Vector4 mean, out Vector4 principalAxis, + out Vector4 min, out Vector4 max) + { + Span vectors = stackalloc Vector4[colorsPerceptual.Length]; + Span colorsWeighted = stackalloc Vector4[colorsPerceptual.Length]; + + ConvertToVector4Weighted(colorsPerceptual, vectors, weights); + + // Make a copy, because we will modify the vectors + vectors.CopyTo(colorsWeighted); + + var cov = CalculateCovariance(vectors, out var v4Mean); + + mean = new Vector4(v4Mean.X, v4Mean.Y, v4Mean.Z, v4Mean.W); + + var pa = CalculatePrincipalAxis(cov); + + principalAxis = new Vector4(pa.X, pa.Y, pa.Z, pa.W); + if (principalAxis.LengthSquared() == 0) + { + principalAxis = Vector4.UnitY; + } + else + { + principalAxis = Vector4.Normalize(principalAxis); + } + // Get extreme points in weighted perceptual space + GetExtremePoints(colorsWeighted, mean, principalAxis, out var minV, out var maxV); + + // Convert min and max back to linear space + minV = weights.TransformFromPerceptual(weights.InverseTransformPca(minV)); + maxV = weights.TransformFromPerceptual(weights.InverseTransformPca(maxV)); + + // Recalculate principal axis in linear space + principalAxis = maxV - minV; + + // Mean is in the middle + mean = minV + principalAxis * 0.5f; + + if (principalAxis.LengthSquared() == 0) + principalAxis = Vector4.UnitY; + + principalAxis = Vector4.Normalize(principalAxis); + + min = minV; + max = maxV; + } + + /// + /// Creates PCA vectors with color weighting, ignoring black pixels + /// + private static int CreateIgnorePixels(Span colorsPerceptual, RgbWeights weights, Func ignorePixel, out Vector4 mean, out Vector4 principalAxis, + out Vector4 min, out Vector4 max) + { + int numIgnored = 0; + + for (int i = 0; i < colorsPerceptual.Length; i++) + { + if (ignorePixel(colorsPerceptual[i])) + { + numIgnored++; + } + } + + Span colorsWeighted = stackalloc Vector4[colorsPerceptual.Length - numIgnored]; + Span vectors = stackalloc Vector4[colorsPerceptual.Length - numIgnored]; + + int vecIdx = 0; + for (int i = 0; i < colorsPerceptual.Length; i++) + { + if (!ignorePixel(colorsPerceptual[i])) + { + var color = colorsPerceptual[i]; + vectors[vecIdx] = weights.TransformPca(color); + colorsWeighted[vecIdx] = vectors[vecIdx]; + vecIdx++; + } + } + + var cov = CalculateCovariance(vectors, out var v4Mean); + + mean = new Vector4(v4Mean.X, v4Mean.Y, v4Mean.Z, v4Mean.W); + + var pa = CalculatePrincipalAxis(cov); + + principalAxis = new Vector4(pa.X, pa.Y, pa.Z, pa.W); + + if (principalAxis.LengthSquared() == 0) + { + principalAxis = Vector4.UnitY; + } + else + { + principalAxis = Vector4.Normalize(principalAxis); + } + + // Get extreme points in weighted perceptual space + GetExtremePoints(colorsWeighted, mean, principalAxis, out var minV, out var maxV); + + // Convert min and max back to linear space + minV = weights.TransformFromPerceptual(weights.InverseTransformPca(minV)); + maxV = weights.TransformFromPerceptual(weights.InverseTransformPca(maxV)); + + // Recalculate principal axis in linear space + principalAxis = maxV - minV; + + // Mean is in the middle + mean = minV + principalAxis * 0.5f; + + if (principalAxis.LengthSquared() == 0) + principalAxis = Vector4.UnitY; + + principalAxis = Vector4.Normalize(principalAxis); + + min = minV; + max = maxV; + + return numIgnored; + } + + /// + /// Creates PCA vectors with color weighting, ignoring transaprent pixels + /// + public static int CreateIgnoreTransparent(Span colorsPerceptual, RgbWeights weights, float alphaCutoff, out Vector4 mean, out Vector4 principalAxis, + out Vector4 min, out Vector4 max) + { + bool IsTransparentPixel(Vector4 c) => c.W < alphaCutoff; + + return CreateIgnorePixels(colorsPerceptual, weights, IsTransparentPixel, out mean, out principalAxis, out min, out max); + } + + /// + /// Creates PCA vectors with color weighting, ignoring black pixels + /// + public static int CreateIgnoreBlack(Span colorsPerceptual, RgbWeights weights, out Vector4 mean, out Vector4 principalAxis, + out Vector4 min, out Vector4 max) + { + bool IsBlackPixel(Vector4 c) => (c.X < 0.01f && c.Y < 0.01f && c.Z < 0.01f); + + return CreateIgnorePixels(colorsPerceptual, weights, IsBlackPixel, out mean, out principalAxis, out min, out max); + } + + public static void GetExtremePoints(Span colors, Vector4 mean, Vector4 principalAxis, + out Vector4 min, + out Vector4 max) + { + float minD = 0; + float maxD = 0; + + for (var i = 0; i < colors.Length; i++) + { + var colorVec = colors[i]; + + var v = colorVec - mean; + var d = Vector4.Dot(v, principalAxis); + if (d < minD) minD = d; + if (d > maxD) maxD = d; + } + + Vector4 minPoint = mean + principalAxis * minD; + Vector4 maxPoint = mean + principalAxis * maxD; + + int nearMinCount = 0; + int nearMaxCount = 0; + float thresholdDist = 0.05f; + float percentageSkipInset = 0.6f; + + for (var i = 0; i < colors.Length; i++) + { + var colorVec = colors[i]; + + float distToMin = Vector4.Distance(colorVec, minPoint); + float distToMax = Vector4.Distance(colorVec, maxPoint); + + if (distToMin < thresholdDist) + nearMinCount++; + else if (distToMax < thresholdDist) + nearMaxCount++; + } + + // Skip inset if a significant percentage of colors are near the endpoints + bool skipInset = (nearMinCount + nearMaxCount) > (colors.Length * percentageSkipInset); + + // Inset + if (!skipInset) + { + minD *= 15 / 16f; + maxD *= 15 / 16f; + } + + min = mean + principalAxis * minD; + max = mean + principalAxis * maxD; + } +} diff --git a/BCnEnc.Net/Shared/ProgressElement.cs b/BCnEnc.Net/Shared/ProgressElement.cs index 77bf43e..169fde2 100644 --- a/BCnEnc.Net/Shared/ProgressElement.cs +++ b/BCnEnc.Net/Shared/ProgressElement.cs @@ -1,23 +1,23 @@ namespace BCnEncoder.Shared { - public struct ProgressElement + public readonly struct ProgressElement { /// /// Current block being processed /// - public int CurrentBlock { get; } + public long CurrentBlock { get; } /// /// The total amount of blocks to be processed /// - public int TotalBlocks { get; } + public long TotalBlocks { get; } /// /// Returns the progress percentage as a float from 0 to 1 /// public float Percentage => CurrentBlock / (float) TotalBlocks; - public ProgressElement(int currentBlock, int totalBlocks) + public ProgressElement(long currentBlock, long totalBlocks) { CurrentBlock = currentBlock; TotalBlocks = totalBlocks; diff --git a/BCnEnc.Net/Shared/RawBlocks.cs b/BCnEnc.Net/Shared/RawBlocks.cs deleted file mode 100644 index c78e5d8..0000000 --- a/BCnEnc.Net/Shared/RawBlocks.cs +++ /dev/null @@ -1,316 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using BCnEncoder.Encoder.Bptc; - -namespace BCnEncoder.Shared -{ - - public struct RawBlock4X4Rgba32 - { - public ColorRgba32 p00, p10, p20, p30; - public ColorRgba32 p01, p11, p21, p31; - public ColorRgba32 p02, p12, p22, p32; - public ColorRgba32 p03, p13, p23, p33; - - public RawBlock4X4Rgba32(ColorRgba32 fillColor) - { - p00 = p01 = p02 = p03 = - p10 = p11 = p12 = p13 = - p20 = p21 = p22 = p23 = - p30 = p31 = p32 = p33 = fillColor; - } - - public Span AsSpan => MemoryMarshal.CreateSpan(ref p00, 16); - - public ColorRgba32 this[int x, int y] - { - get => AsSpan[x + y * 4]; - set => AsSpan[x + y * 4] = value; - } - - public ColorRgba32 this[int index] - { - get => AsSpan[index]; - set => AsSpan[index] = value; - } - - internal int CalculateError(RawBlock4X4Rgba32 other, bool useAlpha = false) - { - float error = 0; - var pix1 = AsSpan; - var pix2 = other.AsSpan; - for (var i = 0; i < pix1.Length; i++) - { - var col1 = pix1[i]; - var col2 = pix2[i]; - - var re = col1.r - col2.r; - var ge = col1.g - col2.g; - var be = col1.b - col2.b; - - error += re * re; - error += ge * ge; - error += be * be; - - if (useAlpha) - { - var ae = col1.a - col2.a; - error += ae * ae * 4; - } - } - - error /= pix1.Length; - error = MathF.Sqrt(error); - - return (int)error; - } - - internal float CalculateYCbCrError(RawBlock4X4Rgba32 other) - { - float yError = 0; - float cbError = 0; - float crError = 0; - var pix1 = AsSpan; - var pix2 = other.AsSpan; - for (var i = 0; i < pix1.Length; i++) - { - var col1 = new ColorYCbCr(pix1[i]); - var col2 = new ColorYCbCr(pix2[i]); - - var ye = col1.y - col2.y; - var cbe = col1.cb - col2.cb; - var cre = col1.cr - col2.cr; - - yError += ye * ye; - cbError += cbe * cbe; - crError += cre * cre; - } - - var error = yError * 2 + cbError / 2 + crError / 2; - - return error; - } - - internal float CalculateYCbCrAlphaError(RawBlock4X4Rgba32 other, float yMultiplier = 2, float alphaMultiplier = 1) - { - float yError = 0; - float cbError = 0; - float crError = 0; - float alphaError = 0; - var pix1 = AsSpan; - var pix2 = other.AsSpan; - for (var i = 0; i < pix1.Length; i++) - { - var col1 = new ColorYCbCrAlpha(pix1[i]); - var col2 = new ColorYCbCrAlpha(pix2[i]); - - var ye = (col1.y - col2.y) * yMultiplier; - var cbe = col1.cb - col2.cb; - var cre = col1.cr - col2.cr; - var ae = (col1.alpha - col2.alpha) * alphaMultiplier; - - yError += ye * ye; - cbError += cbe * cbe; - crError += cre * cre; - alphaError += ae * ae; - } - - var error = yError + cbError + crError + alphaError; - return error; - } - - internal RawBlock4X4Ycbcr ToRawBlockYcbcr() - { - var rawYcbcr = new RawBlock4X4Ycbcr(); - var pixels = AsSpan; - var ycbcrPs = rawYcbcr.AsSpan; - for (var i = 0; i < pixels.Length; i++) - { - ycbcrPs[i] = new ColorYCbCr(pixels[i]); - } - return rawYcbcr; - } - - public bool HasTransparentPixels() - { - var pixels = AsSpan; - for (var i = 0; i < pixels.Length; i++) - { - if (pixels[i].a < 255) return true; - } - return false; - } - } - - public struct RawBlock4X4RgbFloat - { - public ColorRgbFloat p00, p10, p20, p30; - public ColorRgbFloat p01, p11, p21, p31; - public ColorRgbFloat p02, p12, p22, p32; - public ColorRgbFloat p03, p13, p23, p33; - - public RawBlock4X4RgbFloat(ColorRgbFloat fillColor) - { - p00 = p01 = p02 = p03 = - p10 = p11 = p12 = p13 = - p20 = p21 = p22 = p23 = - p30 = p31 = p32 = p33 = fillColor; - } - - public Span AsSpan => MemoryMarshal.CreateSpan(ref p00, 16); - - public ColorRgbFloat this[int x, int y] - { - get => AsSpan[x + y * 4]; - set => AsSpan[x + y * 4] = value; - } - - public ColorRgbFloat this[int index] - { - get => AsSpan[index]; - set => AsSpan[index] = value; - } - - internal float CalculateError(RawBlock4X4RgbFloat other) - { - float error = 0; - var pix1 = AsSpan; - var pix2 = other.AsSpan; - for (var i = 0; i < pix1.Length; i++) - { - var col1 = pix1[i]; - var col2 = pix2[i]; - - var re = Math.Sign(col1.r) * MathF.Log( 1 + MathF.Abs(col1.r)) - Math.Sign(col2.r) * MathF.Log( 1 + MathF.Abs(col2.r)); - var ge = Math.Sign(col1.g) * MathF.Log( 1 + MathF.Abs(col1.g)) - Math.Sign(col2.g) * MathF.Log( 1 + MathF.Abs(col2.g)); - var be = Math.Sign(col1.b) * MathF.Log( 1 + MathF.Abs(col1.b)) - Math.Sign(col2.b) * MathF.Log( 1 + MathF.Abs(col2.b)); - - error += re * re; - error += ge * ge; - error += be * be; - - } - - error /= pix1.Length * 3; - error = MathF.Sqrt(error); - - return error; - } - - internal float CalculateYCbCrError(RawBlock4X4RgbFloat other) - { - float yError = 0; - float cbError = 0; - float crError = 0; - var pix1 = AsSpan; - var pix2 = other.AsSpan; - for (var i = 0; i < pix1.Length; i++) - { - var col1 = new ColorYCbCr(pix1[i]); - var col2 = new ColorYCbCr(pix2[i]); - - var ye = col1.y - col2.y; - var cbe = col1.cb - col2.cb; - var cre = col1.cr - col2.cr; - - yError += ye * ye; - cbError += cbe * cbe; - crError += cre * cre; - } - - var error = yError * 2 + cbError / 2 + crError / 2; - - return error; - } - - - internal RawBlock4X4Ycbcr ToRawBlockYcbcr() - { - var rawYcbcr = new RawBlock4X4Ycbcr(); - var pixels = AsSpan; - var ycbcrPs = rawYcbcr.AsSpan; - for (var i = 0; i < pixels.Length; i++) - { - ycbcrPs[i] = new ColorYCbCr(pixels[i]); - } - return rawYcbcr; - } - } - - //Used for Bc6H - internal struct RawBlock4X4RgbHalfInt - { - public (int, int, int) p00, p10, p20, p30; - public (int, int, int) p01, p11, p21, p31; - public (int, int, int) p02, p12, p22, p32; - public (int, int, int) p03, p13, p23, p33; - public Span<(int, int, int)> AsSpan => MemoryMarshal.CreateSpan(ref p00, 16); - - public (int, int, int) this[int x, int y] - { - get => AsSpan[x + y * 4]; - set => AsSpan[x + y * 4] = value; - } - - public (int, int, int) this[int index] - { - get => AsSpan[index]; - set => AsSpan[index] = value; - } - - public static RawBlock4X4RgbHalfInt FromRawFloats(RawBlock4X4RgbFloat other, bool signed) - { - var output = new RawBlock4X4RgbHalfInt(); - var span = output.AsSpan; - var floats = other.AsSpan; - for (var i = 0; i < 16; i++) - { - span[i] = Bc6EncodingHelpers.PreQuantizeRawEndpoint(floats[i], signed); - } - return output; - } - - } - - - internal struct RawBlock4X4Ycbcr - { - public ColorYCbCr p00, p10, p20, p30; - public ColorYCbCr p01, p11, p21, p31; - public ColorYCbCr p02, p12, p22, p32; - public ColorYCbCr p03, p13, p23, p33; - public Span AsSpan => MemoryMarshal.CreateSpan(ref p00, 16); - - public ColorYCbCr this[int x, int y] - { - get => AsSpan[x + y * 4]; - set => AsSpan[x + y * 4] = value; - } - - public float CalculateError(RawBlock4X4Rgba32 other) - { - float yError = 0; - float cbError = 0; - float crError = 0; - var pix1 = AsSpan; - var pix2 = other.AsSpan; - for (var i = 0; i < pix1.Length; i++) - { - var col1 = pix1[i]; - var col2 = new ColorYCbCr(pix2[i]); - - var ye = col1.y - col2.y; - var cbe = col1.cb - col2.cb; - var cre = col1.cr - col2.cr; - - yError += ye * ye; - cbError += cbe * cbe; - crError += cre * cre; - } - - var error = yError * 2 + cbError / 2 + crError / 2; - - return error; - } - } -} diff --git a/BCnEnc.Net/Shared/RgbBoundingBox.cs b/BCnEnc.Net/Shared/RgbBoundingBox.cs index b6b2f44..20279d6 100644 --- a/BCnEnc.Net/Shared/RgbBoundingBox.cs +++ b/BCnEnc.Net/Shared/RgbBoundingBox.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using BCnEncoder.Shared.Colors; namespace BCnEncoder.Shared { @@ -11,7 +12,7 @@ namespace BCnEncoder.Shared internal static class RgbBoundingBox { - public static void Create565(ReadOnlySpan colors, out ColorRgb565 min, out ColorRgb565 max) + public static void Create565(ReadOnlySpan colors, out ColorB5G6R5Packed min, out ColorB5G6R5Packed max) { const int colorInsetShift = 4; const int c5655Mask = 0xF8; @@ -67,11 +68,11 @@ public static void Create565(ReadOnlySpan colors, out ColorRgb565 m maxG = (maxG & c5656Mask) | (maxG >> 6); maxB = (maxB & c5655Mask) | (maxB >> 5); - min = new ColorRgb565((byte)minR, (byte)minG, (byte)minB); - max = new ColorRgb565((byte)maxR, (byte)maxG, (byte)maxB); + min = new ColorB5G6R5Packed((byte)minR, (byte)minG, (byte)minB); + max = new ColorB5G6R5Packed((byte)maxR, (byte)maxG, (byte)maxB); } - public static void Create565AlphaCutoff(ReadOnlySpan colors, out ColorRgb565 min, out ColorRgb565 max, int alphaCutoff = 128) + public static void Create565AlphaCutoff(ReadOnlySpan colors, out ColorB5G6R5Packed min, out ColorB5G6R5Packed max, int alphaCutoff = 128) { const int colorInsetShift = 4; const int c5655Mask = 0xF8; @@ -125,11 +126,11 @@ public static void Create565AlphaCutoff(ReadOnlySpan colors, out Co maxG = (maxG & c5656Mask) | (maxG >> 6); maxB = (maxB & c5655Mask) | (maxB >> 5); - min = new ColorRgb565((byte)minR, (byte)minG, (byte)minB); - max = new ColorRgb565((byte)maxR, (byte)maxG, (byte)maxB); + min = new ColorB5G6R5Packed((byte)minR, (byte)minG, (byte)minB); + max = new ColorB5G6R5Packed((byte)maxR, (byte)maxG, (byte)maxB); } - public static void Create565A(ReadOnlySpan colors, out ColorRgb565 min, out ColorRgb565 max, out byte minAlpha, out byte maxAlpha) + public static void Create565A(ReadOnlySpan colors, out ColorB5G6R5Packed min, out ColorB5G6R5Packed max, out byte minAlpha, out byte maxAlpha) { const int colorInsetShift = 4; const int alphaInsetShift = 5; @@ -193,8 +194,8 @@ public static void Create565A(ReadOnlySpan colors, out ColorRgb565 maxG = (maxG & c5656Mask) | (maxG >> 6); maxB = (maxB & c5655Mask) | (maxB >> 5); - min = new ColorRgb565((byte)minR, (byte)minG, (byte)minB); - max = new ColorRgb565((byte)maxR, (byte)maxG, (byte)maxB); + min = new ColorB5G6R5Packed((byte)minR, (byte)minG, (byte)minB); + max = new ColorB5G6R5Packed((byte)maxR, (byte)maxG, (byte)maxB); minAlpha = (byte)minA; maxAlpha = (byte)maxA; } @@ -203,7 +204,7 @@ public static void Create565A(ReadOnlySpan colors, out ColorRgb565 /// Hdr rgb bounding box inset by Krzysztof Narkowicz. https://github.com/knarkowicz/GPURealTimeBC6H /// Code is public domain. /// - private static void InsetHdrChannel(ReadOnlySpan colors, int channel, ref float blockMax, ref float blockMin) + private static void InsetHdrChannel(ReadOnlySpan colors, int channel, ref float blockMax, ref float blockMin) { var offset = 0f; if (blockMin < 0) @@ -212,14 +213,14 @@ private static void InsetHdrChannel(ReadOnlySpan colors, int chan blockMin += offset; blockMax += offset; } - + float Select(ReadOnlySpan span, int i) { - return span[i * 3 + channel] + offset; + return span[i * 4 + channel] + offset; } - - var floats = MemoryMarshal.Cast(colors); - + + var floats = MemoryMarshal.Cast(colors); + var refinedBlockMin = blockMax; var refinedBlockMax = blockMin; @@ -243,15 +244,17 @@ float Select(ReadOnlySpan span, int i) blockMax = MathF.Pow(2, logBlockMax) - 1.0f - offset; } - public static void CreateFloat(ReadOnlySpan colors, out ColorRgbFloat min, out ColorRgbFloat max) + public static void CreateFloat(ReadOnlySpan colors, out ColorRgbaFloat min, out ColorRgbaFloat max) { float minR = float.MaxValue, minG = float.MaxValue, - minB = float.MaxValue; + minB = float.MaxValue, + minA = float.MaxValue; float maxR = float.MinValue, maxG = float.MinValue, - maxB = float.MinValue; + maxB = float.MinValue, + maxA = float.MinValue; for (var i = 0; i < colors.Length; i++) { @@ -260,18 +263,21 @@ public static void CreateFloat(ReadOnlySpan colors, out ColorRgbF if (c.r < minR) minR = c.r; if (c.g < minG) minG = c.g; if (c.b < minB) minB = c.b; + if (c.a < minA) minA = c.a; if (c.r > maxR) maxR = c.r; if (c.g > maxG) maxG = c.g; if (c.b > maxB) maxB = c.b; + if (c.b > maxA) maxA = c.a; } InsetHdrChannel(colors, 0, ref maxR, ref minR); InsetHdrChannel(colors, 1, ref maxG, ref minG); InsetHdrChannel(colors, 2, ref maxB, ref minB); + InsetHdrChannel(colors, 3, ref maxA, ref minA); - min = new ColorRgbFloat(minR, minG, minB); - max = new ColorRgbFloat(maxR, maxG, maxB); + min = new ColorRgbaFloat(minR, minG, minB, minA); + max = new ColorRgbaFloat(maxR, maxG, maxB, maxA); } } } diff --git a/BCnEnc.Net/Shared/TextureData.cs b/BCnEnc.Net/Shared/TextureData.cs new file mode 100644 index 0000000..67132a3 --- /dev/null +++ b/BCnEnc.Net/Shared/TextureData.cs @@ -0,0 +1,669 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using BCnEncoder.Decoder; +using BCnEncoder.Decoder.Options; +using BCnEncoder.Encoder; +using BCnEncoder.Shared.Colors; +using BCnEncoder.TextureFormats; +using CommunityToolkit.HighPerformance; + +namespace BCnEncoder.Shared +{ + public enum CubeMapFaceDirection + { + /// + /// Right face + /// + XPositive = 0, + /// + /// Left face + /// + XNegative = 1, + /// + /// Top face + /// + YPositive = 2, + /// + /// Bottom face + /// + YNegative = 3, + /// + /// Back face + /// + ZPositive = 4, + /// + /// Front face + /// + ZNegative = 5 + } + + /// + /// Indicates how the alpha channel in texture data is encoded in relation to the color channels. + /// + public enum AlphaChannelHint + { + /// + /// The alpha encoding type is not known or not specified. + /// + Unknown, + + /// + /// Alpha is straight/non-premultiplied (color values are independent of alpha). + /// This is the typical format for standard image formats like PNG. + /// + Straight, + + /// + /// Alpha is premultiplied (color channels are already multiplied by alpha). + /// This is commonly used in GPU texture formats and during rendering. + /// + Premultiplied + } + + public class BCnTextureData + { + /// + /// Creates a new BCnTexture object + /// + /// The compression format of the texture + /// Width of the texture in pixels + /// Height of the texture in pixels + /// Depth of the texture for volume textures, default is 1 + /// Number of mip map levels, default is 0 (no mipmaps) + /// Number of array elements for texture arrays, default is 0 (not an array) + /// Whether this texture is a cubemap (6 faces), default is false + /// Whether to allocate memory buffers for the texture data, default is false + /// Hint about how the alpha channel is encoded (straight, premultiplied, or unknown), default is Unknown + public BCnTextureData(CompressionFormat inputFormat, int width, int height, int depth = 1, int numMips = 0, int numArrayElements = 0, bool isCubeMap = false, bool allocateBuffers = false, AlphaChannelHint alphaChannelHint = AlphaChannelHint.Unknown) + { + if (width <= 0) + throw new ArgumentException($"Invalid texture size width: {width}", nameof(width)); + if (height <= 0) + throw new ArgumentException($"Invalid texture size height: {height}", nameof(height)); + + Format = inputFormat; + Width = width; + Height = height; + Depth = Math.Max(depth, 1); + + NumFaces = isCubeMap ? 6 : 1; + NumMips = Math.Max(numMips, 1); + NumArrayElements = Math.Max(numArrayElements, 1); + AlphaChannelHint = alphaChannelHint; + + Mips = new MipMapLevel[NumMips]; + for (int i = 0; i < NumMips; i++) + { + Mips[i] = new MipMapLevel(this, i, NumFaces, NumArrayElements, allocateBuffers); + } + } + + public class MipMapLevel + { + private readonly BCnTextureData texture; + public int Width { get; set; } + public int Height { get; set; } + public int Depth { get; set; } + public long SizeInBytes { get; set; } + + private TexData[] textureData; + + public TexData this[CubeMapFaceDirection face, int arrayIndex = 0] + { + get => textureData[(int)face * texture.NumArrayElements + arrayIndex]; + set => textureData[(int)face * texture.NumArrayElements + arrayIndex] = value; + } + + public TexData First => textureData[0]; + + internal MipMapLevel(BCnTextureData texture, int level, int numFaces, int numArrayElements, bool allocateBuffers) + { + if (level >= texture.NumMips) + throw new ArgumentException("Invalid mip level"); + + this.texture = texture; + MipMapper.CalculateMipLevelSize(texture.Width, texture.Height, texture.Depth, level, out var mipWidth, out var mipHeight, out var mipDepth); + + if (mipWidth == 0 || mipHeight == 0 || mipDepth == 0) + throw new ArgumentException("Invalid mip level size"); + if (numFaces != 1 && numFaces != 6) + throw new ArgumentException("Invalid number of faces"); + if (numArrayElements < 1) + throw new ArgumentException("Invalid number of array items"); + + this.Width = mipWidth; + this.Height = mipHeight; + this.Depth = mipDepth; + this.SizeInBytes = texture.Format.CalculateMipByteSize(mipWidth, mipHeight, mipDepth); + this.textureData = new TexData[numFaces * numArrayElements]; + + for (var f = 0; f < numFaces; f++) + { + for (var a = 0; a < numArrayElements; a++) + { + byte[] data = null; + if (allocateBuffers) + { + data = new byte[SizeInBytes]; + } + textureData[f * numArrayElements + a] = new TexData((CubeMapFaceDirection)f, a, this, data); + } + } + } + } + + public class TexData + { + public CubeMapFaceDirection Direction { get; private set; } + public int ArrayIndex { get; private set; } + private MipMapLevel Mip { get; } + + private byte[] data; + + public byte[] Data + { + get => data; + set + { + if (value != null && value.LongLength != Mip.SizeInBytes) + throw new ArgumentException("Data size does not match expected mip size! Please provide a buffer of the correct size."); + + data = value; + } + } + + public int Width => Mip.Width; + public int Height => Mip.Height; + public int Depth => Mip.Depth; + public long SizeInBytes => Mip.SizeInBytes; + + public TexData(CubeMapFaceDirection direction, int arrayIndex, MipMapLevel mip, byte[] data) + { + Direction = direction; + ArrayIndex = arrayIndex; + Mip = mip; + Data = data; + } + + + /// + /// Potentially dangerous! + /// Please use only when you know for sure the underlying data is of the provided type. + /// I.e. Check that CompressionFormat == + /// before trying to get a memory. + /// + public Memory2D AsMemory2D() where T : unmanaged + { + if (Unsafe.SizeOf() * Width * Height != Data.Length) + { + throw new InvalidCastException($"The size of {typeof(T).Name} does not match data format!"); + } + return Data.AsMemory().Cast() + .AsMemory2D(Height, Width); + } + + /// + /// Potentially dangerous! + /// Please use only when you know for sure the underlying data is of the provided type. + /// I.e. Check that CompressionFormat == + /// before trying to get a memory. + /// + public Memory AsMemory() where T : unmanaged + { + if (Unsafe.SizeOf() * Width * Height != Data.Length) + { + throw new InvalidCastException($"The size of {typeof(T).Name} does not match data format!"); + } + + return Data.AsMemory().Cast(); + } + } + + /// + /// Width of mip0 + /// + public int Width { get; init; } + + /// + /// Height of mip0 + /// + public int Height { get; init; } + + /// + /// Depth of mip0 (For volume textures only. Otherwise, 1) + /// + public int Depth { get; init; } + + /// + /// Number of mip maps + /// + public int NumMips { get; set; } + + /// + /// Number of faces. 1 if normal texture, 6 if cube map + /// + public int NumFaces { get; set; } + + /// + /// Number of array elements. For texture arrays. + /// + public int NumArrayElements { get; set; } + + /// + /// The CompressionFormat of this texture data. + /// + public CompressionFormat Format { get; set; } + + /// + /// Access mip levels. Only mip level 0 is present if the texture has no mip levels. + /// + public MipMapLevel[] Mips { get; set; } + + /// + /// Access the first texture of the first mip level. + /// This is the default texture for the texture data if there are no mips, no cubemap faces, and no array elements. + /// + public TexData First => Mips[0].First; + + /// + /// True if texture is cube map + /// + public bool IsCubeMap => NumFaces > 1; + + /// + /// True if texture has mip levels + /// + public bool HasMipLevels => NumMips > 1; + + /// + /// True if the texture format is one of the block-compressed formats + /// + public bool IsBlockCompressed => Format.IsBlockCompressedFormat(); + + /// + /// True if the texture is a volume texture. (Z > 1) + /// + public bool IsVolumeTexture => Depth > 1; + + /// + /// True if the texture is a texture array. (NumArrayElements > 1) + /// + public bool IsArrayTexture => NumArrayElements > 1; + + /// + /// Hint about how the alpha channel is encoded in relation to color channels. + /// This helps the encoder/decoder make appropriate decisions when processing the texture data. + /// + public AlphaChannelHint AlphaChannelHint { get; set; } = AlphaChannelHint.Unknown; + + public long TotalByteSize => Mips.Sum(m => m.SizeInBytes * NumFaces * NumArrayElements); + + + + public TTexture AsTexture() + where TTexture : class, ITextureFileFormat, new() + { + var tex = new TTexture(); + tex.FromTextureData(this); + return tex; + } + + public static BCnTextureData CombineCubeMap( + BCnTextureData right, + BCnTextureData left, + BCnTextureData top, + BCnTextureData down, + BCnTextureData back, + BCnTextureData front + ) + { + if ( + right.Width != left.Width || right.Height != left.Height || + right.Width != top.Width || right.Height != top.Height || + right.Width != down.Width || right.Height != down.Height || + right.Width != back.Width || right.Height != back.Height || + right.Width != front.Width || right.Height != front.Height + ) + { + throw new ArgumentException("All faces of a cubeMap must be of equal width and height!"); + } + if ( + right.Depth != 1 || + left.Depth != 1 || + top.Depth != 1 || + down.Depth != 1 || + back.Depth != 1 || + front.Depth != 1) + { + throw new ArgumentException("All faces of a cubeMap must have a depth of 1!"); + } + if ( + right.Format != left.Format || + left.Format != top.Format || + top.Format != down.Format || + down.Format != back.Format || + back.Format != front.Format) + { + throw new ArgumentException("All faces of a cubeMap must have the same format!"); + } + if ( + right.NumMips != left.NumMips || + left.NumMips != top.NumMips || + top.NumMips != down.NumMips || + down.NumMips != back.NumMips || + back.NumMips != front.NumMips) + { + throw new ArgumentException("All faces of a cubeMap must have the same amount of mipmaps!"); + } + if ( + right.NumArrayElements != left.NumArrayElements || + left.NumArrayElements != top.NumArrayElements || + top.NumArrayElements != down.NumArrayElements || + down.NumArrayElements != back.NumArrayElements || + back.NumArrayElements != front.NumArrayElements) + { + throw new ArgumentException("All faces of a cubeMap must have the same amount of array elements!"); + } + if ( + right.AlphaChannelHint != left.AlphaChannelHint || + left.AlphaChannelHint != top.AlphaChannelHint || + top.AlphaChannelHint != down.AlphaChannelHint || + down.AlphaChannelHint != back.AlphaChannelHint || + back.AlphaChannelHint != front.AlphaChannelHint) + { + throw new ArgumentException("All faces of a cubeMap must have the same amount of array elements!"); + } + + var outData = new BCnTextureData(right.Format, right.Width, right.Height, right.Depth, + right.NumMips, + right.NumArrayElements, + isCubeMap: true, + allocateBuffers: false, + alphaChannelHint: right.AlphaChannelHint); + + for (var m = 0; m < right.NumMips; m++) + { + for (var a = 0; a < right.NumArrayElements; a++) + { + outData.Mips[m][CubeMapFaceDirection.XPositive, a].Data = right.Mips[m][CubeMapFaceDirection.XPositive, a].Data; + outData.Mips[m][CubeMapFaceDirection.XNegative, a].Data = left.Mips[m][CubeMapFaceDirection.XNegative, a].Data; + outData.Mips[m][CubeMapFaceDirection.YPositive, a].Data = top.Mips[m][CubeMapFaceDirection.YPositive, a].Data; + outData.Mips[m][CubeMapFaceDirection.YNegative, a].Data = down.Mips[m][CubeMapFaceDirection.YNegative, a].Data; + outData.Mips[m][CubeMapFaceDirection.ZPositive, a].Data = back.Mips[m][CubeMapFaceDirection.ZPositive, a].Data; + outData.Mips[m][CubeMapFaceDirection.ZNegative, a].Data = front.Mips[m][CubeMapFaceDirection.ZNegative, a].Data; + } + } + return outData; + } + + /// + /// Checks if the data is valid. + /// + /// True if the data is valid, false otherwise. + public bool IsValid() + { + if (Width <= 0 || Height <= 0) + return false; + if (NumMips <= 0) + return false; + if (NumFaces != 1 && NumFaces != 6) + return false; + if (NumArrayElements < 1) + return false; + if (Mips.Length != NumMips) + return false; + + foreach (var t in Mips) + { + if (t.Width <= 0 || t.Height <= 0) + return false; + if (t.SizeInBytes <= 0) + return false; + + for (var f = 0; f < NumFaces; f++) + { + for (var a = 0; a < NumArrayElements; a++) + { + if (t[(CubeMapFaceDirection)f, a].Data == null) + return false; + if (t[(CubeMapFaceDirection)f, a].Data.Length != t.SizeInBytes) + return false; + } + } + } + + return true; + } + + /// + /// Validates the texture data. Throws if the data is invalid. + /// + public void Validate() + { + + if (Width <= 0 || Height <= 0) + { + throw new InvalidTextureException($"Texture has invalid dimensions: Width={Width}, Height={Height}"); + } + + if (NumMips <= 0) + { + throw new InvalidTextureException("Texture has no mip levels"); + } + + if (NumFaces != 1 && NumFaces != 6) + { + throw new InvalidTextureException("Texture has invalid number of faces"); + } + + if (NumArrayElements < 1) + { + throw new InvalidTextureException("Texture has invalid number of array elements"); + } + + if (Mips.Length != NumMips) + { + throw new InvalidTextureException("Texture has incorrect number of mip levels"); + } + + for (var i = 0; i < Mips.Length; i++) + { + var mip = Mips[i]; + if (mip.Width <= 0 || mip.Height <= 0) + { + throw new InvalidTextureException( + $"Mip level {i} has invalid dimensions: Width={mip.Width}, Height={mip.Height}"); + } + if (mip.SizeInBytes <= 0) + throw new InvalidTextureException($"Mip level {i} has invalid size: {mip.SizeInBytes}"); + + for (var f = 0; f < NumFaces; f++) + { + for (var a = 0; a < NumArrayElements; a++) + { + var texData = mip[(CubeMapFaceDirection)f, a]; + if (texData.Data == null) + { + throw new InvalidTextureException( + $"Mip level {i}, face {(CubeMapFaceDirection)f}, array element {a} has null data"); + } + + if (texData.Data.Length != mip.SizeInBytes) + { + throw new InvalidTextureException( + $"Mip level {i}, face {(CubeMapFaceDirection)f}, array element {a} has incorrect data size"); + } + } + } + } + } + + /// + /// Creates a simple BCnTextureData instance from a single byte array with no mips, no array elements, and not a cubemap. + /// + /// The compression format of the input texture data. + /// The width of the texture. + /// The height of the texture. + /// The texture data as a byte array. + /// Specifies how the alpha channel is encoded. If set to Unknown, will attempt to guess based on format. + /// A new BCnTextureData instance. + public static BCnTextureData FromSingle(CompressionFormat format, int width, int height, byte[] data, AlphaChannelHint alphaChannelHint) + { + if (alphaChannelHint == AlphaChannelHint.Unknown) + { + alphaChannelHint = format.GuessAlphaChannelHint(); + } + var texture = new BCnTextureData(format, width, height, 1, 1, 1, false, false, alphaChannelHint); + texture.Mips[0].First.Data = data.ToArray(); + return texture; + } + + /// + /// Creates a cubemap BCnTextureData instance from six byte[] faces. + /// + /// The compression format of the texture data. + /// The size of each cubemap face (assuming square faces). + /// An IEnumerable of six byte arrays, one for each cubemap face. + /// Specifies how the alpha channel is encoded. If set to Unknown, will attempt to guess based on format. + /// A new BCnTextureData instance representing a cubemap. + /// Thrown when the number of faces is not exactly 6. + public static BCnTextureData FromCubemap(CompressionFormat format, int size, IEnumerable faces, AlphaChannelHint alphaChannelHint) + { + var facesArray = faces.ToArray(); + if (facesArray.Length != 6) + throw new ArgumentException("Cubemap requires exactly 6 faces", nameof(faces)); + + if (alphaChannelHint == AlphaChannelHint.Unknown) + { + alphaChannelHint = format.GuessAlphaChannelHint(); + } + + var texture = new BCnTextureData(format, size, size, depth: 1, numMips: 1, numArrayElements: 1, true, false, alphaChannelHint); + for (int f = 0; f < facesArray.Length; f++) + { + texture.Mips[0][(CubeMapFaceDirection)f, 0].Data = facesArray[f]; + } + return texture; + } + + /// + /// Creates a texture array BCnTextureData instance from multiple byte[] slices. + /// + /// The compression format of the texture data. + /// The width of each texture in the array. + /// The height of each texture in the array. + /// An IEnumerable of byte arrays, one for each texture in the array. + /// A new BCnTextureData instance representing a texture array. + public static BCnTextureData FromTextureArray(CompressionFormat format, int width, int height, IEnumerable slices) + { + var slicesArray = slices.ToArray(); + var texture = new BCnTextureData(format, width, height, depth: 1, numMips: 1, slicesArray.Length, false, false); + for (int i = 0; i < slicesArray.Length; i++) + { + texture.Mips[0][0, i].Data = slicesArray[i]; + } + return texture; + } + + /// + /// Creates a BCnTextureData instance with multiple mip levels from byte arrays. + /// + /// The compression format of the texture data. + /// The width of the largest (first) mip level. + /// The height of the largest (first) mip level. + /// An IEnumerable of byte arrays, one for each mip level. + /// A new BCnTextureData instance with the specified mip levels. + public static BCnTextureData FromMipLevels(CompressionFormat format, int width, int height, IEnumerable mipLevels) + { + var mipsArray = mipLevels.ToArray(); + var texture = new BCnTextureData(format, width, height, depth: 1, mipsArray.Length, 1, false, false); + for (int i = 0; i < mipsArray.Length; i++) + { + texture.Mips[i].First.Data = mipsArray[i]; + } + return texture; + } + } + + /// + /// Thrown when the provided texture data is invalid. + /// + public class InvalidTextureException : Exception + { + public InvalidTextureException(string message) : base(message) + { + } + } + + public static class BCnTextureDataExtensions + { + /// + /// Convert a to another uncompressed pixel format. + /// + /// + /// + /// Whether to do colorspace conversion when the source format does not match the target format + /// Returns self if already desired format. New data is created otherwise. + /// + public static BCnTextureData ConvertTo(this BCnTextureData data, CompressionFormat format, bool convertColorspace = false) + { + if (data.Format == format) + return data; + + if (format.IsBlockCompressedFormat()) + { + throw new ArgumentException( + $"New format should be a non-block-compressed format. Please use {nameof(BcEncoder)} for encoding to compressed formats!"); + } + if(data.Format.IsBlockCompressedFormat()) + { + BcDecoder decoder = new BcDecoder(new DecoderOptions + { + IsParallel = false + }, new DecoderOutputOptions + { + InputColorSpace = InputColorSpaceAssumption.Auto, + OutputColorSpace = convertColorspace ? OutputColorSpaceTarget.Auto : OutputColorSpaceTarget.KeepAsIs + }); + + return decoder.Decode(data, format); + } + + if (data.Format == format) + { + return data; + } + + return ConvertPixelFormat(data, format, convertColorspace); + } + + private static BCnTextureData ConvertPixelFormat(BCnTextureData data, CompressionFormat newFormat, bool convertColorspace) + { + var newData = new BCnTextureData(newFormat, data.Width, data.Height, data.Depth, data.NumMips, data.NumArrayElements, data.IsCubeMap, false, data.AlphaChannelHint); + for (var m = 0; m < data.NumMips; m++) + { + for (var f = 0; f < data.NumFaces; f++) + { + for (var a = 0; a < data.NumArrayElements; a++) + { + var converted = + ColorExtensions.InternalConvertToAsBytesFromBytes( + data.Mips[m][(CubeMapFaceDirection)f, a].Data, + data.Format, + newFormat, + convertColorspace ? data.Format.GetColorConversionMode(newFormat) : ColorConversionMode.None); + + newData.Mips[m][(CubeMapFaceDirection)f, a].Data = converted; + } + } + } + + return newData; + } + } +} diff --git a/BCnEnc.Net/Shared/Vectorized/MathV.cs b/BCnEnc.Net/Shared/Vectorized/MathV.cs new file mode 100644 index 0000000..9403e68 --- /dev/null +++ b/BCnEnc.Net/Shared/Vectorized/MathV.cs @@ -0,0 +1,39 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace BCnEncoder.Shared.Vectorized; + +internal static class MathV +{ + /// + /// Calculates the power of each element in with the corresponding element in . + /// Value and exponent are expected to be positive and exponent should be non-zero. + /// + /// The base values. + /// The exponents. + /// A vector containing the results of raising each element in to the power of the corresponding element in . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Pow(Vector128 value, Vector128 exponent) + { + // pow(x,y) = exp(y*log(x)) + Vector128 logX = Vector128.Log(value); + Vector128 multYLogX = exponent * logX; + Vector128 result = Vector128.Exp(multYLogX); + + return result; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Pow(Vector256 value, Vector256 exponent) + { + // pow(x,y) = exp(y*log(x)) + Vector256 logX = Vector256.Log(value); + Vector256 multYLogX = exponent * logX; + Vector256 result = Vector256.Exp(multYLogX); + + return result; + } +} diff --git a/BCnEncNet.sln b/BCnEncNet.sln index 6e3c4d8..0076c4c 100644 --- a/BCnEncNet.sln +++ b/BCnEncNet.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29311.281 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31912.275 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BCnEncoder", "BCnEnc.Net\BCnEncoder.csproj", "{92C54AF8-D727-4060-8118-2C809D1FF9E0}" EndProject @@ -12,7 +12,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BCnEncoder.NET.ImageSharp", "BCnEncoder.NET.ImageSharp\BCnEncoder.NET.ImageSharp.csproj", "{7D884C56-B982-4B8C-907E-68F231CF3D89}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BCnEncoder.NET.ImageSharp", "BCnEncoder.NET.ImageSharp\BCnEncoder.NET.ImageSharp.csproj", "{7D884C56-B982-4B8C-907E-68F231CF3D89}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BCnEncoder.Net.TextureFormats", "BCnEncoder.Net.TextureFormats\BCnEncoder.Net.TextureFormats.csproj", "{C66A515A-1C75-4132-9D05-EA394C26FB5F}" + ProjectSection(ProjectDependencies) = postProject + {92C54AF8-D727-4060-8118-2C809D1FF9E0} = {92C54AF8-D727-4060-8118-2C809D1FF9E0} + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -32,6 +37,10 @@ Global {7D884C56-B982-4B8C-907E-68F231CF3D89}.Debug|Any CPU.Build.0 = Debug|Any CPU {7D884C56-B982-4B8C-907E-68F231CF3D89}.Release|Any CPU.ActiveCfg = Release|Any CPU {7D884C56-B982-4B8C-907E-68F231CF3D89}.Release|Any CPU.Build.0 = Release|Any CPU + {C66A515A-1C75-4132-9D05-EA394C26FB5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C66A515A-1C75-4132-9D05-EA394C26FB5F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C66A515A-1C75-4132-9D05-EA394C26FB5F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C66A515A-1C75-4132-9D05-EA394C26FB5F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/BCnEncTests/Api/ApiTests.cs b/BCnEncTests/Api/ApiTests.cs new file mode 100644 index 0000000..0694b88 --- /dev/null +++ b/BCnEncTests/Api/ApiTests.cs @@ -0,0 +1,289 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using BCnEncoder.Encoder; +using BCnEncoder.ImageSharp; +using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; +using BCnEncTests.Support; +using Xunit; + +namespace BCnEncTests.Api; + +public class EncoderApiTests +{ + private static BcEncoder MakeEncoder(CompressionFormat format) + { + var encoder = new BcEncoder(format); + encoder.OutputOptions.Quality = CompressionQuality.Fast; + encoder.OutputOptions.GenerateMipMaps = true; + encoder.OutputOptions.MaxMipMapLevel = -1; + return encoder; + } + + private static BCnTextureData LoadTestFile(bool raw, string name) + { + if (raw) + { + return ImageLoader.TestRawImages[name].ToBCnTextureData(); + } + else + { + return ImageLoader.TestEncodedImages[name].Item1.ToTextureData(); + } + } + + private static void ValidateData(BcEncoder encoder, int inputWidth, int inputHeight, int inputDepth, BCnTextureData outputData) + { + Assert.Equal(outputData.NumMips, encoder.CalculateNumberOfMipLevels(inputWidth, inputHeight , inputDepth)); + + for (var f = 0; f < outputData.NumFaces; f++) + { + for (var m = 0; m < outputData.NumMips; m++) + { + Assert.Equal((CubeMapFaceDirection)f, outputData.Mips[m][(CubeMapFaceDirection)f].Direction); + + var mip = outputData.Mips[m][(CubeMapFaceDirection)f]; + encoder.CalculateMipMapSize(inputWidth, inputHeight, inputDepth, m, out var mW, out var mH, out var mD); + var byteSize = encoder.CalculateMipMapByteSize(inputWidth, inputHeight, inputDepth, m); + Assert.Equal(mW, mip.Width); + Assert.Equal(mH, mip.Height); + Assert.Equal(mD, mip.Depth); + Assert.Equal(byteSize, mip.SizeInBytes); + Assert.Equal(byteSize, mip.Data.Length); + } + } + } + + private static void AssertNonCube(CompressionFormat format, BCnTextureData outputData, BCnTextureData inputData) + { + Assert.True(outputData.HasMipLevels); + Assert.False(outputData.IsCubeMap); + Assert.Equal(1, outputData.NumFaces); + Assert.Equal(format, outputData.Format); + Assert.Equal(inputData.Width, outputData.Width); + Assert.Equal(inputData.Height, outputData.Height); + Assert.True(outputData.NumMips > 1); + Assert.Equal(outputData.NumMips, outputData.Mips.Length); + } + + [Theory] + [InlineData(true, "blocks", CompressionFormat.Bc1)] + [InlineData(true, "rg_1", CompressionFormat.Bc5)] + [InlineData(false, "raw_r8g8b8_unorm", CompressionFormat.Bc1)] + [InlineData(false, "bc1_unorm", CompressionFormat.Bc7)] + [InlineData(false, "bc6h_ufloat", CompressionFormat.RgbaHalf)] + [InlineData(false, "bc6h_ufloat", CompressionFormat.Rgb24)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.Rgb24)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.RgbaHalf)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.Bc6S)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.Bc6U)] + public void TestEncode(bool raw, string name, CompressionFormat format) + { + var encoder = MakeEncoder(format); + + var inputData = LoadTestFile(raw, name); + + Assert.True(inputData.Format != CompressionFormat.Unknown); + + if (inputData.Format.IsBlockCompressedFormat()) + { + inputData = inputData.ConvertTo(CompressionFormat.RgbaFloat); + } + + // Test + var outputData = encoder.Encode(inputData); + + AssertNonCube(format, outputData, inputData); + ValidateData(encoder, inputData.Width, inputData.Height, inputData.Depth, outputData); + } + + [Theory] + [InlineData(true, "blocks", CompressionFormat.Bc1)] + [InlineData(true, "rg_1", CompressionFormat.Bc5)] + [InlineData(false, "raw_r8g8b8_unorm", CompressionFormat.Bc1)] + [InlineData(false, "bc1_unorm", CompressionFormat.Bc7)] + [InlineData(false, "bc6h_ufloat", CompressionFormat.RgbaHalf)] + [InlineData(false, "bc6h_ufloat", CompressionFormat.Rgb24)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.Rgb24)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.RgbaHalf)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.Bc6S)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.Bc6U)] + public void TestEncodeBytes(bool raw, string name, CompressionFormat format) + { + var encoder = MakeEncoder(format); + + var inputData = LoadTestFile(raw, name); + + Assert.True(inputData.Format != CompressionFormat.Unknown); + + // Test + var outputData = encoder.EncodeBytes(inputData.First.Data, inputData.Width, inputData.Height, inputData.Format); + + AssertNonCube(format, outputData, inputData); + ValidateData(encoder, inputData.Width, inputData.Height, inputData.Depth, outputData); + } + + [Theory] + [InlineData("blocks", CompressionFormat.Bc1)] + [InlineData("blocks", CompressionFormat.R5G5B5A1_Packed)] + public void TestEncodeLdr(string name, CompressionFormat format) + { + var encoder = MakeEncoder(format); + + var inputData = LoadTestFile(true, name); + + // Test + var outputData = encoder.EncodeHdr(inputData.First.AsMemory2D()); + + AssertNonCube(format, outputData, inputData); + ValidateData(encoder, inputData.Width, inputData.Height, inputData.Depth, outputData); + } + + [Theory] + [InlineData("hdr_1_rgbe", CompressionFormat.Bc6U)] + [InlineData("hdr_1_rgbe", CompressionFormat.RgbaFloat)] + [InlineData("hdr_1_rgbe", CompressionFormat.R9G9B9E5_Packed)] + [InlineData("hdr_1_rgbe", CompressionFormat.RgbHalf)] + [InlineData("hdr_1_rgbe", CompressionFormat.Bc1)] + public void TestEncodeHdr(string name, CompressionFormat format) + { + var encoder = MakeEncoder(format); + + var inputData = LoadTestFile(false, name).ConvertTo(CompressionFormat.RgbaFloat); + + Assert.Equal(CompressionFormat.RgbaFloat, inputData.Format); + + // Test + var outputData = encoder.EncodeHdr(inputData.First.AsMemory2D()); + + AssertNonCube(format, outputData, inputData); + + ValidateData(encoder, inputData.Width, inputData.Height, inputData.Depth, outputData); + } + + [Theory] + [InlineData(true, "blocks", CompressionFormat.Bc1)] + [InlineData(true, "rg_1", CompressionFormat.Bc5)] + [InlineData(false, "raw_r8g8b8_unorm", CompressionFormat.Bc1)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.Rgb24)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.RgbaHalf)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.Bc6S)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.Bc6U)] + public void TestEncodeToRawBytes(bool raw, string name, CompressionFormat format) + { + var encoder = MakeEncoder(format); + + var inputData = LoadTestFile(raw, name); + + Assert.NotEqual(CompressionFormat.Unknown, inputData.Format); + + var outputData = new BCnTextureData(format, inputData.Width, inputData.Height, inputData.Depth, + encoder.CalculateNumberOfMipLevels(inputData.Width, inputData.Height, inputData.Depth)); + + // Test + for (var m = 0; m < outputData.NumMips; m++) + { + outputData.Mips[m].First.Data = + encoder.EncodeToRawBytes(inputData, m, out var mipWidth, out var mipHeight, out var mipDepth); + + Assert.Equal(outputData.Mips[m].Width, mipWidth); + Assert.Equal(outputData.Mips[m].Height, mipHeight); + Assert.Equal(outputData.Mips[m].Depth, mipDepth); + } + + AssertNonCube(format, outputData, inputData); + ValidateData(encoder, inputData.Width, inputData.Height, inputData.Depth, outputData); + } + + [Theory] + [InlineData(true, "blocks", CompressionFormat.Bc1)] + [InlineData(true, "rg_1", CompressionFormat.Bc5)] + [InlineData(false, "raw_r8g8b8_unorm", CompressionFormat.Bc1)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.Rgb24)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.RgbaHalf)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.Bc6S)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.Bc6U)] + public void TestEncodeBytesToRawBytes(bool raw, string name, CompressionFormat format) + { + var encoder = MakeEncoder(format); + + var inputData = LoadTestFile(raw, name); + + Assert.NotEqual(CompressionFormat.Unknown, inputData.Format); + + var outputData = new BCnTextureData(format, inputData.Width, inputData.Height, inputData.Depth, + encoder.CalculateNumberOfMipLevels(inputData.Width, inputData.Height, inputData.Depth)); + + // Test + for (var m = 0; m < outputData.NumMips; m++) + { + outputData.Mips[m].First.Data = + encoder.EncodeToRawBytes(inputData.First.Data, inputData.Width, inputData.Height, inputData.Format, m, out var mipWidth, out var mipHeight); + + Assert.Equal(outputData.Mips[m].Width, mipWidth); + Assert.Equal(outputData.Mips[m].Height, mipHeight); + } + + AssertNonCube(format, outputData, inputData); + ValidateData(encoder, inputData.Width, inputData.Height, inputData.Depth, outputData); + } + + // [Theory] + // [InlineData("blocks", CompressionFormat.Bc1)] + // [InlineData("blocks", CompressionFormat.RgbaFloat)] + // public void TestEncodeToRawBytesLdr(string name, CompressionFormat format) + // { + // var encoder = MakeEncoder(format); + // + // var inputData = LoadTestFile(true, name); + // + // Assert.Equal(CompressionFormat.Rgba32, inputData.Format); + // + // var outputData = new BCnTextureData(format, inputData.Width, inputData.Height, + // encoder.CalculateNumberOfMipLevels(inputData.Width, inputData.Height)); + // + // // Test + // for (var m = 0; m < outputData.NumMips; m++) + // { + // outputData.Mips[m].First.Data = + // encoder.EncodeToRawBytes(inputData.First.Data, m, out var mipWidth, out var mipHeight); + // + // Assert.Equal(outputData.Mips[m].Width, mipWidth); + // Assert.Equal(outputData.Mips[m].Height, mipHeight); + // } + // + // AssertNonCube(format, outputData, inputData); + // ValidateData(encoder, inputData.Width, inputData.Height, outputData); + // } + // + // [Theory] + // [InlineData("hdr_1_rgbe", CompressionFormat.Bc6U)] + // [InlineData("hdr_1_rgbe", CompressionFormat.RgbaFloat)] + // [InlineData("hdr_1_rgbe", CompressionFormat.Bc1)] + // public void TestEncodeToRawBytesHdr(string name, CompressionFormat format) + // { + // var encoder = MakeEncoder(format); + // + // var inputData = LoadTestFile(false, name).ConvertTo(CompressionFormat.RgbaFloat); + // + // Assert.Equal(CompressionFormat.RgbaFloat, inputData.Format); + // + // var outputData = new BCnTextureData(format, inputData.Width, inputData.Height, + // encoder.CalculateNumberOfMipLevels(inputData.Width, inputData.Height)); + // + // // Test + // for (var m = 0; m < outputData.NumMips; m++) + // { + // outputData.Mips[m].First.Data = + // encoder.EncodeToRawBytes(inputData.First.Data, inputData.First.Width, inputData.First.Height, m, out var mipWidth, out var mipHeight); + // + // Assert.Equal(outputData.Mips[m].Width, mipWidth); + // Assert.Equal(outputData.Mips[m].Height, mipHeight); + // } + // + // AssertNonCube(format, outputData, inputData); + // ValidateData(encoder, inputData.Width, inputData.Height, outputData); + // } +} diff --git a/BCnEncTests/Api/BcTextureDataTests.cs b/BCnEncTests/Api/BcTextureDataTests.cs new file mode 100644 index 0000000..3475d36 --- /dev/null +++ b/BCnEncTests/Api/BcTextureDataTests.cs @@ -0,0 +1,67 @@ +using BCnEncoder.Decoder; +using BCnEncoder.Shared; +using BCnEncTests.Support; +using Xunit; + +namespace BCnEncTests.Api; + +public class BcTextureDataTests +{ + [Fact] + public void TestConversions() + { + var originalData = ImageLoader.TestEncodedImages["bc1_unorm"].Item1.ToTextureData(); + Assert.Equal(CompressionFormat.Bc1, originalData.Format); + + BcDecoder decoder = new BcDecoder(); + + var decompressedRgba = decoder.Decode(originalData, CompressionFormat.Rgba32); + Assert.Equal(CompressionFormat.Rgba32, decompressedRgba.Format); + + var convertedBgra = decompressedRgba.ConvertTo(CompressionFormat.Bgra32); + Assert.Equal(CompressionFormat.Bgra32, convertedBgra.Format); + + for (var f = 0; f < decompressedRgba.NumFaces; f++) + { + for (var m = 0; m < decompressedRgba.NumMips; m++) + { + var mip = decompressedRgba.Mips[m][(CubeMapFaceDirection)f]; + var mip2 = convertedBgra.Mips[m][(CubeMapFaceDirection)f]; + + Assert.Equal(mip2.SizeInBytes, mip.SizeInBytes); + Assert.Equal(mip2.Data.Length, mip2.SizeInBytes); + Assert.Equal(mip.Data.Length, mip.SizeInBytes); + + for (var i = 0; i < mip2.SizeInBytes; i += 4) + { + Assert.Equal(mip.Data[i+0], mip2.Data[i+2]); + Assert.Equal(mip.Data[i+1], mip2.Data[i+1]); + Assert.Equal(mip.Data[i+2], mip2.Data[i+0]); + Assert.Equal(mip.Data[i+3], mip2.Data[i+3]); + } + } + } + + + var convertedRg = convertedBgra.ConvertTo(CompressionFormat.R8G8); + Assert.Equal(CompressionFormat.R8G8, convertedRg.Format); + for (var f = 0; f < convertedBgra.NumFaces; f++) + { + for (var m = 0; m < convertedBgra.NumMips; m++) + { + var mip = convertedBgra.Mips[m][(CubeMapFaceDirection)f]; + var mip2 = convertedRg.Mips[m][(CubeMapFaceDirection)f]; + + Assert.Equal(mip2.SizeInBytes, mip.SizeInBytes / 2); + Assert.Equal(mip2.Data.Length, mip2.SizeInBytes); + Assert.Equal(mip.Data.Length, mip.SizeInBytes); + + for (var i = 0; i < mip2.Width * mip2.Height; i++) + { + Assert.Equal(mip.Data[i * 4 + 2], mip2.Data[i * 2 + 0]); + Assert.Equal(mip.Data[i * 4 + 1], mip2.Data[i * 2 + 1]); + } + } + } + } +} diff --git a/BCnEncTests/Api/CancellationTests.cs b/BCnEncTests/Api/CancellationTests.cs new file mode 100644 index 0000000..9ed6889 --- /dev/null +++ b/BCnEncTests/Api/CancellationTests.cs @@ -0,0 +1,155 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using BCnEncoder.Decoder; +using BCnEncoder.Encoder; +using BCnEncoder.Shared; +using BCnEncTests.Support; +using Xunit; + +namespace BCnEncTests.Api; + +public class CancellationTests +{ + private static BcEncoder MakeEncoder(CompressionFormat format, bool parallel) + { + var encoder = new BcEncoder(format); + encoder.OutputOptions.Quality = CompressionQuality.Fast; + encoder.OutputOptions.GenerateMipMaps = true; + encoder.OutputOptions.MaxMipMapLevel = -1; + encoder.Options.IsParallel = parallel; + return encoder; + } + + private static BcDecoder MakeDecoder(bool parallel) + { + return new BcDecoder() + { + Options = { IsParallel = parallel } + }; + } + + [Theory] + [InlineData(true, "blocks", CompressionFormat.Bc1)] + [InlineData(true, "hdr_1_rgbe", CompressionFormat.Rgb24)] + [InlineData(true, "hdr_1_rgbe", CompressionFormat.RgbaHalf)] + [InlineData(true, "hdr_1_rgbe", CompressionFormat.Bc6U)] + [InlineData(false, "blocks", CompressionFormat.Bc1)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.Rgb24)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.RgbaHalf)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.Bc6U)] + public async Task TestEncodeToRawBytesCancel(bool parallel, string name, CompressionFormat format) + { + var encoder = MakeEncoder(format, parallel); + + var inputData = ImageLoader.LoadTestData(name); + + Assert.NotEqual(CompressionFormat.Unknown, inputData.Format); + + var cancelSource = new CancellationTokenSource(); + // Test + + var task = Assert.ThrowsAnyAsync(() => encoder.EncodeToRawBytesAsync(inputData, 0, cancelSource.Token)); + cancelSource.Cancel(); + await task; + } + + [Theory] + [InlineData(true, "blocks", CompressionFormat.Bc1)] + [InlineData(true, "hdr_1_rgbe", CompressionFormat.Rgb24)] + [InlineData(true, "hdr_1_rgbe", CompressionFormat.RgbaHalf)] + [InlineData(true, "hdr_1_rgbe", CompressionFormat.Bc6U)] + [InlineData(false, "blocks", CompressionFormat.Bc1)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.Rgb24)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.RgbaHalf)] + [InlineData(false, "hdr_1_rgbe", CompressionFormat.Bc6U)] + public async Task TestEncodeCancel(bool parallel, string name, CompressionFormat format) + { + var encoder = MakeEncoder(format, parallel); + + var inputData = ImageLoader.LoadTestData(name); + + Assert.NotEqual(CompressionFormat.Unknown, inputData.Format); + + var cancelSource = new CancellationTokenSource(); + // Test + + var task = Assert.ThrowsAnyAsync(() => encoder.EncodeAsync(inputData, cancelSource.Token)); + cancelSource.Cancel(); + await task; + } + + [Theory] + [InlineData(true, "bc1_unorm")] + [InlineData(true, "bc1a_unorm")] + [InlineData(true, "bc7_unorm")] + [InlineData(true, "bc6h_ufloat")] + [InlineData(false, "bc1_unorm")] + [InlineData(false, "bc1a_unorm")] + [InlineData(false, "bc7_unorm")] + [InlineData(false, "bc6h_ufloat")] + public async Task TestDecodeRawBytesCancel(bool parallel, string name) + { + var decoder = MakeDecoder(parallel); + + var inputData = ImageLoader.LoadTestData(name); + + Assert.NotEqual(CompressionFormat.Unknown, inputData.Format); + + var cancelSource = new CancellationTokenSource(); + Task task; + // Test +#pragma warning disable xUnit2021 + if (inputData.Format.IsHdrFormat()) + { + task = Assert.ThrowsAnyAsync( + () => decoder.DecodeRawAsync( + inputData.First.Data, + inputData.Width, + inputData.Height, + inputData.Format, + CompressionFormat.RgbaFloat, + cancelSource.Token)); + } + else + { + task = Assert.ThrowsAnyAsync( + () => decoder.DecodeRawAsync( + inputData.First.Data, + inputData.Width, + inputData.Height, + inputData.Format, + CompressionFormat.Rgba32_sRGB, + cancelSource.Token)); + } +#pragma warning restore xUnit2021 + + cancelSource.Cancel(); + await task; + } + + [Theory] + [InlineData(true, "bc1_unorm")] + [InlineData(true, "bc1a_unorm")] + [InlineData(true, "bc7_unorm")] + [InlineData(true, "bc6h_ufloat")] + [InlineData(false, "bc1_unorm")] + [InlineData(false, "bc1a_unorm")] + [InlineData(false, "bc7_unorm")] + [InlineData(false, "bc6h_ufloat")] + public async Task TestDecodeCancel(bool parallel, string name) + { + var decoder = MakeDecoder(parallel); + + var inputData = ImageLoader.LoadTestData(name); + + Assert.NotEqual(CompressionFormat.Unknown, inputData.Format); + + var cancelSource = new CancellationTokenSource(); + + // Test + var task = Assert.ThrowsAnyAsync(() => decoder.DecodeAsync(inputData, CompressionFormat.Rgba32, cancelSource.Token)); + cancelSource.Cancel(); + await task; + } +} diff --git a/BCnEncTests/Api/CompressionFormatTests.cs b/BCnEncTests/Api/CompressionFormatTests.cs new file mode 100644 index 0000000..f733c15 --- /dev/null +++ b/BCnEncTests/Api/CompressionFormatTests.cs @@ -0,0 +1,131 @@ +using System; +using System.Linq; +using BCnEncoder.Shared; +using Xunit; + +namespace BCnEncTests.Api; + +public class CompressionFormatTests +{ + private static readonly string[] blockCompressedFormatFilter = { "Bc", "Atc" }; + + [Fact] + public void CheckFilters() + { + string[] blockCompressedFormatFilter = { "bc", "atc" }; + + var values = Enum.GetValues(); + + foreach (CompressionFormat format in values) + { + if (format == CompressionFormat.Unknown) + continue; + + CompressionFormatInfo info = format.GetInfo(); + + Assert.Equal(format, info.Format); + + if (format.ToString().EndsWith("_Packed", StringComparison.OrdinalIgnoreCase)) + { + string name = format.ToString().Replace("UF", ""); + name = name.ToString().Replace("_Packed", ""); + Assert.True(info.TexelType.Name.Contains(name), $"Format {format} does not match texel type: Info={info.TexelType.Name}"); + } + + if (format.ToString().Contains("Bc6u", StringComparison.OrdinalIgnoreCase)) + { + Assert.Equal(CompressionFormatType.BlockUFloat, info.FormatType); + Assert.False(format.IsSignedFormat(), $"Format {format} should not be signed: Info={info}"); + Assert.False(format.IsSNormFormat(), $"Format {format} should not be SNorm: Info={info}"); + Assert.False(format.IsUNormFormat(), $"Format {format} should not be UNorm: Info={info}"); + Assert.True(format.IsHdrFormat(), $"Format {format} should be HDR: Info={info}"); + } + else if (format.ToString().Contains("Bc6s", StringComparison.OrdinalIgnoreCase)) + { + Assert.Equal(CompressionFormatType.BlockSFloat, info.FormatType); + Assert.True(format.IsSignedFormat(), $"Format {format} should not be signed: Info={info}"); + Assert.False(format.IsSNormFormat(), $"Format {format} should not be SNorm: Info={info}"); + Assert.False(format.IsUNormFormat(), $"Format {format} should not be UNorm: Info={info}"); + Assert.True(format.IsHdrFormat(), $"Format {format} should be HDR: Info={info}"); + } + else if (format.ToString().EndsWith("S", StringComparison.OrdinalIgnoreCase)) + { + Assert.True(format.IsSignedFormat(), $"Format {format} should be signed: Info={info}"); + Assert.True(format.IsSNormFormat(), $"Format {format} should be SNorm: Info={info}"); + Assert.False(format.IsUNormFormat(), $"Format {format} should not be UNorm: Info={info}"); + Assert.False(format.IsHdrFormat(), $"Format {format} should not be HDR: Info={info}"); + } + else if (format.ToString().EndsWith("Half", StringComparison.OrdinalIgnoreCase)) + { + Assert.Equal(CompressionFormatType.RawFloat, info.FormatType); + Assert.True(format.IsSignedFormat(), $"Format {format} should be signed: Info={info}"); + Assert.False(format.IsSNormFormat(), $"Format {format} should not be SNorm: Info={info}"); + Assert.False(format.IsUNormFormat(), $"Format {format} should not be UNorm: Info={info}"); + Assert.True(format.IsHdrFormat(), $"Format {format} should be HDR: Info={info}"); + } + else if (format.ToString().EndsWith("UI", StringComparison.Ordinal)) + { + Assert.Equal(CompressionFormatType.RawUint, info.FormatType); + Assert.True(format.IsIntegerFormat(), $"Format {format} should be integer: Info={info}"); + Assert.False(format.IsSignedFormat(), $"Format {format} should not be signed: Info={info}"); + Assert.False(format.IsSNormFormat(), $"Format {format} should not be SNorm: Info={info}"); + Assert.False(format.IsUNormFormat(), $"Format {format} should not be UNorm: Info={info}"); + Assert.False(format.IsHdrFormat(), $"Format {format} should not be HDR: Info={info}"); + } + else if (format.ToString().EndsWith("UFloat", StringComparison.OrdinalIgnoreCase) || + format.ToString().EndsWith("UF", StringComparison.OrdinalIgnoreCase) || + format.ToString().EndsWith("UF_Packed", StringComparison.OrdinalIgnoreCase)) + { + Assert.Equal(CompressionFormatType.RawUFloat, info.FormatType); + Assert.False(format.IsSignedFormat(), $"Format {format} should be signed: Info={info}"); + Assert.False(format.IsSNormFormat(), $"Format {format} should not be SNorm: Info={info}"); + Assert.False(format.IsUNormFormat(), $"Format {format} should not be UNorm: Info={info}"); + Assert.True(format.IsHdrFormat(), $"Format {format} should be HDR: Info={info}"); + } + else if (format.ToString().EndsWith("Float", StringComparison.OrdinalIgnoreCase) || + format.ToString().EndsWith("F", StringComparison.OrdinalIgnoreCase)) + { + Assert.Equal(CompressionFormatType.RawFloat, info.FormatType); + Assert.True(format.IsSignedFormat(), $"Format {format} should be signed: Info={info}"); + Assert.False(format.IsSNormFormat(), $"Format {format} should not be SNorm: Info={info}"); + Assert.False(format.IsUNormFormat(), $"Format {format} should not be UNorm: Info={info}"); + Assert.True(format.IsHdrFormat(), $"Format {format} should be HDR: Info={info}"); + } + else if (format.ToString().EndsWith("e32", StringComparison.OrdinalIgnoreCase) || + format.ToString().EndsWith("E5_Packed", StringComparison.Ordinal)) + { + Assert.Equal(CompressionFormatType.RawSharedExponent, info.FormatType); + Assert.False(format.IsSignedFormat(), $"Format {format} should not be signed: Info={info}"); + Assert.False(format.IsSNormFormat(), $"Format {format} should not be SNorm: Info={info}"); + Assert.False(format.IsUNormFormat(), $"Format {format} should not be UNorm: Info={info}"); + Assert.True(format.IsHdrFormat(), $"Format {format} should be HDR: Info={info}"); + } + else + { + Assert.False(format.IsSignedFormat(), $"Format {format} should not be signed: Info={info}"); + Assert.False(format.IsSNormFormat(), $"Format {format} should not be SNorm: Info={info}"); + Assert.True(format.IsUNormFormat(), $"Format {format} should be UNorm: Info={info}"); + Assert.False(format.IsIntegerFormat(), $"Format {format} should not be integer: Info={info}"); + Assert.False(format.IsHdrFormat(), $"Format {format} should not be HDR: Info={info}"); + } + + if (format.ToString().EndsWith("sRGB", StringComparison.OrdinalIgnoreCase)) + Assert.True(format.IsSRGBFormat() && format.IsUNormFormat(), $"Format {format} should be sRGB and UNorm: IsSRGB={format.IsSRGBFormat()}, IsUNorm={format.IsUNormFormat()}, Info={info}"); + else + Assert.False(format.IsSRGBFormat(), $"Format {format} should not be sRGB: Info={info}"); + + if (blockCompressedFormatFilter.Any(filter => format.ToString().Contains(filter, StringComparison.OrdinalIgnoreCase))) + { + Assert.True(format.IsBlockCompressedFormat(), + $"Format {format} should be block compressed: Info={info}"); + Assert.Equal(BlockPixelSize.Size4x4x1, format.GetBlockPixelSize()); + } + else + { + Assert.False(format.IsBlockCompressedFormat(), + $"Format {format} should not be block compressed: Info={info}"); + Assert.Equal(BlockPixelSize.Size1x1x1, format.GetBlockPixelSize()); + } + } + } +} diff --git a/BCnEncTests/Api/DecodingTests.cs b/BCnEncTests/Api/DecodingTests.cs new file mode 100644 index 0000000..27be294 --- /dev/null +++ b/BCnEncTests/Api/DecodingTests.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using BCnEncoder.Decoder; +using BCnEncoder.Decoder.Options; +using BCnEncoder.Shared; +using BCnEncoder.TextureFormats; +using BCnEncTests.Support; +using Xunit; + +namespace BCnEncTests.Api; + +public class DecodingTests +{ + enum FileType + { + Dds, + Ktx, + Ktx2 + } + + const string testImageFolderRoot = "testImages"; + const string referenceFolder = "reference"; + + static string[] unsupportedFilter = new string[] { "astc", "eac", "etc1", "etc2", "bc4s", "bc5s", "basisu", "pvrtc1" }; + + private static DecoderOutputOptions defaultOptions = new DecoderOutputOptions() + { + AlphaHandling = DecoderAlphaHandling.KeepAsIs, + InputColorSpace = InputColorSpaceAssumption.Auto, + OutputColorSpace = OutputColorSpaceTarget.ProcessLinearPreserveColorSpace, + RescaleSnormToUnorm = true + }; + + [SkippableTheory] + [MemberData(nameof(GetTestFiles), "testdds", ".dds")] + public void TestDecodingDds(string testImage, string referenceImage, string path) + { + CheckSkip(testImage); + var decoder = new BcDecoder(defaultOptions); + + // Load the DDS file + var file = DdsFile.Load(File.OpenRead(Path.Combine(path, testImage))); + + var reference = TestHelper.GetReferenceImage(Path.Combine(path, referenceImage)); + + var tolerance = GetTolerance(file.ToTextureData().Format, FileType.Dds); + + TestHelper.TestDecoding(testImage, file, reference, true, decoder, null, tolerance); + } + + [SkippableTheory] + [MemberData(nameof(GetTestFiles), "testktx", ".ktx")] + public void TestDecodingKtx(string testImage, string referenceImage, string path) + { + CheckSkip(testImage); + var decoder = new BcDecoder(defaultOptions); + + // Load the KTX file + var file = KtxFile.Load(File.OpenRead(Path.Combine(path, testImage))); + + var reference = TestHelper.GetReferenceImage(Path.Combine(path, referenceImage)); + + var tolerance = GetTolerance(file.ToTextureData().Format, FileType.Ktx); + + // Very high tolerance due to differences in decoding methods. + TestHelper.TestDecoding(testImage, file, reference, true, decoder, null, tolerance); + } + + [SkippableTheory(Skip = "Ktx2 not implemented")] + [MemberData(nameof(GetTestFiles), "testktx2", ".ktx2")] + public void TestDecodingKtx2(string testImage, string referenceImage, string path) + { + Skip.If(true, "Ktx2 not implemented"); + } + + public static void CheckSkip(string testImage) + { + string skipFound = unsupportedFilter.FirstOrDefault(filter => + testImage.Contains(filter, StringComparison.OrdinalIgnoreCase)); + + Skip.If(skipFound != null, $"Unsupported format matching {skipFound}"); + } + + public static IEnumerable GetTestFiles(string path, string extension) + { + path = Path.Combine(TestHelper.GetProjectRoot(), testImageFolderRoot, path); + + // Check if path exists + if (!Directory.Exists(path)) + { + throw new ArgumentException($"Path does not exist: {path}"); + } + + // Find the reference folder (should be in the test directory) + string refPath = Path.Combine(path, referenceFolder); + if (!Directory.Exists(refPath)) + { + // If no reference folder found in test directory, use the explicit path + refPath = referenceFolder; + if (!Directory.Exists(refPath)) + { + throw new ArgumentException($"Path does not exist: {path}"); + } + } + + var testFiles = Directory.GetFiles(path, $"*{extension}") + .Select(f => new FileInfo(f)); + + var result = new List(); + + foreach (var testFile in testFiles) + { + // For each test file, find the corresponding reference PNG + string refImageName = $"{Path.GetFileNameWithoutExtension(testFile.Name)}.png"; + string refImagePath = Path.Combine(refPath, refImageName); + + if (File.Exists(refImagePath)) + { + result.Add(new object[] { Path.GetRelativePath(path, testFile.FullName), Path.GetRelativePath(path, refImagePath), path }); + } + else + { + //throw new ArgumentException($"Reference image not found: {refImagePath}"); + } + } + + return result; + } + + private static float GetTolerance(CompressionFormat format, FileType type) + { + const float minTolerance = 0.001f; + const float one = 1.5f / 255f; + const float three = 3.5f / 255f; + + + return (format, type) switch + { + (_, _) when format.IsRawPixelFormat() => one, + (CompressionFormat.Bc7, _) => minTolerance, + (CompressionFormat.Bc7_sRGB, _) => minTolerance, + (CompressionFormat.Bc6S, _) => one, + (CompressionFormat.Bc6U, _) => one, + (_, FileType.Dds) => one, // Dds has a small tolerance + (_, FileType.Ktx) => three, // Ktx has a large tolerance due to different decoding methods + }; + } +} diff --git a/BCnEncTests/Api/EncoderOptionsTests.cs b/BCnEncTests/Api/EncoderOptionsTests.cs new file mode 100644 index 0000000..760b6af --- /dev/null +++ b/BCnEncTests/Api/EncoderOptionsTests.cs @@ -0,0 +1,81 @@ +using BCnEncoder.Encoder; +using BCnEncoder.ImageSharp; +using BCnEncoder.Shared; +using BCnEncoder.TextureFormats; +using BCnEncTests.Support; +using Xunit; + +namespace BCnEncTests.Api; + +public class EncoderOptionsTests +{ + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(5)] + public void MaxMipMaps(int requestedMipMaps) + { + var testImage = ImageLoader.TestRawImages["blocks"]; + var encoder = new BcEncoder() + { + OutputOptions = + { + GenerateMipMaps = true, + MaxMipMapLevel = requestedMipMaps + } + }; + + Assert.Equal(requestedMipMaps, encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height, 1)); + + var bcnData = encoder.Encode(testImage); + + Assert.Equal(requestedMipMaps, bcnData.NumMips); + Assert.Equal(requestedMipMaps, bcnData.Mips.Length); + + var ktx = encoder.EncodeToTexture(testImage); + + Assert.Equal(requestedMipMaps, (int)ktx.header.NumberOfMipmapLevels); + Assert.Equal(requestedMipMaps, ktx.MipMaps.Count); + + var dds = encoder.EncodeToTexture(testImage); + + Assert.Equal(requestedMipMaps, (int)dds.header.dwMipMapCount); + Assert.Equal(requestedMipMaps, dds.ArrayElements[0].MipMaps.Length); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void GenerateMipMaps(bool generateMipMaps) + { + var testImage = ImageLoader.TestRawImages["blocks"]; + int requestedMipMaps = generateMipMaps ? + MipMapper.CalculateMipChainLength(testImage.Width, testImage.Height, 1, 0) : + 1; + var encoder = new BcEncoder() + { + OutputOptions = + { + GenerateMipMaps = generateMipMaps + } + }; + + Assert.Equal(requestedMipMaps, encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height)); + + var bcnData = encoder.Encode(testImage); + + Assert.Equal(requestedMipMaps, bcnData.NumMips); + Assert.Equal(requestedMipMaps, bcnData.Mips.Length); + + var ktx = encoder.EncodeToTexture(testImage); + + Assert.Equal(requestedMipMaps, (int)ktx.header.NumberOfMipmapLevels); + Assert.Equal(requestedMipMaps, (int)ktx.MipMaps.Count); + + var dds = encoder.EncodeToTexture(testImage); + + Assert.Equal(requestedMipMaps, (int)dds.header.dwMipMapCount); + Assert.Equal(requestedMipMaps, dds.ArrayElements[0].MipMaps.Length); + } +} diff --git a/BCnEncTests/Api/EncodingTests.cs b/BCnEncTests/Api/EncodingTests.cs new file mode 100644 index 0000000..3b163a2 --- /dev/null +++ b/BCnEncTests/Api/EncodingTests.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.IO; +using BCnEncoder.Encoder; +using BCnEncoder.ImageSharp; +using BCnEncoder.Shared; +using BCnEncoder.TextureFormats; +using BCnEncTests.Support; +using Xunit; +using Xunit.Abstractions; + +namespace BCnEncTests.Api; + +public class EncoderTests +{ + private readonly ITestOutputHelper output; + + public EncoderTests(ITestOutputHelper output) + { + this.output = output; + } + + public static (string, TextureType)[] TestImages = [ + ("rgb_hard", TextureType.Albedo) + ]; + public static string[] RgTestImages = ["blocks", "rg"]; + + public static CompressionFormat[] OverrideFormats = + [ + CompressionFormat.Bc1 + ]; + + public enum TestFileType + { + dds, + ktx + } + + public static IEnumerable GetTestCases(TestFileType fileType) + { + ITextureFileFormat tex = fileType switch + { + TestFileType.dds => new DdsFile(), + TestFileType.ktx => new KtxFile(), + }; + + IEnumerable formats = OverrideFormats.Length > 0 ? OverrideFormats : Enum.GetValues(); + + foreach (CompressionFormat format in formats) + { + (string, TextureType)[] images = TestImages; + + if (tex.IsSupportedFormat(format)) + { + foreach ((string fileName, TextureType texType) image in images) + { + if (format.IsRawPixelFormat()) + { + yield return new object[] { fileType, image.fileName, image.texType, format, CompressionQuality.Fast, tex }; + } + else + { + yield return new object[] { fileType, image.fileName, image.texType, format, CompressionQuality.Fast, tex }; + yield return new object[] { fileType, image.fileName, image.texType, format, CompressionQuality.Balanced, tex }; + yield return new object[] { fileType, image.fileName, image.texType, format, CompressionQuality.BestQuality, tex }; + } + } + } + } + } + + + [Theory] + [MemberData(nameof(GetTestCases), TestFileType.dds)] + public void TestEncoding(TestFileType fileType, string testImage, TextureType texType, CompressionFormat format, CompressionQuality quality, TTexture _) + where TTexture : class, ITextureFileFormat, new() + { + var outFileName = $"test_enc_{fileType.ToString()}_{testImage}_{format}_{quality}.{fileType.ToString()}"; + var rawFile = ImageLoader.TestRawImages[testImage]; + + TestHelper.TestEncodingLdr(rawFile, texType, outFileName, format, quality, output); + } +} diff --git a/BCnEncTests/Api/ProgressTests.cs b/BCnEncTests/Api/ProgressTests.cs new file mode 100644 index 0000000..6416d13 --- /dev/null +++ b/BCnEncTests/Api/ProgressTests.cs @@ -0,0 +1,177 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using BCnEncoder.Decoder; +using BCnEncoder.Encoder; +using BCnEncoder.ImageSharp; +using BCnEncoder.Shared; +using BCnEncoder.TextureFormats; +using BCnEncTests.Support; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; +using Xunit.Abstractions; + +namespace BCnEncTests.Api; + +public class ProgressTests +{ + private readonly ITestOutputHelper output; + + public ProgressTests(ITestOutputHelper output) => this.output = output; + + private async Task ExecuteEncodeProgressReport(BcEncoder encoder, Image testImage) + { + var lastProgress = new ProgressElement(0, 1); + + encoder.Options.Progress = new SynchronousProgress(element => + { + Assert.True(lastProgress.CurrentBlock < element.CurrentBlock); + lastProgress = element; + }); + + var numMips = encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height); + + var expectedTotalBlocks = 0L; + + for (var i = 0; i < numMips; i++) + { + encoder.CalculateMipMapSize(testImage.Width, testImage.Height, i, out var mW, out var mH); + expectedTotalBlocks += encoder.OutputOptions.Format.CalculateMipByteSize(mW, mH, 1) / encoder.OutputOptions.Format.GetBytesPerBlock(); + } + + await using var ms = new MemoryStream(); + await encoder.EncodeToStreamAsync(testImage, ms); + + output.WriteLine("LastProgress = " + lastProgress); + + Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks); + Assert.Equal(1, lastProgress.Percentage); + } + + private async Task ExecuteDecodeProgressReport(BcDecoder decoder, T image) + where T : ITextureFileFormat + { + var lastProgress = new ProgressElement(0, 1); + + decoder.Options.Progress = new SynchronousProgress(element => + { + Assert.True(lastProgress.CurrentBlock < element.CurrentBlock); + lastProgress = element; + }); + + var bcnData = image.ToTextureData(); + + var expectedTotalBlocks = bcnData.Mips.Sum(m => + bcnData.Format.CalculateMipByteSize(m.Width, m.Height, 1)) / bcnData.Format.GetBytesPerBlock(); + + await decoder.DecodeAsync(bcnData, CompressionFormat.Rgba32); + + output.WriteLine("LastProgress = " + lastProgress); + + Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks); + Assert.Equal(1, lastProgress.Percentage); + } + + private async Task ExecuteEncodeSingleMipProgressReport(BcEncoder encoder, Image testImage, int mipLevel) + { + var lastProgress = new ProgressElement(0, 1); + + encoder.Options.Progress = new SynchronousProgress(element => + { + Assert.True(lastProgress.CurrentBlock < element.CurrentBlock); + lastProgress = element; + }); + + encoder.CalculateMipMapSize(testImage.Width, testImage.Height, mipLevel, out var mW, out var mH); + var expectedTotalBlocks = encoder.OutputOptions.Format.CalculateMipByteSize(mW, mH, 1) / encoder.OutputOptions.Format.GetBytesPerBlock(); + + await using var ms = new MemoryStream(); + await encoder.EncodeToRawBytesAsync(testImage, mipLevel); + + output.WriteLine("LastProgress = " + lastProgress); + + Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks); + Assert.Equal(1, lastProgress.Percentage); + } + + private async Task ExecuteDecodeSingleMipProgressReport(BcDecoder decoder, T image, int mipLevel) + where T : ITextureFileFormat + { + var lastProgress = new ProgressElement(0, 1); + + decoder.Options.Progress = new SynchronousProgress(element => + { + Assert.True(lastProgress.CurrentBlock < element.CurrentBlock); + lastProgress = element; + }); + + var bcnData = image.ToTextureData(); + var expectedTotalBlocks = bcnData.Format.CalculateMipByteSize(bcnData.Mips[mipLevel].Width, bcnData.Mips[mipLevel].Height, 1) / bcnData.Format.GetBytesPerBlock(); + + await decoder.DecodeRawAsync(bcnData.Mips[mipLevel].First.Data, bcnData.Mips[mipLevel].Width, bcnData.Mips[mipLevel].Height, bcnData.Format, CompressionFormat.Rgba32); + + output.WriteLine("LastProgress = " + lastProgress); + + Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks); + Assert.Equal(1, lastProgress.Percentage); + } + + + + [Theory] + [InlineData("bc1_unorm", true)] + [InlineData("bc1_unorm", false)] + [InlineData("alpha_1_bgra", true)] + [InlineData("alpha_1_bgra", false)] + public async void DecodeProgress(string decodeFile, bool parallel) + { + var testImage = ImageLoader.TestEncodedImages[decodeFile].Item1; + var decoder = new BcDecoder + { + Options = { IsParallel = parallel } + }; + + await ExecuteDecodeProgressReport(decoder, testImage); + await ExecuteDecodeSingleMipProgressReport(decoder, testImage, 0); + await ExecuteDecodeSingleMipProgressReport(decoder, testImage, 1); + await ExecuteDecodeSingleMipProgressReport(decoder, testImage, 2); + } + + [Theory] + [InlineData("diffuse_1", true, CompressionFormat.Bc1)] + [InlineData("diffuse_1", false, CompressionFormat.Bc1)] + [InlineData("diffuse_1", true, CompressionFormat.R8G8)] + [InlineData("diffuse_1", false, CompressionFormat.R8G8)] + public async void EncodeProgressReport(string testRaw, bool parallel, CompressionFormat format) + { + var testImage = ImageLoader.TestRawImages[testRaw]; + var encoder = new BcEncoder(format) + { + Options = { IsParallel = parallel }, + OutputOptions = { Quality = CompressionQuality.Fast } + }; + + await ExecuteEncodeProgressReport(encoder, testImage); + await ExecuteEncodeSingleMipProgressReport(encoder, testImage, 0); + await ExecuteEncodeSingleMipProgressReport(encoder, testImage, 1); + await ExecuteEncodeSingleMipProgressReport(encoder, testImage, 2); + await ExecuteEncodeSingleMipProgressReport(encoder, testImage, 3); + } +} + +class SynchronousProgress : IProgress +{ + private readonly Action handler; + + public SynchronousProgress(Action handler) + { + this.handler = handler; + } + + public void Report(T value) + { + handler.Invoke(value); + } +} diff --git a/BCnEncTests/AtcTests.cs b/BCnEncTests/AtcTests.cs deleted file mode 100644 index d02da3e..0000000 --- a/BCnEncTests/AtcTests.cs +++ /dev/null @@ -1,108 +0,0 @@ -using BCnEncoder.Decoder; -using BCnEncoder.Encoder; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using Xunit; -using BCnEncoder.ImageSharp; - -namespace BCnEncTests -{ - public class AtcTests - { - [Fact] - public void AtcKtxDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.Atc); - var original = ImageLoader.TestLenna; - - // Act - var ktx = encoder.EncodeToKtx(original); - var image = decoder.DecodeToImageRgba32(ktx); - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - - [Fact] - public void AtcDdsDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.Atc); - var original = ImageLoader.TestLenna; - - // Act - var dds = encoder.EncodeToDds(original); - var image = decoder.DecodeToImageRgba32(dds); - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - - [Fact] - public void AtcExplicitKtxDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.AtcExplicitAlpha); - var original = ImageLoader.TestAlphaGradient1; - - // Act - var ktx = encoder.EncodeToKtx(original); - var image = decoder.DecodeToImageRgba32(ktx); - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - - [Fact] - public void AtcExplicitDdsDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.AtcExplicitAlpha); - var original = ImageLoader.TestAlphaGradient1; - - // Act - var dds = encoder.EncodeToDds(original); - var image = decoder.DecodeToImageRgba32(dds); - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - - [Fact] - public void AtcInterpolatedKtxDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.AtcInterpolatedAlpha); - var original = ImageLoader.TestAlphaGradient1; - - // Act - var ktx = encoder.EncodeToKtx(original); - var image = decoder.DecodeToImageRgba32(ktx); - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - - [Fact] - public void AtcInterpolatedDdsDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.AtcInterpolatedAlpha); - var original = ImageLoader.TestAlphaGradient1; - - // Act - var dds = encoder.EncodeToDds(original); - var image = decoder.DecodeToImageRgba32(dds); - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - } -} diff --git a/BCnEncTests/BC1Tests.cs b/BCnEncTests/BC1Tests.cs deleted file mode 100644 index bfc7320..0000000 --- a/BCnEncTests/BC1Tests.cs +++ /dev/null @@ -1,164 +0,0 @@ -using BCnEncoder.Shared; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; -using BCnEncoder.ImageSharp; - -namespace BCnEncTests -{ - public class Bc1Tests - { - - [Fact] - public void Decode() { - var block = new Bc1Block - { - color0 = new ColorRgb565(255, 255, 255), - color1 = new ColorRgb565(0, 0, 0) - }; - - Assert.False(block.HasAlphaOrBlack); - block[0] = 1; - block[1] = 1; - block[2] = 1; - block[3] = 1; - - block[4] = 3; - block[5] = 3; - block[6] = 3; - block[7] = 3; - - block[8] = 2; - block[9] = 2; - block[10] = 2; - block[11] = 2; - - block[12] = 0; - block[13] = 0; - block[14] = 0; - block[15] = 0; - - var raw = block.Decode(false); - Assert.Equal(new ColorRgba32(0, 0, 0), raw.p00); - Assert.Equal(new ColorRgba32(0, 0, 0), raw.p10); - Assert.Equal(new ColorRgba32(0, 0, 0), raw.p20); - Assert.Equal(new ColorRgba32(0, 0, 0), raw.p30); - - Assert.Equal(new ColorRgba32(85, 85, 85), raw.p01); - Assert.Equal(new ColorRgba32(85, 85, 85), raw.p11); - Assert.Equal(new ColorRgba32(85, 85, 85), raw.p21); - Assert.Equal(new ColorRgba32(85, 85, 85), raw.p31); - - Assert.Equal(new ColorRgba32(170, 170, 170), raw.p02); - Assert.Equal(new ColorRgba32(170, 170, 170), raw.p12); - Assert.Equal(new ColorRgba32(170, 170, 170), raw.p22); - Assert.Equal(new ColorRgba32(170, 170, 170), raw.p32); - - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p03); - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p13); - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p23); - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p33); - } - - [Fact] - public void DecodeBlack() { - var block = new Bc1Block - { - color0 = new ColorRgb565(200, 200, 200), - color1 = new ColorRgb565(255, 255, 255) - }; - - Assert.True(block.HasAlphaOrBlack); - block[0] = 0; - block[1] = 0; - block[2] = 0; - block[3] = 0; - - block[4] = 3; - block[5] = 3; - block[6] = 3; - block[7] = 3; - - block[8] = 2; - block[9] = 2; - block[10] = 2; - block[11] = 2; - - block[12] = 1; - block[13] = 1; - block[14] = 1; - block[15] = 1; - - var raw = block.Decode(false); - Assert.Equal(new ColorRgba32(206, 203, 206), raw.p00); - Assert.Equal(new ColorRgba32(206, 203, 206), raw.p10); - Assert.Equal(new ColorRgba32(206, 203, 206), raw.p20); - Assert.Equal(new ColorRgba32(206, 203, 206), raw.p30); - - Assert.Equal(new ColorRgba32(0, 0, 0), raw.p01); - Assert.Equal(new ColorRgba32(0, 0, 0), raw.p11); - Assert.Equal(new ColorRgba32(0, 0, 0), raw.p21); - Assert.Equal(new ColorRgba32(0, 0, 0), raw.p31); - - Assert.Equal(new ColorRgba32(230, 229, 230), raw.p02); - Assert.Equal(new ColorRgba32(230, 229, 230), raw.p12); - Assert.Equal(new ColorRgba32(230, 229, 230), raw.p22); - Assert.Equal(new ColorRgba32(230, 229, 230), raw.p32); - - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p03); - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p13); - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p23); - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p33); - } - - [Fact] - public void DecodeAlpha() { - var block = new Bc1Block - { - color0 = new ColorRgb565(200, 200, 200), - color1 = new ColorRgb565(255, 255, 255) - }; - - Assert.True(block.HasAlphaOrBlack); - block[0] = 0; - block[1] = 0; - block[2] = 0; - block[3] = 0; - - block[4] = 3; - block[5] = 3; - block[6] = 3; - block[7] = 3; - - block[8] = 2; - block[9] = 2; - block[10] = 2; - block[11] = 2; - - block[12] = 1; - block[13] = 1; - block[14] = 1; - block[15] = 1; - - var raw = block.Decode(true); - Assert.Equal(new ColorRgba32(206, 203, 206), raw.p00); - Assert.Equal(new ColorRgba32(206, 203, 206), raw.p10); - Assert.Equal(new ColorRgba32(206, 203, 206), raw.p20); - Assert.Equal(new ColorRgba32(206, 203, 206), raw.p30); - - Assert.Equal(new ColorRgba32(0,0,0,0), raw.p01); - Assert.Equal(new ColorRgba32(0,0,0,0), raw.p11); - Assert.Equal(new ColorRgba32(0,0,0,0), raw.p21); - Assert.Equal(new ColorRgba32(0,0,0,0), raw.p31); - - Assert.Equal(new ColorRgba32(230, 229, 230), raw.p02); - Assert.Equal(new ColorRgba32(230, 229, 230), raw.p12); - Assert.Equal(new ColorRgba32(230, 229, 230), raw.p22); - Assert.Equal(new ColorRgba32(230, 229, 230), raw.p32); - - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p03); - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p13); - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p23); - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p33); - } - } -} diff --git a/BCnEncTests/BCnEncTests.csproj b/BCnEncTests/BCnEncTests.csproj index 22b08b5..c69747b 100644 --- a/BCnEncTests/BCnEncTests.csproj +++ b/BCnEncTests/BCnEncTests.csproj @@ -1,21 +1,31 @@ - netcoreapp3.1 + net9.0 false - - - - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/BCnEncTests/Bc5Tests.cs b/BCnEncTests/Bc5Tests.cs deleted file mode 100644 index 7eb37ff..0000000 --- a/BCnEncTests/Bc5Tests.cs +++ /dev/null @@ -1,84 +0,0 @@ -using BCnEncoder.Decoder; -using BCnEncoder.ImageSharp; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using Xunit; - -namespace BCnEncTests -{ - public class Bc5Tests - { - [Fact] - public void Bc5Indices() - { - var block = new Bc5Block(); - - for (var i = 0; i < 16; i++) - { - block.SetRedIndex(i, (byte)(i % 8)); - block.SetGreenIndex(i, (byte)((i + 3) % 8)); - } - - for (var i = 0; i < 16; i++) - { - int rI = block.GetRedIndex(i); - int gI = block.GetGreenIndex(i); - - Assert.Equal((byte)(i % 8), rI); - Assert.Equal((byte)((i + 3) % 8), gI); - } - } - - [Fact] - public void Bc5DdsDecode() - { - var reference = ImageLoader.TestDecodingBc5Reference; - var decoded = new BcDecoder().DecodeToImageRgba32(DdsLoader.TestDecompressBc5); - - var refSpan = TestHelper.GetSinglePixelArrayAsColors(reference); - var decSpan = TestHelper.GetSinglePixelArrayAsColors(decoded); - - Assert.Equal(reference.Width, decoded.Width); - Assert.Equal(reference.Height, decoded.Height); - - // Exactly equal - for (var i = 0; i < reference.Width * reference.Height; i++) - Assert.Equal(refSpan[i], decSpan[i]); - } - - [Fact] - public void Bc5BlockDecode() - { - var block = new Bc5Block() - { - greenBlock = new Bc4ComponentBlock() { componentBlock = 0x91824260008935ee }, - redBlock = new Bc4ComponentBlock() { componentBlock = 0x6d900f66d3c0a70d } - }; - - var referenceBlock = new RawBlock4X4Rgba32 - { - p00 = new ColorRgba32(13, 53, 0, 255), - p01 = new ColorRgba32(136, 238, 0, 255), - p02 = new ColorRgba32(255, 212, 0, 255), - p03 = new ColorRgba32(167, 238, 0, 255), - p10 = new ColorRgba32(13, 53, 0, 255), - p11 = new ColorRgba32(136, 238, 0, 255), - p12 = new ColorRgba32(167, 238, 0, 255), - p13 = new ColorRgba32(75, 185, 0, 255), - p20 = new ColorRgba32(255, 212, 0, 255), - p21 = new ColorRgba32(167, 238, 0, 255), - p22 = new ColorRgba32(13, 53, 0, 255), - p23 = new ColorRgba32(75, 159, 0, 255), - p30 = new ColorRgba32(167, 238, 0, 255), - p31 = new ColorRgba32(75, 185, 0, 255), - p32 = new ColorRgba32(13, 53, 0, 255), - p33 = new ColorRgba32(75, 159, 0, 255) - }; - - var decodedBlock = block.Decode(); - - for (var i = 0; i < 16; i++) - Assert.Equal(referenceBlock[i], decodedBlock[i]); - } - } -} diff --git a/BCnEncTests/Bc6EncoderTests.cs b/BCnEncTests/Bc6EncoderTests.cs deleted file mode 100644 index ae389b2..0000000 --- a/BCnEncTests/Bc6EncoderTests.cs +++ /dev/null @@ -1,405 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; -using BCnEncoder.Encoder; -using BCnEncoder.Encoder.Bptc; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using Microsoft.Toolkit.HighPerformance; -using Xunit; -using Xunit.Abstractions; - -namespace BCnEncTests -{ - public class Bc6EncoderTests - { - private ITestOutputHelper output; - - public Bc6EncoderTests(ITestOutputHelper output) => this.output = output; - - [Theory] - [InlineData(0x7F, 8, false)] - [InlineData(0xFF, 8, false)] - [InlineData(0x7F, 8, true)] - [InlineData(0xFF, 8, true)] - [InlineData(0b111_1100_1101, 11, true)] - [InlineData(0b111_1100_1101, 11, false)] - [InlineData(0b011_1100_1101, 11, true)] - [InlineData(0b011_1100_1101, 11, false)] - [InlineData(0b0_1001, 5, true)] - [InlineData(0b0_1001, 5, false)] - [InlineData(0b1_1100, 5, true)] - [InlineData(0b1_1100, 5, false)] - public void Quantize(int initialQuantizedValue, int endpointBits, bool signed) - { - if (signed) - { - initialQuantizedValue = IntHelper.SignExtend(initialQuantizedValue, endpointBits); - } - var unquantized = Bc6Block.UnQuantize(initialQuantizedValue, endpointBits, signed); - var finishedUnquantized = Bc6Block.FinishUnQuantize(unquantized, signed); - - var prequantized = Bc6EncodingHelpers.PreQuantize(finishedUnquantized, signed); - var quantized = Bc6EncodingHelpers.Quantize(prequantized, endpointBits, signed); - - Assert.InRange(prequantized, unquantized - 2, unquantized + 2); - Assert.InRange(quantized, initialQuantizedValue - 1, initialQuantizedValue + 1); - } - - [Fact] - public void PreQuantizeRawEndpoint() - { - var ep0 = new ColorRgbFloat(1, 0.001f, 0.2f); - - var preQuantized = Bc6EncodingHelpers.PreQuantizeRawEndpoint(ep0, false); - var unQu = Bc6Block.FinishUnQuantize(preQuantized, false); - - Assert.InRange(unQu.Item1, ep0.r - 0.001f, ep0.r + 0.001f); - Assert.InRange(unQu.Item2, ep0.g - 0.001f, ep0.g + 0.001f); - Assert.InRange(unQu.Item3, ep0.b - 0.001f, ep0.b + 0.001f); - } - - [Fact] - public void RgbBoundingBox() - { - var testBlock = new RawBlock4X4RgbFloat() - { - p00 = new ColorRgbFloat(0.01f, -0.05f, 0.02f), - p01 = new ColorRgbFloat(0.02f, 0.02f, 0.03f), - p02 = new ColorRgbFloat(0.02f, 0.02f, 0.02f), - p03 = new ColorRgbFloat(0.01f, 0.02f, 0.02f), - p10 = new ColorRgbFloat(0.02f, 0.02f, 0.02f), - p11 = new ColorRgbFloat(0.045f, 0.02f, 0.02f), - p12 = new ColorRgbFloat(0.04f, 0.02f, 0.02f), - p13 = new ColorRgbFloat(0.04f, 0.02f, 0.02f), - p20 = new ColorRgbFloat(0.21f, 0.02f, 0.02f), - p21 = new ColorRgbFloat(0.01f, 0.02f, 0.02f), - p22 = new ColorRgbFloat(0.01f, 0.32f, 0.02f), - p23 = new ColorRgbFloat(1.01f, 0.22f, 0.02f), - p30 = new ColorRgbFloat(0.01f, 0.12f, 0.02f), - p31 = new ColorRgbFloat(0.01f, 0.02f, 0.02f), - p32 = new ColorRgbFloat(0.01f, 0.62f, 0.7f), - p33 = new ColorRgbFloat(0.01f, 0.02f, 0.02f) - }; - - BCnEncoder.Shared.RgbBoundingBox.CreateFloat(testBlock.AsSpan, out var min, out var max); - - Assert.InRange(min.r, 0.01, 0.02); - Assert.InRange(min.g, -0.05, -0.03); - Assert.InRange(min.b, 0.02, 0.03); - - Assert.InRange(max.r, 0.9, 1.01); - Assert.InRange(max.g, 0.59, 0.62); - Assert.InRange(max.b, 0.60, 0.7); - } - - - [Fact] - public void PackMode3() - { - const int endpointPrecision = 10; - var random = new Random(); - var rand = random.Next(); - - var ep0 = (random.Next() & (1 << endpointPrecision) - 1, - random.Next() & (1 << endpointPrecision) - 1, - random.Next() & (1 << endpointPrecision) - 1); - var ep1 = (random.Next() & (1 << endpointPrecision) - 1, - random.Next() & (1 << endpointPrecision) - 1, - random.Next() & (1 << endpointPrecision) - 1); - - var indices = new byte[16]; - for (var i = 1; i < indices.Length; i++) - { - indices[i] = (byte) random.Next(1 << 4); - } - - var block = Bc6Block.PackType3(ep0, ep1, indices); - - Assert.Equal(Bc6BlockType.Type3, block.Type); - - var extracted0 = block.ExtractEp0(); - var extracted1 = block.ExtractEp1(); - - Assert.Equal(ep0, extracted0); - Assert.Equal(ep1, extracted1); - - for (var i = 0; i < indices.Length; i++) - { - var idx = block.GetColorIndex(1, 0, 4, i); - Assert.Equal(indices[i], idx); - } - } - - - [Theory] - [InlineData(Bc6BlockType.Type0)] - [InlineData(Bc6BlockType.Type1)] - [InlineData(Bc6BlockType.Type2)] - [InlineData(Bc6BlockType.Type6)] - [InlineData(Bc6BlockType.Type10)] - [InlineData(Bc6BlockType.Type14)] - [InlineData(Bc6BlockType.Type18)] - [InlineData(Bc6BlockType.Type22)] - [InlineData(Bc6BlockType.Type26)] - [InlineData(Bc6BlockType.Type30)] - [InlineData(Bc6BlockType.Type3)] - [InlineData(Bc6BlockType.Type7)] - [InlineData(Bc6BlockType.Type11)] - //[InlineData(Bc6BlockType.Type15)] //deltabits too small to encode the testblock - internal void EncodeAllModesUnsigned(Bc6BlockType type) - { - var testBlock = new RawBlock4X4RgbFloat() - { - p00 = new ColorRgbFloat(1.011f, 10.01f, 2.01f), - p01 = new ColorRgbFloat(1.01f, 10.014f, 2.012f), - p02 = new ColorRgbFloat(1.005f, 10.012f, 2.02f), - p03 = new ColorRgbFloat(1.01f, 10.013f, 2.023f), - p10 = new ColorRgbFloat(1.011f, 10.01f, 2.01f), - p11 = new ColorRgbFloat(1.01f, 10.014f, 2.012f), - p12 = new ColorRgbFloat(1.005f, 10.012f, 2.02f), - p13 = new ColorRgbFloat(1.01f, 10.013f, 2.023f), - p20 = new ColorRgbFloat(1.011f, 10.01f, 2.01f), - p21 = new ColorRgbFloat(1.01f, 10.014f, 2.012f), - p22 = new ColorRgbFloat(1.005f, 10.012f, 2.02f), - p23 = new ColorRgbFloat(1.01f, 10.013f, 2.023f), - p30 = new ColorRgbFloat(1.011f, 10.01f, 2.01f), - p31 = new ColorRgbFloat(1.01f, 10.014f, 2.012f), - p32 = new ColorRgbFloat(1.005f, 10.012f, 2.02f), - p33 = new ColorRgbFloat(1.01f, 10.013f, 2.023f) - }; - Bc6Block encoded; - var badTransform = false; - - if(type.HasSubsets()) - { - var indexBlock = Bc6Encoder.CreateClusterIndexBlock(testBlock, out var numClusters, 2); - var best2SubsetPartitions = BptcEncodingHelpers.Rank2SubsetPartitions(indexBlock, numClusters, true); - - var bestPartition = best2SubsetPartitions[0]; - - Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep0, out var ep1, bestPartition, 0); - Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep2, out var ep3, bestPartition, 1); - - encoded = Bc6ModeEncoder.EncodeBlock2Sub(type, testBlock, ep0, ep1, ep2, ep3, bestPartition, - false, out badTransform); - } - else - { - BCnEncoder.Shared.RgbBoundingBox.CreateFloat(testBlock.AsSpan, out var min, out var max); - encoded = Bc6ModeEncoder.EncodeBlock1Sub(type, testBlock, min, max, false, - out badTransform); - } - - Assert.False(badTransform); - Assert.Equal(type, encoded.Type); - var decoded = encoded.Decode(false); - var error = testBlock.CalculateError(decoded); - Assert.InRange(error, 0, 0.3); - } - - [Theory] - [InlineData(Bc6BlockType.Type0)] - [InlineData(Bc6BlockType.Type1)] - [InlineData(Bc6BlockType.Type2)] - [InlineData(Bc6BlockType.Type6)] - [InlineData(Bc6BlockType.Type10)] - [InlineData(Bc6BlockType.Type14)] - [InlineData(Bc6BlockType.Type18)] - [InlineData(Bc6BlockType.Type22)] - [InlineData(Bc6BlockType.Type26)] - [InlineData(Bc6BlockType.Type30)] - [InlineData(Bc6BlockType.Type3)] - [InlineData(Bc6BlockType.Type7)] - [InlineData(Bc6BlockType.Type11)] - [InlineData(Bc6BlockType.Type15)] - internal void EncodeAllModesSigned(Bc6BlockType type) - { - var testBlock = new RawBlock4X4RgbFloat() - { - p00 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f), - p01 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f), - p02 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f), - p03 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f), - p10 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f), - p11 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f), - p12 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f), - p13 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f), - p20 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f), - p21 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f), - p22 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f), - p23 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f), - p30 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f), - p31 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f), - p32 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f), - p33 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f) - }; - Bc6Block encoded; - var badTransform = false; - - if (type.HasSubsets()) - { - var indexBlock = Bc6Encoder.CreateClusterIndexBlock(testBlock, out var numClusters, 2); - var best2SubsetPartitions = BptcEncodingHelpers.Rank2SubsetPartitions(indexBlock, numClusters, true); - - var bestPartition = best2SubsetPartitions[0]; - - Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep0, out var ep1, bestPartition, 0); - Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep2, out var ep3, bestPartition, 1); - - encoded = Bc6ModeEncoder.EncodeBlock2Sub(type, testBlock, ep0, ep1, ep2, ep3, bestPartition, - true, out badTransform); - } - else - { - BCnEncoder.Shared.RgbBoundingBox.CreateFloat(testBlock.AsSpan, out var min, out var max); - encoded = Bc6ModeEncoder.EncodeBlock1Sub(type, testBlock, min, max, true, - out badTransform); - } - - Assert.False(badTransform); - Assert.Equal(type, encoded.Type); - var decoded = encoded.Decode(true); - var error = testBlock.CalculateError(decoded); - Assert.InRange(error, 0, 0.5); - } - - [Fact] - public void Encode() - { - var signed = true; - var image = HdrLoader.TestHdrKiara; - var blocks = ImageToBlocks.ImageTo4X4(image.pixels.AsMemory().AsMemory2D(image.height, image.width), out var bW, out var bH); - - for (var i = 0; i < blocks.Length; i++) - { - var encoded = Bc6Encoder.Bc6EncoderBalanced.EncodeBlock(blocks[i], signed); - var decoded = encoded.Decode(signed); - - var error = decoded.CalculateError(blocks[i]); - if (error > 0.06 || i == 14749) - { - Debugger.Break(); - encoded = Bc6Encoder.Bc6EncoderBalanced.EncodeBlock(blocks[i], signed); - } - } - } - - [Fact] - public void EncodeFast() - { - TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrKiara, CompressionFormat.Bc6U, CompressionQuality.Fast, - "encoding_bc6_kiara_fast.ktx", output); - } - - [Fact] - public void EncodeBalanced() - { - TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrKiara, CompressionFormat.Bc6U, CompressionQuality.Balanced, - "encoding_bc6_kiara_balanced.ktx", output); - } - - [Fact] - public void EncodeBestQuality() - { - TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrKiara, CompressionFormat.Bc6U, CompressionQuality.BestQuality, - "encoding_bc6_kiara_bestquality.ktx", output); - } - - [Fact] - public void EncodeProbeFast() - { - TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6U, CompressionQuality.Fast, - "encoding_bc6_probe_fast.ktx", output); - } - - [Fact] - public void EncodeProbeBalanced() - { - TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6U, CompressionQuality.Balanced, - "encoding_bc6_probe_balanced.ktx", output); - } - - [Fact] - public void EncodeProbeBestQuality() - { - TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6U, CompressionQuality.BestQuality, - "encoding_bc6_probe_bestquality.ktx", output); - } - - [Fact] - public void EncodeProbeSignedFast() - { - TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6S, CompressionQuality.Fast, - "encoding_bc6_probe_signed_fast.ktx", output); - } - - [Fact] - public void EncodeProbSignedBalanced() - { - TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6S, CompressionQuality.Balanced, - "encoding_bc6_probe_signed_balanced.ktx", output); - } - - [Fact] - public void EncodeProbeSignedBestQuality() - { - TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6S, CompressionQuality.BestQuality, - "encoding_bc6_probe_signed_bestquality.ktx", output); - } - - [Fact] - public void EncodeToKtx() - { - var encoder = new BcEncoder(); - encoder.OutputOptions.Quality = CompressionQuality.Fast; - encoder.OutputOptions.GenerateMipMaps = true; - encoder.OutputOptions.Format = CompressionFormat.Bc6U; - - var ktx = encoder.EncodeToKtxHdr(HdrLoader.TestHdrKiara.PixelMemory); - - using var fs = File.OpenWrite("encoding_bc6_ktx.ktx"); - ktx.Write(fs); - } - - [Fact] - public void EncodeToDds() - { - var encoder = new BcEncoder(); - encoder.OutputOptions.Quality = CompressionQuality.Fast; - encoder.OutputOptions.GenerateMipMaps = true; - encoder.OutputOptions.Format = CompressionFormat.Bc6U; - - var dds = encoder.EncodeToDdsHdr(HdrLoader.TestHdrKiara.PixelMemory); - - using var fs = File.OpenWrite("encoding_bc6_dds.dds"); - dds.Write(fs); - } - - [Fact] - public void EncodeToRaw() - { - var encoder = new BcEncoder(); - encoder.OutputOptions.Quality = CompressionQuality.Fast; - encoder.OutputOptions.GenerateMipMaps = true; - encoder.OutputOptions.Format = CompressionFormat.Bc6U; - - var ktx = encoder.EncodeToKtxHdr(HdrLoader.TestHdrKiara.PixelMemory); - - var allMips = encoder.EncodeToRawBytesHdr(HdrLoader.TestHdrKiara.PixelMemory); - - Assert.True(allMips.Length > 1); - Assert.True(allMips.Length == ktx.MipMaps.Count); - - for (var i = 0; i < allMips.Length; i++) - { - var single = encoder.EncodeToRawBytesHdr(HdrLoader.TestHdrKiara.PixelMemory, i, out var mW, out var mH); - - Assert.Equal(ktx.MipMaps[i].Faces[0].Data, single); - Assert.Equal(allMips[i], single); - } - } - } -} diff --git a/BCnEncTests/Bc6HDecoderTests.cs b/BCnEncTests/Bc6HDecoderTests.cs deleted file mode 100644 index b30ca72..0000000 --- a/BCnEncTests/Bc6HDecoderTests.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using BCnEncoder.Decoder; -using BCnEncoder.Encoder; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using Microsoft.Toolkit.HighPerformance; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; -using Xunit.Abstractions; - -namespace BCnEncTests -{ - public class Bc6HDecoderTests - { - private ITestOutputHelper output; - - public Bc6HDecoderTests(ITestOutputHelper output) => this.output = output; - - [Fact] - public void DecodeDds() - { - var decoder = new BcDecoder(); - var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraDds); - - var img = new Image((int)HdrLoader.TestHdrKiaraDds.header.dwWidth, - (int)HdrLoader.TestHdrKiaraDds.header.dwHeight); - - var pixels = TestHelper.GetSinglePixelArray(img); - - for (var i = 0; i < decoded.Length; i++) - { - pixels[i] = new RgbaVector(decoded[i].r, decoded[i].g, decoded[i].b); - } - - TestHelper.SetSinglePixelArray(img, pixels); - - var hdr = new HdrImage((int) HdrLoader.TestHdrKiaraDds.header.dwWidth, - (int) HdrLoader.TestHdrKiaraDds.header.dwHeight); - - Assert.Equal(hdr.pixels.Length, decoded.Length); - - hdr.pixels = decoded; - using var sfs = File.OpenWrite("decoding_test_dds_bc6h.hdr"); - hdr.Write(sfs); - - img.SaveAsPng("decoding_test_dds_bc6h.png"); - - TestHelper.AssertPixelsEqual(HdrLoader.TestHdrKiara.pixels, decoded, CompressionQuality.Fast, output); - } - - [Fact] - public void DecodeKtx() - { - var decoder = new BcDecoder(); - var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraKtx); - - var img = new Image((int)HdrLoader.TestHdrKiaraKtx.header.PixelWidth, - (int)HdrLoader.TestHdrKiaraKtx.header.PixelHeight); - - var pixels = TestHelper.GetSinglePixelArray(img); - - for (var i = 0; i < decoded.Length; i++) - { - pixels[i] = new RgbaVector(decoded[i].r, decoded[i].g, decoded[i].b); - } - - TestHelper.SetSinglePixelArray(img, pixels); - - var hdr = new HdrImage((int)HdrLoader.TestHdrKiaraKtx.header.PixelWidth, - (int)HdrLoader.TestHdrKiaraKtx.header.PixelHeight); - - Assert.Equal(hdr.pixels.Length, decoded.Length); - - hdr.pixels = decoded; - using var sfs = File.OpenWrite("decoding_test_ktx_bc6h.hdr"); - hdr.Write(sfs); - - img.SaveAsPng("decoding_test_ktx_bc6h.png"); - - TestHelper.AssertPixelsEqual(HdrLoader.TestHdrKiara.pixels, decoded, CompressionQuality.BestQuality, output); - } - - // TestHdrKiaraDds includes some blocks with all modes. - // The test_hdr_kiara_dds_float16_data.bin file contains Half-float values decoded with a reference decoder. - // This test ensures that decoding is bit-exact. - [Fact] - public void AllBlocksDecodesExact() - { - var decoder = new BcDecoder(); - var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraDds); - - using var fs = File.OpenRead("../../../testImages/test_hdr_kiara_dds_float16_data.bin"); - using var ms = new MemoryStream(); - fs.CopyTo(ms); - var length = (int)ms.Position; - - var bytes = ms.GetBuffer().AsSpan(0, length); - var halfs = MemoryMarshal.Cast(bytes); - Assert.Equal(halfs.Length / 4, decoded.Length); - - for (var i = 0; i < decoded.Length; i++) - { - float r = halfs[i * 4 + 0]; - float g = halfs[i * 4 + 1]; - float b = halfs[i * 4 + 2]; - - Assert.Equal(r, decoded[i].r); - Assert.Equal(g, decoded[i].g); - Assert.Equal(b, decoded[i].b); - } - } - - // Test that above statement holds true. And that modes are recognized correctly - [Fact] - public void DecodeModes() - { - var modes = new int[31]; - - var hdr = HdrLoader.TestHdrKiaraDds; - - var bytes = hdr.Faces[0].MipMaps[0].Data; - var blocks = MemoryMarshal.Cast(bytes); - - for (var i = 0; i < blocks.Length; i++) - { - var block = blocks[i]; - var mode = block.Type; - modes[(int) mode]++; - } - - for (var i = 0; i < modes.Length; i++) - { - output.WriteLine($"Mode {i}: {modes[i]}"); - } - - Assert.True(modes[0] > 0); - Assert.True(modes[1] > 0); - Assert.True(modes[2] > 0); - Assert.True(modes[6] > 0); - Assert.True(modes[10] > 0); - Assert.True(modes[14] > 0); - Assert.True(modes[18] > 0); - Assert.True(modes[22] > 0); - Assert.True(modes[26] > 0); - Assert.True(modes[30] > 0); - Assert.True(modes[3] > 0); - Assert.True(modes[7] > 0); - Assert.True(modes[11] > 0); - Assert.True(modes[15] > 0); - } - - [Fact] - public void DecodeErrorBlock() - { - var decoder = new BcDecoder(); - - var width = 16; - var height = 16; - var bufferSize = decoder.GetBlockSize(CompressionFormat.Bc6U) * width * height; - - var buffer = new byte[bufferSize]; - Random r = new Random(44); - r.NextBytes(buffer); - - var decoded = decoder.DecodeRawHdr(buffer, width * 4, height * 4, CompressionFormat.Bc6U); - Assert.Contains(new ColorRgbFloat(1, 0, 1), decoded); - - HdrImage image = new HdrImage(new Span2D(decoded, height * 4, width * 4)); - using var fs = File.OpenWrite("test_decode_bc6h_error.hdr"); - image.Write(fs); - } - - } -} diff --git a/BCnEncTests/BgraTests.cs b/BCnEncTests/BgraTests.cs deleted file mode 100644 index 7ae666e..0000000 --- a/BCnEncTests/BgraTests.cs +++ /dev/null @@ -1,78 +0,0 @@ -using BCnEncoder.Decoder; -using BCnEncoder.Encoder; -using BCnEncoder.ImageSharp; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using Xunit; - -namespace BCnEncTests -{ - public class BgraTests - { - [Fact] - public void BgraDdsDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.Bgra); - var original = ImageLoader.TestLenna; - - // Act - var dds = encoder.EncodeToDds(original); - var image = decoder.DecodeToImageRgba32(dds); - - - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - - [Fact] - public void BgraAlphaDdsDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.Bgra); - var original = ImageLoader.TestAlphaGradient1; - - // Act - var dds = encoder.EncodeToDds(original); - var image = decoder.DecodeToImageRgba32(dds); - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - - [Fact] - public void BgraKtxDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.Bgra); - var original = ImageLoader.TestLenna; - - // Act - var ktx = encoder.EncodeToKtx(original); - var image = decoder.DecodeToImageRgba32(ktx); - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - - [Fact] - public void BgraAlphaKtxDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.Bgra); - var original = ImageLoader.TestAlphaGradient1; - - // Act - var ktx = encoder.EncodeToKtx(original); - var image = decoder.DecodeToImageRgba32(ktx); - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - } -} diff --git a/BCnEncTests/BlockCompression/BC1Tests.cs b/BCnEncTests/BlockCompression/BC1Tests.cs new file mode 100644 index 0000000..d7b0048 --- /dev/null +++ b/BCnEncTests/BlockCompression/BC1Tests.cs @@ -0,0 +1,215 @@ +using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; +using Xunit; + +namespace BCnEncTests.BlockCompression +{ + public class Bc1Tests + { + + [Fact] + public void Decode() { + var block = new Bc1Block + { + color0 = new ColorB5G6R5Packed(0.3f, 1, 0.6f), + color1 = new ColorB5G6R5Packed(0, 0, 0) + }; + + Assert.False(block.HasAlphaOrBlack); + block[0] = 1; + block[1] = 1; + block[2] = 1; + block[3] = 1; + + block[4] = 3; + block[5] = 3; + block[6] = 3; + block[7] = 3; + + block[8] = 2; + block[9] = 2; + block[10] = 2; + block[11] = 2; + + block[12] = 0; + block[13] = 0; + block[14] = 0; + block[15] = 0; + + var raw = block.Decode(false, false); + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p00.As()); + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p10.As()); + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p20.As()); + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p30.As()); + + Assert.Equal(new ColorRgba32(25, 85, 52), raw.p01.As()); + Assert.Equal(new ColorRgba32(25, 85, 52), raw.p11.As()); + Assert.Equal(new ColorRgba32(25, 85, 52), raw.p21.As()); + Assert.Equal(new ColorRgba32(25, 85, 52), raw.p31.As()); + + Assert.Equal(new ColorRgba32(49, 170, 104), raw.p02.As()); + Assert.Equal(new ColorRgba32(49, 170, 104), raw.p12.As()); + Assert.Equal(new ColorRgba32(49, 170, 104), raw.p22.As()); + Assert.Equal(new ColorRgba32(49, 170, 104), raw.p32.As()); + + Assert.Equal(new ColorRgba32(74, 255, 156), raw.p03.As()); + Assert.Equal(new ColorRgba32(74, 255, 156), raw.p13.As()); + Assert.Equal(new ColorRgba32(74, 255, 156), raw.p23.As()); + Assert.Equal(new ColorRgba32(74, 255, 156), raw.p33.As()); + } + + [Fact] + public void DecodeIgnoreOrder() { + var block = new Bc1Block + { + color0 = new ColorB5G6R5Packed(0, 0, 0), + color1 = new ColorB5G6R5Packed(0.3f, 1, 0.6f) + }; + + Assert.True(block.HasAlphaOrBlack); + block[0] = 0; + block[1] = 0; + block[2] = 0; + block[3] = 0; + + block[4] = 2; + block[5] = 2; + block[6] = 2; + block[7] = 2; + + block[8] = 3; + block[9] = 3; + block[10] = 3; + block[11] = 3; + + block[12] = 1; + block[13] = 1; + block[14] = 1; + block[15] = 1; + + var raw = block.Decode(false, false); + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p00.As()); + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p10.As()); + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p20.As()); + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p30.As()); + + Assert.Equal(new ColorRgba32(25, 85, 52), raw.p01.As()); + Assert.Equal(new ColorRgba32(25, 85, 52), raw.p11.As()); + Assert.Equal(new ColorRgba32(25, 85, 52), raw.p21.As()); + Assert.Equal(new ColorRgba32(25, 85, 52), raw.p31.As()); + + Assert.Equal(new ColorRgba32(49, 170, 104), raw.p02.As()); + Assert.Equal(new ColorRgba32(49, 170, 104), raw.p12.As()); + Assert.Equal(new ColorRgba32(49, 170, 104), raw.p22.As()); + Assert.Equal(new ColorRgba32(49, 170, 104), raw.p32.As()); + + Assert.Equal(new ColorRgba32(74, 255, 156), raw.p03.As()); + Assert.Equal(new ColorRgba32(74, 255, 156), raw.p13.As()); + Assert.Equal(new ColorRgba32(74, 255, 156), raw.p23.As()); + Assert.Equal(new ColorRgba32(74, 255, 156), raw.p33.As()); + } + + [Fact] + public void DecodeBlack() { + var block = new Bc1Block + { + color0 = new ColorB5G6R5Packed(100 / 255f, 200 / 255f, 200 / 255f), + color1 = new ColorB5G6R5Packed(1, 1, 1) + }; + + Assert.True(block.HasAlphaOrBlack); + block[0] = 0; + block[1] = 0; + block[2] = 0; + block[3] = 0; + + block[4] = 3; + block[5] = 3; + block[6] = 3; + block[7] = 3; + + block[8] = 2; + block[9] = 2; + block[10] = 2; + block[11] = 2; + + block[12] = 1; + block[13] = 1; + block[14] = 1; + block[15] = 1; + + var raw = block.Decode(true, false); + Assert.Equal(new ColorRgba32(99, 198, 197), raw.p00.As()); + Assert.Equal(new ColorRgba32(99, 198, 197), raw.p10.As()); + Assert.Equal(new ColorRgba32(99, 198, 197), raw.p20.As()); + Assert.Equal(new ColorRgba32(99, 198, 197), raw.p30.As()); + + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p01.As()); + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p11.As()); + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p21.As()); + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p31.As()); + + Assert.Equal(new ColorRgba32(177, 227, 226), raw.p02.As()); + Assert.Equal(new ColorRgba32(177, 227, 226), raw.p12.As()); + Assert.Equal(new ColorRgba32(177, 227, 226), raw.p22.As()); + Assert.Equal(new ColorRgba32(177, 227, 226), raw.p32.As()); + + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p03.As()); + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p13.As()); + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p23.As()); + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p33.As()); + } + + [Fact] + public void DecodeAlpha() { + var block = new Bc1Block + { + color0 = new ColorB5G6R5Packed(100 / 255f, 200 / 255f, 200 / 255f), + color1 = new ColorB5G6R5Packed(1, 1, 1) + }; + + Assert.True(block.HasAlphaOrBlack); + block[0] = 0; + block[1] = 0; + block[2] = 0; + block[3] = 0; + + block[4] = 3; + block[5] = 3; + block[6] = 3; + block[7] = 3; + + block[8] = 2; + block[9] = 2; + block[10] = 2; + block[11] = 2; + + block[12] = 1; + block[13] = 1; + block[14] = 1; + block[15] = 1; + + var raw = block.Decode(true, true); + Assert.Equal(new ColorRgba32(99, 198, 197), raw.p00.As()); + Assert.Equal(new ColorRgba32(99, 198, 197), raw.p10.As()); + Assert.Equal(new ColorRgba32(99, 198, 197), raw.p20.As()); + Assert.Equal(new ColorRgba32(99, 198, 197), raw.p30.As()); + + // Alpha 0 + Assert.Equal(new ColorRgba32(0,0,0,0), raw.p01.As()); + Assert.Equal(new ColorRgba32(0,0,0,0), raw.p11.As()); + Assert.Equal(new ColorRgba32(0,0,0,0), raw.p21.As()); + Assert.Equal(new ColorRgba32(0,0,0,0), raw.p31.As()); + + Assert.Equal(new ColorRgba32(177, 227, 226), raw.p02.As()); + Assert.Equal(new ColorRgba32(177, 227, 226), raw.p12.As()); + Assert.Equal(new ColorRgba32(177, 227, 226), raw.p22.As()); + Assert.Equal(new ColorRgba32(177, 227, 226), raw.p32.As()); + + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p03.As()); + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p13.As()); + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p23.As()); + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p33.As()); + } + } +} diff --git a/BCnEncTests/BlockCompression/Bc5Tests.cs b/BCnEncTests/BlockCompression/Bc5Tests.cs new file mode 100644 index 0000000..fde3d40 --- /dev/null +++ b/BCnEncTests/BlockCompression/Bc5Tests.cs @@ -0,0 +1,69 @@ +using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; +using Xunit; + +namespace BCnEncTests.BlockCompression +{ + public class Bc5Tests + { + [Fact] + public void Bc5Indices() + { + var block = new Bc5Block(); + + for (var i = 0; i < 16; i++) + { + block.SetRedIndex(i, (byte)(i % 8)); + block.SetGreenIndex(i, (byte)((i + 3) % 8)); + } + + for (var i = 0; i < 16; i++) + { + int rI = block.GetRedIndex(i); + int gI = block.GetGreenIndex(i); + + Assert.Equal((byte)(i % 8), rI); + Assert.Equal((byte)((i + 3) % 8), gI); + } + } + + [Fact] + public void Bc5BlockDecode() + { + var block = new Bc5Block() + { + greenBlock = new Bc4ComponentBlock() { componentBlock = 0x91824260008935ee }, + redBlock = new Bc4ComponentBlock() { componentBlock = 0x6d900f66d3c0a70d } + }; + + var referenceBlock = new ColorRgba32[] + { + // 1st row + new ColorRgba32(13, 53, 0, 255), + new ColorRgba32(13, 53, 0, 255), + new ColorRgba32(255, 212, 0, 255), + new ColorRgba32(167, 238, 0, 255), + // 2nd row + new ColorRgba32(136, 238, 0, 255), + new ColorRgba32(136, 238, 0, 255), + new ColorRgba32(167, 238, 0, 255), + new ColorRgba32(75, 185, 0, 255), + // 3rd row + new ColorRgba32(255, 212, 0, 255), + new ColorRgba32(167, 238, 0, 255), + new ColorRgba32(13, 53, 0, 255), + new ColorRgba32(13, 53, 0, 255), + // 4th row + new ColorRgba32(167, 238, 0, 255), + new ColorRgba32(75, 185, 0, 255), + new ColorRgba32(75, 159, 0, 255), + new ColorRgba32(75, 159, 0, 255) + }; + + var decodedBlock = block.Decode(); + + for (var i = 0; i < 16; i++) + Assert.Equal(referenceBlock[i], decodedBlock[i].As()); + } + } +} diff --git a/BCnEncTests/BlockCompression/Bc6HDecoderTests.cs b/BCnEncTests/BlockCompression/Bc6HDecoderTests.cs new file mode 100644 index 0000000..171dd10 --- /dev/null +++ b/BCnEncTests/BlockCompression/Bc6HDecoderTests.cs @@ -0,0 +1,116 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using BCnEncoder.Decoder; +using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; +using BCnEncoder.TextureFormats; +using Xunit; +using Xunit.Abstractions; + +namespace BCnEncTests.BlockCompression +{ + public class Bc6HDecoderTests + { + private ITestOutputHelper output; + + public Bc6HDecoderTests(ITestOutputHelper output) => this.output = output; + + + // bc6h_alltypes.dds includes some blocks with all modes. + // The bc6h_alltypes.bin file contains Half-float values decoded with a reference decoder. + // This test ensures that decoding is bit-exact. + [Fact] + public void AllBlocksDecodesExact() + { + var decoder = new BcDecoder(); + using var fs1 = File.OpenRead("../../../testImages/bc6h_alltypes.dds"); + + var ddsFile = DdsFile.Load(fs1); + var decoded = decoder.Decode(ddsFile).First.AsMemory(); + + using var fs = File.OpenRead("../../../testImages/bc6h_alltypes.bin"); + using var ms = new MemoryStream(); + fs.CopyTo(ms); + var length = (int)ms.Position; + + var bytes = ms.GetBuffer().AsSpan(0, length); + var halfs = MemoryMarshal.Cast(bytes); + Assert.Equal(halfs.Length / 4, decoded.Length); + + for (var i = 0; i < decoded.Length; i++) + { + float r = (float)halfs[i * 4 + 0]; + float g = (float)halfs[i * 4 + 1]; + float b = (float)halfs[i * 4 + 2]; + + Assert.Equal(r, decoded.Span[i].r); + Assert.Equal(g, decoded.Span[i].g); + Assert.Equal(b, decoded.Span[i].b); + } + } + + // Test that above statement holds true. And that modes are recognized correctly + [Fact] + public void DecodeModes() + { + var modes = new int[31]; + + using var fs1 = File.OpenRead("../../../testImages/bc6h_alltypes.dds"); + + var hdr = DdsFile.Load(fs1); + + var bytes = hdr.ArrayElements[0].MipMaps[0].Data; + var blocks = MemoryMarshal.Cast(bytes); + + for (var i = 0; i < blocks.Length; i++) + { + var block = blocks[i]; + var mode = block.Type; + modes[(int) mode]++; + } + + for (var i = 0; i < modes.Length; i++) + { + output.WriteLine($"Mode {i}: {modes[i]}"); + } + + Assert.True(modes[0] > 0); + Assert.True(modes[1] > 0); + Assert.True(modes[2] > 0); + Assert.True(modes[6] > 0); + Assert.True(modes[10] > 0); + Assert.True(modes[14] > 0); + Assert.True(modes[18] > 0); + Assert.True(modes[22] > 0); + Assert.True(modes[26] > 0); + Assert.True(modes[30] > 0); + Assert.True(modes[3] > 0); + Assert.True(modes[7] > 0); + Assert.True(modes[11] > 0); + Assert.True(modes[15] > 0); + } + + [Fact] + public void DecodeErrorBlock() + { + var decoder = new BcDecoder(); + + var width = 16; + var height = 16; + var bufferSize = decoder.GetBlockSize(CompressionFormat.Bc6U) * width * height; + + var buffer = new byte[bufferSize]; + Random r = new Random(44); + r.NextBytes(buffer); + + var decoded = decoder.DecodeRaw(buffer, width * 4, height * 4, CompressionFormat.Bc6U, CompressionFormat.RgbaFloat); + Assert.Contains(new ColorRgbaFloat(1, 0, 1), decoded); + + var image = decoded.AsBCnTextureData(width * 4, height * 4, false).AsTexture(); + using var fs = File.OpenWrite("test_decode_bc6h_error.hdr"); + image.WriteToStream(fs); + } + + } +} diff --git a/BCnEncTests/Bc7BlockTests.cs b/BCnEncTests/BlockCompression/Bc7BlockTests.cs similarity index 94% rename from BCnEncTests/Bc7BlockTests.cs rename to BCnEncTests/BlockCompression/Bc7BlockTests.cs index c05fdc6..fe432db 100644 --- a/BCnEncTests/Bc7BlockTests.cs +++ b/BCnEncTests/BlockCompression/Bc7BlockTests.cs @@ -5,11 +5,12 @@ using BCnEncoder.Decoder; using BCnEncoder.ImageSharp; using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; +using BCnEncoder.Shared.Colors; +using BCnEncoder.TextureFormats; using SixLabors.ImageSharp; using Xunit; -namespace BCnEncTests +namespace BCnEncTests.BlockCompression { public class Bc7BlockTests { @@ -21,10 +22,11 @@ public void PackTypes() var outputBlocks = new Bc7Block[64 * numWidthBlocks * numHeightBlocks]; var encoded = new byte[64 * numWidthBlocks * numHeightBlocks * Unsafe.SizeOf()]; - var output = new KtxFile( + var output = new KtxFile(); + output.header = KtxHeader.InitializeCompressed(numWidthBlocks * 8 * 4, numHeightBlocks * 8 * 4, GlInternalFormat.GlCompressedRgbaBptcUnormArb, - GlFormat.GlRgba)); + GlFormat.GlRgba); var type0 = new Span(outputBlocks, 0, 64); Type0Pack(type0); @@ -60,13 +62,13 @@ public void PackTypes() output.MipMaps.Add(new KtxMipmap((uint)encoded.Length, (uint)(8 * 4 * numWidthBlocks), - (uint)(8 * 4 * numHeightBlocks), 1)); - output.MipMaps[0].Faces[0] = new KtxMipFace(encoded, + (uint)(8 * 4 * numHeightBlocks), 1, 1)); + output.MipMaps[0].SurfaceArray[0] = new KtxMipSurface(encoded, (uint)(8 * 4 * numWidthBlocks), (uint)(8 * 4 * numHeightBlocks)); var fs = File.OpenWrite("bc7_blocktests.ktx"); - output.Write(fs); + output.WriteToStream(fs); } [Fact] @@ -82,12 +84,11 @@ public void DecodeErrorBlock() Random r = new Random(50); r.NextBytes(buffer); - var pixels = decoder.DecodeRaw(buffer, width * 4, height * 4, CompressionFormat.Bc7); - var decoded = decoder.DecodeRawToImageRgba32(buffer, width * 4, height * 4, CompressionFormat.Bc7); + var pixels = decoder.DecodeRaw(buffer, width * 4, height * 4, CompressionFormat.Bc7, CompressionFormat.Rgba32); Assert.Contains(new ColorRgba32(255, 0, 255), pixels); using var fs = File.OpenWrite("test_decode_bc7_error.png"); - decoded.SaveAsPng(fs); + pixels.AsBCnTextureData(width, height, true).AsImageRgba32().SaveAsPng(fs); } #region Type Packs diff --git a/BCnEncTests/BlockTests.cs b/BCnEncTests/BlockTests.cs deleted file mode 100644 index b413563..0000000 --- a/BCnEncTests/BlockTests.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using BCnEncoder.Shared; -using Microsoft.Toolkit.HighPerformance; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -namespace BCnEncTests -{ - public class BlockTests - { - [Fact] - public void CreateBlocksExact() - { - var testImage = new ColorRgba32[16, 16]; - - var blocks = ImageToBlocks.ImageTo4X4(testImage, out var blocksWidth, out var blocksHeight); - - Assert.Equal(16, blocks.Length); - Assert.Equal(4, blocksWidth); - Assert.Equal(4, blocksHeight); - } - - [Fact] - public void CreateBlocksPadding() - { - var testImage = new ColorRgba32[15, 11]; - - var blocks = ImageToBlocks.ImageTo4X4(testImage, out var blocksWidth, out var blocksHeight); - - Assert.Equal(12, blocks.Length); - Assert.Equal(3, blocksWidth); - Assert.Equal(4, blocksHeight); - } - - [Fact] - public void PaddingColor() - { - var testImage = new ColorRgba32[15, 11]; - - var pixels = testImage.AsSpan(); - - for (var i = 0; i < pixels.Length; i++) { - pixels[i] = new ColorRgba32(0, 125, 125); - } - - var blocks = ImageToBlocks.ImageTo4X4(testImage, out var blocksWidth, out var blocksHeight); - - Assert.Equal(12, blocks.Length); - Assert.Equal(3, blocksWidth); - Assert.Equal(4, blocksHeight); - - for (var x = 0; x < blocksWidth; x++) { - for (var y = 0; y < blocksHeight; y++) { - foreach (var color in blocks[x + y * blocksWidth].AsSpan) { - Assert.Equal(new ColorRgba32(0, 125, 125), color); - } - } - } - } - - [Fact] - public void BlocksToImage() - { - var r = new Random(0); - var testImage = new ColorRgba32[16, 16]; - - var pixels = testImage.AsSpan(); - - for (var i = 0; i < pixels.Length; i++) { - pixels[i] = new ColorRgba32( - (byte)r.Next(255), - (byte)r.Next(255), - (byte)r.Next(255), - (byte)r.Next(255)); - } - - var blocks = ImageToBlocks.ImageTo4X4(testImage, out var blocksWidth, out var blocksHeight); - - Assert.Equal(16, blocks.Length); - Assert.Equal(4, blocksWidth); - Assert.Equal(4, blocksHeight); - - var output = ImageToBlocks.ColorsFromRawBlocks(blocks, 16, 16); - - var pixels2 = output.AsSpan(); - - Assert.Equal(pixels.Length, pixels2.Length); - for (var i = 0; i < pixels.Length; i++) { - Assert.Equal(pixels[i], pixels2[i]); - } - } - - [Fact] - public void BlockError() - { - var testImage = new ColorRgba32[16, 16]; - - var blocks = ImageToBlocks.ImageTo4X4(testImage, out var blocksWidth, out var blocksHeight); - - var block1 = blocks[2 + 2 * blocksWidth]; - var block2 = blocks[2 + 2 * blocksWidth]; - - Assert.Equal(0, block1.CalculateError(block2)); - - for (var i = 0; i < block2.AsSpan.Length; i++) { - block2.AsSpan[i].r = (byte) (block2.AsSpan[i].r + 2); - } - Assert.Equal(2, block1.CalculateError(block2)); - - for (var i = 0; i < block2.AsSpan.Length; i++) { - block2.AsSpan[i].g = (byte) (block2.AsSpan[i].r + 20); - } - Assert.Equal(22, block1.CalculateError(block2)); - } - } -} diff --git a/BCnEncTests/CancellationTests.cs b/BCnEncTests/CancellationTests.cs deleted file mode 100644 index 07689a7..0000000 --- a/BCnEncTests/CancellationTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Threading; -using BCnEncoder.Decoder; -using BCnEncTests.Support; -using Xunit; - -namespace BCnEncTests -{ - public class CancellationTests - { - [Fact] - public async void EncodeParallelCancellation() - { - await TestHelper.ExecuteCancellationTest(ImageLoader.TestAlphaGradient1, true); - } - - [Fact] - public async void EncodeNonParallelCancellation() - { - await TestHelper.ExecuteCancellationTest(ImageLoader.TestAlphaGradient1, false); - } - - // HINT: Decoding in general is too fast to be cancelled. - // HINT: For parallel decoding even with TimeSpan.FromTicks(1) the test never successfully threw an exception when executed in bulk with other tests. - // HINT: For non parallel decoding the test was partially successful, due to fluctuations in how much time the decoding needed and when the cancellation was introduced. - } -} diff --git a/BCnEncTests/ClusterTests.cs b/BCnEncTests/ClusterTests.cs deleted file mode 100644 index cee9ccd..0000000 --- a/BCnEncTests/ClusterTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.IO; -using System.Runtime.InteropServices; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -namespace BCnEncTests -{ - public class ClusterTests - { - [Fact] - public void Clusterize() - { - var testImage = ImageLoader.TestBlur1.Clone(); - - var pix = TestHelper.GetSinglePixelArray(testImage); - - var pixels = MemoryMarshal.Cast(pix).ToArray(); - - var numClusters = (testImage.Width / 32) * (testImage.Height / 32); - var clusters = LinearClustering.ClusterPixels(pixels, testImage.Width, testImage.Height, numClusters, 10, 10); - - var pixC = new ColorYCbCr[numClusters]; - var counts = new int[numClusters]; - - for (var i = 0; i < pixels.Length; i++) - { - pixC[clusters[i]] += new ColorYCbCr(pixels[i]); - counts[clusters[i]]++; - } - - for (var i = 0; i < numClusters; i++) - { - pixC[i] /= counts[i]; - } - - for (var i = 0; i < pixels.Length; i++) - { - pixels[i] = pixC[clusters[i]].ToColorRgba32(); - pix[i] = new Rgba32(pixels[i].r, pixels[i].g, pixels[i].b, pixels[i].a); - } - - TestHelper.SetSinglePixelArray(testImage, pix); - - using var fs = File.OpenWrite("test_cluster.png"); - testImage.SaveAsPng(fs); - } - } -} diff --git a/BCnEncTests/ColorTest.cs b/BCnEncTests/ColorTest.cs deleted file mode 100644 index eefa12f..0000000 --- a/BCnEncTests/ColorTest.cs +++ /dev/null @@ -1,57 +0,0 @@ -using BCnEncoder.Shared; -using Xunit; - -namespace BCnEncTests -{ - public class ColorTest - { - - [Fact] - public void Rgb565Test() - { - var color = new ColorRgb565(255, 255, 255); - - Assert.Equal(255, color.R); - Assert.Equal(255, color.G); - Assert.Equal(255, color.B); - - color.R = 0; - Assert.Equal(0, color.R); - Assert.Equal(255, color.G); - Assert.Equal(255, color.B); - - color.G = 0; - Assert.Equal(0, color.R); - Assert.Equal(0, color.G); - Assert.Equal(255, color.B); - - color.B = 0; - Assert.Equal(0, color.R); - Assert.Equal(0, color.G); - Assert.Equal(0, color.B); - - color = new ColorRgb565(255, 255, 255); - - color.B = 0; - Assert.Equal(255, color.R); - Assert.Equal(255, color.G); - Assert.Equal(0, color.B); - - color.G = 0; - Assert.Equal(255, color.R); - Assert.Equal(0, color.G); - Assert.Equal(0, color.B); - - color.R = 0; - Assert.Equal(0, color.R); - Assert.Equal(0, color.G); - Assert.Equal(0, color.B); - - color = new ColorRgb565(127, 127, 127); - - Assert.Equal(123, color.R); - Assert.Equal(125, color.G); - Assert.Equal(123, color.B); - } - } -} diff --git a/BCnEncTests/Colors/ColorR10G10B10A2PackedTest.cs b/BCnEncTests/Colors/ColorR10G10B10A2PackedTest.cs new file mode 100644 index 0000000..bd2fbe8 --- /dev/null +++ b/BCnEncTests/Colors/ColorR10G10B10A2PackedTest.cs @@ -0,0 +1,274 @@ +using System; +using BCnEncoder.Shared.Colors; +using Xunit; + +namespace BCnEncTests.Colors +{ + public class ColorR10G10B10A2PackedTest + { + [Fact] + public void AccessComponentValues_HighAndLowBitValues() + { + // Test high values (all bits set) + var colorHigh = new ColorR10G10B10A2Packed(1.0f, 1.0f, 1.0f, 1.0f); + + Assert.Equal(1.0f, colorHigh.R); + Assert.Equal(1.0f, colorHigh.G); + Assert.Equal(1.0f, colorHigh.B); + Assert.Equal(1.0f, colorHigh.A); + + Assert.Equal(1023u, colorHigh.RawR); // 10 bits all set = 1023 + Assert.Equal(1023u, colorHigh.RawG); + Assert.Equal(1023u, colorHigh.RawB); + Assert.Equal(3u, colorHigh.RawA); // 2 bits all set = 3 + + // Test low values (no bits set) + var colorLow = new ColorR10G10B10A2Packed(0.0f, 0.0f, 0.0f, 0.0f); + + Assert.Equal(0.0f, colorLow.R); + Assert.Equal(0.0f, colorLow.G); + Assert.Equal(0.0f, colorLow.B); + Assert.Equal(0.0f, colorLow.A); + + Assert.Equal(0u, colorLow.RawR); + Assert.Equal(0u, colorLow.RawG); + Assert.Equal(0u, colorLow.RawB); + Assert.Equal(0u, colorLow.RawA); + + // Test mid values (roughly half bits set) + var colorMid = new ColorR10G10B10A2Packed(0.5f, 0.5f, 0.5f, 0.5f); + + Assert.Equal(0.5f, colorMid.R, 3); // Allow small floating point error + Assert.Equal(0.5f, colorMid.G, 3); + Assert.Equal(0.5f, colorMid.B, 3); + + // For alpha, with only 2 bits (0-3), a value of 0.5 gets mapped to 1, + // which when converted back is 1/3 or approximately 0.33f + // So we expect either 0.33f (if mapped to 1) or 0.67f (if mapped to 2) + Assert.True(Math.Abs(colorMid.A - 0.33f) < 0.01f || Math.Abs(colorMid.A - 0.67f) < 0.01f, + $"Alpha value {colorMid.A} should be close to either 0.33 or 0.67 due to 2-bit precision"); + + // Floating point imprecision may make these slightly off from exactly 511 (half of 1023) + Assert.InRange(colorMid.RawR, 510u, 512u); + Assert.InRange(colorMid.RawG, 510u, 512u); + Assert.InRange(colorMid.RawB, 510u, 512u); + // With only 2 bits, 0.5 should get mapped to either 1 or 2 + Assert.True(colorMid.RawA == 1u || colorMid.RawA == 2u); + } + + [Fact] + public void SetComponentValues_Individual() + { + // Create a color and modify R, G, B, A individually + var color = new ColorR10G10B10A2Packed(0.0f, 0.0f, 0.0f, 0.0f); + + // Test setting R component + color.R = 1.0f; + Assert.Equal(1.0f, color.R); + Assert.Equal(0.0f, color.G); + Assert.Equal(0.0f, color.B); + Assert.Equal(0.0f, color.A); + Assert.Equal(1023u, color.RawR); + Assert.Equal(0u, color.RawG); + Assert.Equal(0u, color.RawB); + Assert.Equal(0u, color.RawA); + + // Test setting G component + color.G = 0.75f; + Assert.Equal(1.0f, color.R); + Assert.Equal(0.75f, color.G, 3); // Allow small floating point error + Assert.Equal(0.0f, color.B); + Assert.Equal(0.0f, color.A); + Assert.Equal(1023u, color.RawR); + Assert.InRange(color.RawG, 766u, 768u); // ~0.75 * 1023 + Assert.Equal(0u, color.RawB); + Assert.Equal(0u, color.RawA); + + // Test setting B component + color.B = 0.25f; + Assert.Equal(1.0f, color.R); + Assert.Equal(0.75f, color.G, 3); + Assert.Equal(0.25f, color.B, 3); + Assert.Equal(0.0f, color.A); + Assert.Equal(1023u, color.RawR); + Assert.InRange(color.RawG, 766u, 768u); + Assert.InRange(color.RawB, 255u, 257u); // ~0.25 * 1023 + Assert.Equal(0u, color.RawA); + + // Test setting A component + // With only 2 bits of precision, 0.66 should map to 2, which is 2/3 ≈ 0.67 + color.A = 0.66f; + Assert.Equal(1.0f, color.R); + Assert.Equal(0.75f, color.G, 3); + Assert.Equal(0.25f, color.B, 3); + // For 2-bit alpha, 0.66 gets quantized to 2/3 ≈ 0.67 + Assert.Equal(2u, color.RawA); // Should map to 2 out of 3 + Assert.InRange(color.A, 0.65f, 0.68f); // Should be approximately 0.67 + Assert.Equal(1023u, color.RawR); + Assert.InRange(color.RawG, 766u, 768u); + Assert.InRange(color.RawB, 255u, 257u); + } + + [Fact] + public void SetBitMasks_EnsureCorrectBitPositions() + { + // Test that bit masks are working correctly + var color = new ColorR10G10B10A2Packed(0.0f, 0.0f, 0.0f, 0.0f); + + // Set raw values directly to test masks + color.RawR = 1023; // All 10 bits of R set + color.RawG = 0; + color.RawB = 0; + color.RawA = 0; + + // Data should have only R bits set + Assert.Equal(0x000003FFu, color.data); + + // Reset and test G mask + color.RawR = 0; + color.RawG = 1023; // All 10 bits of G set + color.RawB = 0; + color.RawA = 0; + + // Data should have only G bits set + Assert.Equal(0x000FFC00u, color.data); + + // Reset and test B mask + color.RawR = 0; + color.RawG = 0; + color.RawB = 1023; // All 10 bits of B set + color.RawA = 0; + + // Data should have only B bits set + Assert.Equal(0x3FF00000u, color.data); + + // Reset and test A mask + color.RawR = 0; + color.RawG = 0; + color.RawB = 0; + color.RawA = 3; // All 2 bits of A set + + // Data should have only A bits set + Assert.Equal(0xC0000000u, color.data); + + // Test all bits set + color.RawR = 1023; + color.RawG = 1023; + color.RawB = 1023; + color.RawA = 3; + + // All bits should be set + Assert.Equal(0xFFFFFFFFu, color.data); + } + + [Fact] + public void BoundaryValues_ClampNegativeAndOverflow() + { + // Test negative values (should clamp to 0) + var color = new ColorR10G10B10A2Packed(-1.0f, -0.5f, -0.1f, -0.2f); + + Assert.Equal(0.0f, color.R); + Assert.Equal(0.0f, color.G); + Assert.Equal(0.0f, color.B); + Assert.Equal(0.0f, color.A); + + // Test overflow values (should clamp to 1.0) + color = new ColorR10G10B10A2Packed(1.5f, 2.0f, 10.0f, 5.0f); + + Assert.Equal(1.0f, color.R); + Assert.Equal(1.0f, color.G); + Assert.Equal(1.0f, color.B); + Assert.Equal(1.0f, color.A); + + // Test setting raw values beyond limits + color = new ColorR10G10B10A2Packed(0, 0, 0, 0); + color.RawR = 2000; // Beyond 10-bit limit + color.RawG = 5000; // Beyond 10-bit limit + color.RawB = 10000; // Beyond 10-bit limit + color.RawA = 10; // Beyond 2-bit limit + + Assert.Equal(1023u, color.RawR); // Should clamp to 1023 + Assert.Equal(1023u, color.RawG); // Should clamp to 1023 + Assert.Equal(1023u, color.RawB); // Should clamp to 1023 + Assert.Equal(3u, color.RawA); // Should clamp to 3 + } + + [Fact] + public void ColorRgbaFloat_ConversionRoundTrip() + { + // Test conversion from ColorR10G10B10A2 to ColorRgbaFloat and back + var original = new ColorR10G10B10A2Packed(0.1f, 0.5f, 0.75f, 0.66f); + var rgbaFloat = original.ToColorRgbaFloat(); + var roundTrip = rgbaFloat.As(); + + // Compare float values with small tolerance due to precision loss + Assert.Equal(original.R, rgbaFloat.r, 3); + Assert.Equal(original.G, rgbaFloat.g, 3); + Assert.Equal(original.B, rgbaFloat.b, 3); + Assert.Equal(original.A, rgbaFloat.a, 3); + + // Compare original with round-trip values + Assert.Equal(original.R, roundTrip.R, 3); + Assert.Equal(original.G, roundTrip.G, 3); + Assert.Equal(original.B, roundTrip.B, 3); + Assert.Equal(original.A, roundTrip.A, 3); + + // Try with all 1s + original = new ColorR10G10B10A2Packed(1.0f, 1.0f, 1.0f, 1.0f); + rgbaFloat = original.ToColorRgbaFloat(); + roundTrip = rgbaFloat.As(); + + Assert.Equal(1.0f, rgbaFloat.r); + Assert.Equal(1.0f, rgbaFloat.g); + Assert.Equal(1.0f, rgbaFloat.b); + Assert.Equal(1.0f, rgbaFloat.a); + + Assert.Equal(original.R, roundTrip.R); + Assert.Equal(original.G, roundTrip.G); + Assert.Equal(original.B, roundTrip.B); + Assert.Equal(original.A, roundTrip.A); + + // Try with all 0s + original = new ColorR10G10B10A2Packed(0.0f, 0.0f, 0.0f, 0.0f); + rgbaFloat = original.ToColorRgbaFloat(); + roundTrip = rgbaFloat.As(); + + Assert.Equal(0.0f, rgbaFloat.r); + Assert.Equal(0.0f, rgbaFloat.g); + Assert.Equal(0.0f, rgbaFloat.b); + Assert.Equal(0.0f, rgbaFloat.a); + + Assert.Equal(original.R, roundTrip.R); + Assert.Equal(original.G, roundTrip.G); + Assert.Equal(original.B, roundTrip.B); + Assert.Equal(original.A, roundTrip.A); + } + + [Fact] + public void ColorRgba32_ConversionRoundTrip() + { + // Create ColorR10G10B10A2 from ColorRgba32 and convert back + var rgba32 = new ColorRgba32(128, 64, 192, 255); + var r10g10b10a2 = rgba32.As(); + + // Check that values were preserved within reasonable limits + // There will be some precision loss due to converting from 8-bit to 10-bit and back + Assert.InRange(r10g10b10a2.R * 255f, 127, 129); // ~128/255 + Assert.InRange(r10g10b10a2.G * 255f, 63, 65); // ~64/255 + Assert.InRange(r10g10b10a2.B * 255f, 191, 193); // ~192/255 + Assert.InRange(r10g10b10a2.A * 255f, 254, 255); // ~255/255 + + // Convert to ColorRgba32 with the FromColorRgbaFloat method + var roundTrip = new ColorRgba32(); + var rgbaFloat = r10g10b10a2.ToColorRgbaFloat(); + roundTrip.FromColorRgbaFloat(rgbaFloat); + + // Check that values were preserved within reasonable limits + // There will be some precision loss due to the 10-bit to 8-bit conversions + Assert.InRange(roundTrip.r, 127, 129); // ~128 + Assert.InRange(roundTrip.g, 63, 65); // ~64 + Assert.InRange(roundTrip.b, 191, 193); // ~192 + Assert.InRange(roundTrip.a, 254, 255); // ~255 + } + } +} diff --git a/BCnEncTests/Colors/ColorR11G11B10PackedTest.cs b/BCnEncTests/Colors/ColorR11G11B10PackedTest.cs new file mode 100644 index 0000000..d770ac4 --- /dev/null +++ b/BCnEncTests/Colors/ColorR11G11B10PackedTest.cs @@ -0,0 +1,121 @@ +using System; +using BCnEncoder.Shared.Colors; +using Xunit; + +namespace BCnEncTests.Colors +{ + /// + /// Tests for the ColorR11G11B10F format which uses packed float components. + /// + public class ColorR11G11B10PackedUFloatTest + { + [Fact] + public void AccessComponentValues_HighAndLowBitValues() + { + // Test high values (all bits set) + var colorHigh = new ColorR11G11B10PackedUFloat(float.MaxValue, float.MaxValue, float.MaxValue); + + // Values should be clamped to the maximum representable by the format + Assert.True(colorHigh.R > 0); + Assert.True(colorHigh.G > 0); + Assert.True(colorHigh.B > 0); + + Assert.Equal(0x7C0U, colorHigh.RawR); // Exponent all 1s, mantissa 0 + Assert.Equal(0x7C0U, colorHigh.RawG); + Assert.Equal(0x3E0U, colorHigh.RawB); // Exponent all 1s, mantissa 0 + + // Test low values (no bits set) + var colorLow = new ColorR11G11B10PackedUFloat(0.0f, 0.0f, 0.0f); + + Assert.Equal(0.0f, colorLow.R); + Assert.Equal(0.0f, colorLow.G); + Assert.Equal(0.0f, colorLow.B); + + Assert.Equal(0U, colorLow.RawR); + Assert.Equal(0U, colorLow.RawG); + Assert.Equal(0U, colorLow.RawB); + } + + [Fact] + public void TestBitFieldPacking() + { + // Create a color with known bit patterns + var color = new ColorR11G11B10PackedUFloat(0); + color.RawR = 0x123U; // 11-bit value + color.RawG = 0x456U; // 11-bit value + color.RawB = 0x78U; // 10-bit value + + // Verify the bits are in the correct positions + uint expected = 0x123U | (0x456U << 11) | (0x78U << 22); + Assert.Equal(expected, color.data); + + // Verify reading them back works correctly + Assert.Equal(0x123U, color.RawR); + Assert.Equal(0x456U, color.RawG); + Assert.Equal(0x78U, color.RawB); + } + + [Fact] + public void TestConversionToAndFromRgbaFloat() + { + // Start with RGBA + var originalRgba = new ColorRgbaFloat(0.5f, 0.25f, 0.125f); + + // Convert to our format + var packedColor = new ColorR11G11B10PackedUFloat(); + packedColor.FromColorRgbaFloat(originalRgba); + + // Convert back to RGBA + var roundTripRgba = packedColor.ToColorRgbaFloat(); + + // Verify the values are approximately equal (allowing for precision loss) + const float epsilon = 1e-3f; + Assert.True(Math.Abs(originalRgba.r - roundTripRgba.r) < epsilon, $"Red mismatch: {originalRgba.r} vs {roundTripRgba.r}"); + Assert.True(Math.Abs(originalRgba.g - roundTripRgba.g) < epsilon, $"Green mismatch: {originalRgba.g} vs {roundTripRgba.g}"); + Assert.True(Math.Abs(originalRgba.b - roundTripRgba.b) < epsilon, $"Blue mismatch: {originalRgba.b} vs {roundTripRgba.b}"); + } + + [Fact] + public void TestEqualityAndHashCode() + { + var color1 = new ColorR11G11B10PackedUFloat(0.1f, 0.2f, 0.3f); + var color2 = new ColorR11G11B10PackedUFloat(0.1f, 0.2f, 0.3f); + var color3 = new ColorR11G11B10PackedUFloat(0.3f, 0.2f, 0.1f); + + // Test equality + Assert.True(color1.Equals(color2)); + Assert.True(color1 == color2); + Assert.False(color1.Equals(color3)); + Assert.False(color1 == color3); + Assert.True(color1 != color3); + + // Test hash codes + Assert.Equal(color1.GetHashCode(), color2.GetHashCode()); + } + + [Fact] + public void TestNegativeAndSpecialValues() + { + // Test with negative values (should be clamped to 0) + var negativeColor = new ColorR11G11B10PackedUFloat(-1.0f, -2.0f, -3.0f); + Assert.Equal(0.0f, negativeColor.R); + Assert.Equal(0.0f, negativeColor.G); + Assert.Equal(0.0f, negativeColor.B); + + // Test with NaN + var nanColor = new ColorR11G11B10PackedUFloat(float.NaN, float.NaN, float.NaN); + Assert.Equal(float.NaN, nanColor.R); + Assert.Equal(float.NaN, nanColor.G); + Assert.Equal(float.NaN, nanColor.B); + + // Test with infinity + var infColor = new ColorR11G11B10PackedUFloat(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); + Assert.Equal(0x7C0U, infColor.RawR); + Assert.Equal(0x7C0U, infColor.RawG); + Assert.Equal(0x3E0U, infColor.RawB); + Assert.True(float.IsPositiveInfinity(infColor.R)); + Assert.True(float.IsPositiveInfinity(infColor.G)); + Assert.True(float.IsPositiveInfinity(infColor.B)); + } + } +} diff --git a/BCnEncTests/Colors/ColorR9G9B9E5Test.cs b/BCnEncTests/Colors/ColorR9G9B9E5Test.cs new file mode 100644 index 0000000..7c8c979 --- /dev/null +++ b/BCnEncTests/Colors/ColorR9G9B9E5Test.cs @@ -0,0 +1,147 @@ +using System; +using BCnEncoder.Shared.Colors; +using Xunit; + +namespace BCnEncTests.Colors +{ + public class ColorR9G9B9E5Test + { + [Fact] + public void AccessComponentValues_HighAndLowBitValues() + { + // Test high values (all bits set) + var colorHigh = new ColorR9G9B9E5(float.MaxValue, float.MaxValue, float.MaxValue); + + // Extract components to check bit patterns + uint rm = colorHigh.packedValue & 0x1FF; + uint gm = (colorHigh.packedValue >> 9) & 0x1FF; + uint bm = (colorHigh.packedValue >> 18) & 0x1FF; + uint exp = (colorHigh.packedValue >> 27) & 0x1F; + + // For max values, we expect the mantissas to be maximized and exponent to be high + Assert.Equal(0x1FFU, rm); // 9 bits all set = 0x1FF (511) + Assert.Equal(0x1FFU, gm); + Assert.Equal(0x1FFU, bm); + Assert.Equal(31U, exp); // 5 bits all set = 31 + + // Test low values (no bits set) + var colorLow = new ColorR9G9B9E5(0.0f, 0.0f, 0.0f); + Assert.Equal(0U, colorLow.packedValue); + + var rgbaLow = colorLow.ToColorRgbaFloat(); + Assert.Equal(0.0f, rgbaLow.r); + Assert.Equal(0.0f, rgbaLow.g); + Assert.Equal(0.0f, rgbaLow.b); + } + + [Fact] + public void TestSharedExponentPacking() + { + // Test with values that would create a specific known bit pattern + // We'll use values that should produce a simple bit pattern after conversion + + // First, create a color with power-of-2 values that can be exactly represented + var color = new ColorR9G9B9E5(1.0f, 0.5f, 0.25f); + + // Convert to RGBA and back to verify round-trip conversion + var rgba = color.ToColorRgbaFloat(); + var roundTrip = new ColorR9G9B9E5(); + roundTrip.FromColorRgbaFloat(rgba); + + // Values should be very close after round-trip + const float epsilon = 1e-3f; + Assert.True(Math.Abs(1.0f - rgba.r) < epsilon); + Assert.True(Math.Abs(0.5f - rgba.g) < epsilon); + Assert.True(Math.Abs(0.25f - rgba.b) < epsilon); + + // The original and round-trip packed values should be the same or very close + Assert.Equal(color.packedValue, roundTrip.packedValue); + } + + [Fact] + public void TestConversionToAndFromRgbaFloat() + { + // Create a set of test colors + ColorRgbaFloat[] testColors = new [] + { + new ColorRgbaFloat(1.0f, 1.0f, 1.0f), // White + new ColorRgbaFloat(1.0f, 0.0f, 0.0f), // Red + new ColorRgbaFloat(0.0f, 1.0f, 0.0f), // Green + new ColorRgbaFloat(0.0f, 0.0f, 1.0f), // Blue + new ColorRgbaFloat(0.5f, 0.5f, 0.5f), // Gray + new ColorRgbaFloat(0.1f, 0.2f, 0.3f), // Arbitrary color + new ColorRgbaFloat(24f, 32f, 0f), // Large exponent + new ColorRgbaFloat(64f, 0f, 24f), + new ColorRgbaFloat(6f, 0f, 8f), + }; + + foreach (var originalColor in testColors) + { + // Convert to our format + var packedColor = new ColorR9G9B9E5(); + packedColor.FromColorRgbaFloat(originalColor); + + // Convert back to RGBA + var roundTripColor = packedColor.ToColorRgbaFloat(); + + // Verify the values are approximately equal (allowing for precision loss) + const float epsilon = 1e-2f; // Larger epsilon due to shared exponent quantization + Assert.True(Math.Abs(originalColor.r - roundTripColor.r) < epsilon, + $"Red mismatch: {originalColor.r} vs {roundTripColor.r}"); + Assert.True(Math.Abs(originalColor.g - roundTripColor.g) < epsilon, + $"Green mismatch: {originalColor.g} vs {roundTripColor.g}"); + Assert.True(Math.Abs(originalColor.b - roundTripColor.b) < epsilon, + $"Blue mismatch: {originalColor.b} vs {roundTripColor.b}"); + } + } + + [Fact] + public void TestEqualityAndHashCode() + { + var color1 = new ColorR9G9B9E5(0.1f, 0.2f, 0.3f); + var color2 = new ColorR9G9B9E5(0.1f, 0.2f, 0.3f); + var color3 = new ColorR9G9B9E5(0.3f, 0.2f, 0.1f); + + // First, verify we have distinct packed values for color1 and color3 + Assert.NotEqual(color1.packedValue, color3.packedValue); + + // Test equality methods using ColorR9G9B9E5.Equals + Assert.True(color1.Equals(color2)); + Assert.False(color1.Equals(color3)); + + // Test operator overloads + Assert.True(color1 == color2); + Assert.False(color1 == color3); + Assert.True(color1 != color3); + Assert.False(color1 != color2); + + // Test GetHashCode + Assert.Equal(color1.GetHashCode(), color2.GetHashCode()); + Assert.NotEqual(color1.GetHashCode(), color3.GetHashCode()); + } + + [Fact] + public void TestNegativeAndSpecialValues() + { + // Test with negative values (should be clamped to 0) + var negativeColor = new ColorR9G9B9E5(-1.0f, -2.0f, -3.0f); + var rgbaNegative = negativeColor.ToColorRgbaFloat(); + Assert.Equal(0.0f, rgbaNegative.r); + Assert.Equal(0.0f, rgbaNegative.g); + Assert.Equal(0.0f, rgbaNegative.b); + + // Test with NaN (should be treated as 0) + var nanColor = new ColorR9G9B9E5(float.NaN, float.NaN, float.NaN); + var rgbaNan = nanColor.ToColorRgbaFloat(); + Assert.Equal(0.0f, rgbaNan.r); + Assert.Equal(0.0f, rgbaNan.g); + Assert.Equal(0.0f, rgbaNan.b); + + // Test with very large values (should use max representable value) + var largeColor = new ColorR9G9B9E5(float.MaxValue, float.MaxValue, float.MaxValue); + // Extract the exponent - should be the maximum value (31) + uint exp = (largeColor.packedValue >> 27) & 0x1F; + Assert.Equal(31U, exp); + } + } +} diff --git a/BCnEncTests/Colors/ColorSpaceSrgb.cs b/BCnEncTests/Colors/ColorSpaceSrgb.cs new file mode 100644 index 0000000..5e6e3cb --- /dev/null +++ b/BCnEncTests/Colors/ColorSpaceSrgb.cs @@ -0,0 +1,49 @@ +using System; +using System.Numerics; +using BCnEncoder.Shared.Colors; +using Xunit; + +namespace BCnEncTests.Colors; + +public class ColorSpaceSrgb +{ + [Fact] + public void ToSrgb_PerChannelCorrectness() + { + // R in linear region, G in gamma region, B at boundary + var linear = new Vector4(0.002f, 0.5f, 0.0031308f, .5f); + var expected = new Vector4( + 0.002f * 12.92f, // Linear path + 1.055f * MathF.Pow(0.5f, 1f / 2.4f) - 0.055f, // Gamma path + 0.04045f, // Exact threshold boundary + .5f // Preserve alpha + ); + + var result = ColorSpace.Srgb.ToSrgb(linear); + + Assert.Equal(expected.X, result.X, 5); + Assert.Equal(expected.Y, result.Y, 5); + Assert.Equal(expected.Z, result.Z, 5); + Assert.Equal(expected.W, result.W); + } + + [Fact] + public void ToLrgb_PerChannelCorrectness() + { + // R in linear region, G in gamma region, B at boundary + var srgb = new Vector4(0.02f, 0.7353569f, 0.04045f, .5f); + var expected = new Vector4( + 0.02f / 12.92f, // Linear path + MathF.Pow((0.7353569f + 0.055f) / 1.055f, 2.4f), // Gamma path + 0.0031308f, // Exact threshold + .5f // Preserve alpha + ); + + var result = ColorSpace.Srgb.ToLrgb(srgb); + + Assert.Equal(expected.X, result.X, 5); + Assert.Equal(expected.Y, result.Y, 5); + Assert.Equal(expected.Z, result.Z, 5); + Assert.Equal(expected.W, result.W); + } +} diff --git a/BCnEncTests/Colors/ColorTests.cs b/BCnEncTests/Colors/ColorTests.cs new file mode 100644 index 0000000..9fbea74 --- /dev/null +++ b/BCnEncTests/Colors/ColorTests.cs @@ -0,0 +1,800 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Numerics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; +using BCnEncoder.Shared.Colors; +using Xunit; +using Xunit.Abstractions; + +namespace BCnEncTests.Colors +{ + /// + /// Tests for all raw color types, except for shared exponent types. + /// + public class ColorTests + { + private readonly ITestOutputHelper testOutputHelper; + + public ColorTests(ITestOutputHelper testOutputHelper) + { + this.testOutputHelper = testOutputHelper; + } + + #region Type Info and Test Data + + /// + /// Defines the format type of a color. + /// + public enum ColorFormatType + { + Unorm, + Snorm, + Float + } + + /// + /// Stores metadata about a color type for testing purposes. + /// + /// The color type. + /// The raw component type. + public class ColorTypeInfo + where TColor : unmanaged, IColor + where TRaw : unmanaged, INumber + { + public class ChannelInfo + { + /// The number of bits in the color type's components. + public int BitCount { get; init; } + /// The maximum raw value for the color type's components. + public TRaw MaxValue { get; init; } + + /// The minimum raw value for the color type's components (for signed types). + public TRaw MinValue { get; init; } + + /// Tolerance value to use for floating-point comparisons. + public float Tolerance { get; init; } + } + + /// An instance of the color type. + public TColor Instance { get; } + + /// The format type of the color. + public ColorFormatType FormatType { get; } + + /// The number of channels in the color type. + public int ChannelCount { get; } + + public ChannelInfo[] Channels { get; } + + public string Name => typeof(TColor).Name; + + + /// + /// Creates a new ColorTypeInfo instance. + /// + public ColorTypeInfo( + TColor instance, + ColorFormatType formatType, + ChannelInfo[] channelInfos) + { + Instance = instance; + FormatType = formatType; + ChannelCount = channelInfos.Length; + Channels = channelInfos; + } + + public override string ToString() + { + return $"{nameof(Name)}: {Name}, {nameof(FormatType)}: {FormatType}, {nameof(ChannelCount)}: {ChannelCount}"; + } + } + + /// + /// Gets all color types for testing, grouped by format and channel count. + /// + public static IEnumerable GetAllColorTypes() + { + // Single-channel unorm types + yield return CreateUnormInfo([8]); + yield return CreateUnormInfo([16]); + + // Single-channel snorm types + yield return CreateSnormInfo([8]); + yield return CreateSnormInfo([16]); + + // Single-channel float types + yield return CreateFloatInfo(1, Half.MaxValue, Half.MinValue); + yield return CreateFloatInfo(1, float.MaxValue, float.MinValue); + + // Dual-channel types + yield return CreateUnormInfo([4, 4]); + yield return CreateUnormInfo([8, 8]); + yield return CreateSnormInfo([8, 8]); + yield return CreateUnormInfo([16, 16]); + yield return CreateSnormInfo([16, 16]); + yield return CreateFloatInfo(2, Half.MaxValue, Half.MinValue); + yield return CreateFloatInfo(2, float.MaxValue, float.MinValue); + + // Triple-channel (RGB) unorm types + yield return CreateUnormInfo([5, 6, 5]); + yield return CreateUnormInfo([5, 6, 5]); + yield return CreateUnormInfo([5, 5, 5]); + yield return CreateUnormInfo([16, 16, 16]); + yield return CreateUnormInfo([8, 8, 8]); + yield return CreateUnormInfo([8, 8, 8]); + + // Triple-channel (RGB) float types + yield return CreateFloatInfo(3, Half.MaxValue, Half.MinValue, tolerance: 0.0002f); + yield return CreateFloatInfo(3, float.MaxValue, float.MinValue); + + // Quad-channel (RGBA) unorm types with 4 bits per channel + yield return CreateUnormInfo([4, 4, 4, 4]); + yield return CreateUnormInfo([4, 4, 4, 4]); + yield return CreateUnormInfo([4, 4, 4, 4]); + yield return CreateUnormInfo([4, 4, 4, 4]); + + // Quad-channel (RGBA) unorm types with 5-5-5-1 bit patterns + yield return CreateUnormInfo([5, 5, 5, 1]); + yield return CreateUnormInfo([5, 5, 5, 1]); + yield return CreateUnormInfo([5, 5, 5, 1]); + yield return CreateUnormInfo([5, 5, 5, 1]); + + // Quad-channel (RGBA) unorm types with 8 bits per channel + yield return CreateUnormInfo([8, 8, 8, 8]); + yield return CreateUnormInfo([8, 8, 8, 8]); + + // Quad-channel (RGBA) unorm types with 10-10-10-2 bit pattern + yield return CreateUnormInfo([10, 10, 10, 2]); + yield return CreateUnormInfo([10, 10, 10, 2]); + + // Quad-channel (RGBA) unorm other + yield return CreateUnormInfo([16, 16, 16, 16]); + + // Quad-channel (RGBA) float types + yield return CreateFloatInfo(4, Half.MaxValue, Half.MinValue, tolerance: 0.0002f); + yield return CreateFloatInfo(4, float.MaxValue, float.MinValue); + } + + public static IEnumerable GetAllPackedColorTypes() + { + foreach (var colorType in GetAllColorTypes()) + { + var interfaceType = colorType[0].GetType().GetGenericArguments()[0] // From ColorTypeInfo + .GetInterfaces() + .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IColorPacked<>)); + + if (interfaceType != null) + { + var packedType = interfaceType.GetGenericArguments()[0]; + object packedInstance = Activator.CreateInstance(packedType); + + yield return [colorType[0], packedInstance]; + } + } + } + + /// + /// Helper method to create ColorTypeInfo for unsigned normalized color types. + /// + private static object[] CreateUnormInfo(int[] channelBits) + where TColor : unmanaged, IColor + where TRaw : unmanaged, INumber + { + ColorTypeInfo.ChannelInfo[] channels = new ColorTypeInfo.ChannelInfo[channelBits.Length]; + + for (int i = 0; i < channelBits.Length; i++) + { + TRaw maxValue = TRaw.CreateChecked((1 << channelBits[i]) - 1); + TRaw minValue = TRaw.Zero; + float rangeFloat = float.CreateChecked(long.CreateChecked(maxValue) - long.CreateChecked(minValue)); + + channels[i] = new ColorTypeInfo.ChannelInfo() + { + BitCount = channelBits[i], + MaxValue = maxValue, + MinValue = minValue, + Tolerance = 1 / rangeFloat, + }; + } + + return [new ColorTypeInfo( + new TColor(), + ColorFormatType.Unorm, + channels + )]; + } + + /// + /// Helper method to create ColorTypeInfo for signed normalized color types. + /// + private static object[] CreateSnormInfo(int[] channelBits) + where TColor : unmanaged, IColor + where TRaw : unmanaged, INumber + { + ColorTypeInfo.ChannelInfo[] channels = new ColorTypeInfo.ChannelInfo[channelBits.Length]; + + for (int i = 0; i < channelBits.Length; i++) + { + // For SNORM, max is 2^(bits-1)-1, min is -max + TRaw maxValue = TRaw.CreateChecked((1 << (channelBits[i] - 1)) - 1); + TRaw minValue = TRaw.CreateChecked(-maxValue); + float rangeFloat = float.CreateChecked(long.CreateChecked(maxValue) - long.CreateChecked(minValue)); + + channels[i] = new ColorTypeInfo.ChannelInfo() + { + BitCount = channelBits[i], + MaxValue = maxValue, + MinValue = minValue, + Tolerance = 1 / rangeFloat, + }; + } + + return [new ColorTypeInfo( + new TColor(), + ColorFormatType.Snorm, + channels + )]; + } + + /// + /// Helper method to create ColorTypeInfo for floating point color types. + /// + private static object[] CreateFloatInfo(int numChannels, TRaw maxValue, TRaw minValue, float tolerance = 0.0001f) + where TColor : unmanaged, IColor + where TRaw : unmanaged, INumber + { + ColorTypeInfo.ChannelInfo[] channels = new ColorTypeInfo.ChannelInfo[numChannels]; + + var bitCount = 8 * Unsafe.SizeOf(); + + for (int i = 0; i < numChannels; i++) + { + channels[i] = new ColorTypeInfo.ChannelInfo() + { + BitCount = bitCount, + MaxValue = maxValue, + MinValue = minValue, + Tolerance = tolerance, + }; + } + + return [new ColorTypeInfo( + new TColor(), + ColorFormatType.Float, + channels + )]; + } + + #endregion + + #region Component Test Helpers + + /// + /// Defines test operations for a specific component of a color type. + /// + /// The color type. + /// The raw component type. + private class ComponentTestHelper( + string componentName, + ColorTypeInfo.ChannelInfo channelInfo, + Func getFloatComponent, + Func setFloatComponent, + Func getRawComponent, + Func setRawComponent) + where TColor : unmanaged, IColor + where TRaw : unmanaged, INumber + { + public string ComponentName => componentName; + public ColorTypeInfo.ChannelInfo ChannelInfo => channelInfo; + + public float GetValue(TColor color) => getFloatComponent(color); + + public TColor SetValue(TColor color, float value) => setFloatComponent(color, value); + + public TRaw GetRawValue(TColor color) => getRawComponent(color); + + public TColor SetRawValue(TColor color, TRaw value) => setRawComponent(color, value); + } + + private IEnumerable> GetComponents(ColorTypeInfo typeInfo) + where TColor : unmanaged, IColor + where TRaw : unmanaged, INumber + { + if (typeof(IColorRed).IsAssignableFrom(typeof(TColor))) + { + yield return new ComponentTestHelper( + "R", + typeInfo.Channels[0], + color => ((IColorRed)color).R, + (color, value) => + { + var c = (IColorRed)color; + c.R = value; + return (TColor)c; + }, + color => ((IColorRed)color).RawR, + (color, value) => { + var c = (IColorRed)color; + c.RawR = value; + return (TColor)c; + } + ); + } + + if (typeof(IColorRedGreen).IsAssignableFrom(typeof(TColor))) + { + yield return new ComponentTestHelper( + "G", + typeInfo.Channels[1], + color => ((IColorRedGreen)color).G, + (color, value) => + { + var c = (IColorRedGreen)color; + c.G = value; + return (TColor)c; + }, + color => ((IColorRedGreen)color).RawG, + (color, value) => { + var c = (IColorRedGreen)color; + c.RawG = value; + return (TColor)c; + } + ); + } + + if (typeof(IColorRgb).IsAssignableFrom(typeof(TColor))) + { + yield return new ComponentTestHelper( + "B", + typeInfo.Channels[2], + color => ((IColorRgb)color).B, + (color, value) => + { + var c = (IColorRgb)color; + c.B = value; + return (TColor)c; + }, + color => ((IColorRgb)color).RawB, + (color, value) => { + var c = (IColorRgb)color; + c.RawB = value; + return (TColor)c; + } + ); + } + + if (typeof(IColorRgba).IsAssignableFrom(typeof(TColor))) + { + yield return new ComponentTestHelper( + "A", + typeInfo.Channels[3], + color => ((IColorRgba)color).A, + (color, value) => + { + var c = (IColorRgba)color; + c.A = value; + return (TColor)c; + }, + color => ((IColorRgba)color).RawA, + (color, value) => { + var c = (IColorRgba)color; + c.RawA = value; + return (TColor)c; + } + ); + } + } + + #endregion + + [Theory] + [MemberData(nameof(GetAllColorTypes))] + public void TestComponentGetters(ColorTypeInfo typeInfo) + where TColor : unmanaged, IColor, IEquatable + where TRaw : unmanaged, INumber + { + var components = GetComponents(typeInfo).ToArray(); + + Assert.True(components.Length > 0, "Color type must have at least one component."); + Assert.Equal(typeInfo.ChannelCount, components.Length); + } + + [Theory] + [MemberData(nameof(GetAllColorTypes))] + public void TestSize(ColorTypeInfo typeInfo) + where TColor : unmanaged, IColor, IEquatable + where TRaw : unmanaged, INumber + { + var components = GetComponents(typeInfo).ToArray(); + + var expectedSize = (components.Sum(c => c.ChannelInfo.BitCount) + 7) / 8; + + Assert.Equal(expectedSize, Unsafe.SizeOf()); + } + + [Theory] + [MemberData(nameof(GetAllColorTypes))] + public void TestName(ColorTypeInfo typeInfo) + where TColor : unmanaged, IColor, IEquatable + where TRaw : unmanaged, INumber + { + var components = GetComponents(typeInfo).ToArray(); + + // Pattern to match color channels from the type name. + // E.g. "ColorR10G10B10A2Packed" will match "R", "G", "B", and "A". + // "ColorRgb24 will match "Rgb" + // Etc. + Regex regex = new Regex(@"(Rgba|Rgb|Bgra|Bgr|R|G|B|A(?=\d|Float|Half))", RegexOptions.Compiled); // Match both combined channel names and individual channels + + List channels = new List(); + + var matches = regex.Matches(typeInfo.Name); + + foreach (Match match in matches) + { + foreach (char c in match.Value) + { + channels.Add(Char.ToUpperInvariant(c)); + } + } + + Assert.True(channels.Count > 0, "Color type must have at least one component."); + Assert.Equivalent(channels, components.Select(c => c.ComponentName[0]), true); + } + + [Theory] + [MemberData(nameof(GetAllColorTypes))] + public void TestValueClamping(ColorTypeInfo typeInfo) + where TColor : unmanaged, IColor, IEquatable + where TRaw : unmanaged, INumber + { + bool isFloatType = typeInfo.FormatType == ColorFormatType.Float; + + // For each component in the color type + foreach (var component in GetComponents(typeInfo)) + { + var channelInfo = component.ChannelInfo; + + // Test clamping of values > 1.0 to 1.0 + TColor colorHigh = new TColor(); + const float highValue = 2f; + + float expectedHigh = typeInfo.FormatType switch + { + ColorFormatType.Unorm => 1.0f, + ColorFormatType.Snorm => 1.0f, + ColorFormatType.Float => highValue + }; + + colorHigh = component.SetValue(colorHigh, highValue); + + // Check that high float value is properly clamped to 1.0 + Assert.Equal(expectedHigh, component.GetValue(colorHigh), channelInfo.Tolerance); + + // Don't check raw value for float types + if (!isFloatType) + { + Assert.Equal(channelInfo.MaxValue, component.GetRawValue(colorHigh)); + } + + // Test clamping of values < 0.0 to 0.0 + TColor colorLow = new TColor(); + const float lowValue = -2f; + + colorLow = component.SetValue(colorLow, lowValue); + + float expected = typeInfo.FormatType switch + { + ColorFormatType.Unorm => 0.0f, + ColorFormatType.Snorm => -1.0f, + ColorFormatType.Float => lowValue + }; + + Assert.Equal(expected, component.GetValue(colorLow), channelInfo.Tolerance); + + // Don't check raw value for float types + if (!isFloatType) + { + Assert.Equal(channelInfo.MinValue, component.GetRawValue(colorLow)); + } + } + } + + [Theory] + [MemberData(nameof(GetAllColorTypes))] + public void TestValueRounding(ColorTypeInfo typeInfo) + where TColor : unmanaged, IColor, IEquatable + where TRaw : unmanaged, INumber + { + if (typeInfo.FormatType == ColorFormatType.Float) + return; + + // For each component in the color type + foreach (var component in GetComponents(typeInfo)) + { + var channelInfo = component.ChannelInfo; + + double start = typeInfo.FormatType == ColorFormatType.Unorm ? 0.0 : -1.0; + double interval = typeInfo.FormatType == ColorFormatType.Unorm ? + 1.0 / ((1 << channelInfo.BitCount) - 1) : + 1.0 / ((1 << (channelInfo.BitCount - 1)) - 1); + + for (double value = start; value <= 1.0f; value += interval) + { + TColor color = new TColor(); + color = component.SetValue(color, (float)value); + Assert.Equal(value, component.GetValue(color), 0.000001); + + TColor colorPlus = new TColor(); + + colorPlus = component.SetValue(colorPlus, (float)(value + interval * 0.49)); + Assert.Equal(value, component.GetValue(colorPlus), 0.000001); + + TColor colorMinus = new TColor(); + + colorMinus = component.SetValue(colorMinus, (float)(value - interval * 0.49)); + Assert.Equal(value, component.GetValue(colorMinus), 0.000001); + } + } + } + + /// + /// Test component accessors for all color types. + /// + [Theory] + [MemberData(nameof(GetAllColorTypes))] + public void TestComponentAccessors(ColorTypeInfo typeInfo) + where TColor : unmanaged, IColor, IEquatable + where TRaw : unmanaged, INumber + { + ColorRgbaFloat expectedColor = new ColorRgbaFloat(0.2f, 0.6f, 0.7f, 0.8f); + + TColor color = new TColor(); + + foreach (var componentTest in GetComponents(typeInfo)) + { + float valueToSet = componentTest.ComponentName switch + { + "R" => expectedColor.R, + "G" => expectedColor.G, + "B" => expectedColor.B, + "A" => expectedColor.A + }; + + color = componentTest.SetValue(color, valueToSet); + } + + foreach (var componentTest in GetComponents(typeInfo)) + { + float valueToTest = componentTest.ComponentName switch + { + "R" => expectedColor.R, + "G" => expectedColor.G, + "B" => expectedColor.B, + "A" => expectedColor.A + }; + + Assert.Equal(valueToTest, componentTest.GetValue(color), componentTest.ChannelInfo.Tolerance); + } + } + + /// + /// Tests conversion to and from ColorRgbaFloat for all color types. + /// + [Theory] + [MemberData(nameof(GetAllColorTypes))] + public void TestConversionToAndFromRgbaFloat(ColorTypeInfo typeInfo) + where TColor : unmanaged, IColor, IEquatable + where TRaw : unmanaged, INumber + { + testOutputHelper.WriteLine($"Testing conversion to/from ColorRgbaFloat for {typeInfo.Name}"); + + // Create a color with values in each available component + TColor originalColor = new TColor(); + + int i = 0; + // For each component, set a value based on format type + foreach (var componentTest in GetComponents(typeInfo)) + { + float componentValue = 0.0f; + + switch (typeInfo.FormatType) + { + case ColorFormatType.Unorm: + componentValue = 0.5f + (i * .1f); // Use 0.5 + (i * .1) for unorm + break; + case ColorFormatType.Snorm: + componentValue = -0.5f + (i * .1f); // Use -0.5 + (i * .1) for snorm + break; + case ColorFormatType.Float: + componentValue = 1.25f + (i * .5f); // Use 1.25 + (i * .5) for float + break; + } + + originalColor = componentTest.SetValue(originalColor, componentValue); + + i++; + } + + // Convert to ColorRgbaFloat + ColorRgbaFloat rgbaFloat = originalColor.ToColorRgbaFloat(); + + // Log the conversion results + testOutputHelper.WriteLine($"Original: {originalColor}, Converted to RGBA Float: {rgbaFloat}"); + + // Convert back to original type + TColor roundTripColor = new TColor(); + roundTripColor.FromColorRgbaFloat(rgbaFloat); + + // Check that the round-trip conversion preserves values accurately + // For floating-point formats, we need exact equality + if (typeInfo.FormatType == ColorFormatType.Float) + { + // For float formats, each component should match exactly within tolerance + foreach (var componentTest in GetComponents(typeInfo)) + { + var channelInfo = componentTest.ChannelInfo; + + float originalValue = componentTest.GetValue(originalColor); + float roundTripValue = componentTest.GetValue(roundTripColor); + + testOutputHelper.WriteLine($"Component {componentTest.ComponentName}: Original={originalValue}, RoundTrip={roundTripValue}"); + Assert.Equal(originalValue, roundTripValue, channelInfo.Tolerance); + } + } + else + { + // For unorm/snorm formats, we can compare the entire struct for equality + Assert.Equal(originalColor, roundTripColor); + } + } + + /// + /// Tests equality operations and hash code consistency for all color types. + /// + [Theory] + [MemberData(nameof(GetAllColorTypes))] + public void TestEqualityAndHashCode(ColorTypeInfo typeInfo) + where TColor : unmanaged, IColor, IEquatable + where TRaw : unmanaged, INumber + { + testOutputHelper.WriteLine($"Testing equality and hash code for {typeInfo.Name}"); + + // Create two identical colors and one different color + TColor color1 = new TColor(); + TColor color2 = new TColor(); + TColor colorDifferent = new TColor(); + TColor zeroColor = new TColor(); + + // Set different component values based on the available components + foreach (var component in GetComponents(typeInfo)) + { + TColor color1OneComponent = new TColor(); + TColor color2OneComponent = new TColor(); + TColor colorDifferentOneComponent = new TColor(); + + float componentValue = 0.0f; + + switch (typeInfo.FormatType) + { + case ColorFormatType.Unorm: + componentValue = 0.75f; // Use 0.75 for unorm + break; + case ColorFormatType.Snorm: + componentValue = -0.5f; // Use -0.5 for snorm + break; + case ColorFormatType.Float: + componentValue = 1.25f; // Use 1.25 for float + break; + } + + // Set component values + zeroColor = component.SetValue(zeroColor, 0.0f); + color1 = component.SetValue(color1, componentValue); + color2 = component.SetValue(color2, componentValue); + color1OneComponent = component.SetValue(color1OneComponent, componentValue); + color2OneComponent = component.SetValue(color2OneComponent, componentValue); + + // For colorDifferent, use 0.25 + colorDifferent = component.SetValue(color1, 0.25f); + colorDifferentOneComponent = component.SetValue(colorDifferentOneComponent, 0.25f); + + // 1. IEquatable.Equals + Assert.True(color1OneComponent.Equals(color2OneComponent), + $"IEquatable.Equals should return true for identical colors. Color1: {color1OneComponent}, Color2: {color2OneComponent}"); + Assert.False(color1OneComponent.Equals(colorDifferentOneComponent), + $"IEquatable.Equals should return false for different colors. Color1: {color1OneComponent}, ColorDifferent: {colorDifferentOneComponent}"); + + // 2. Object.Equals override + Assert.True(color1OneComponent.Equals((object)color2OneComponent), + $"Object.Equals should return true for identical colors. Color1: {color1OneComponent}, Color2: {color2OneComponent}"); + Assert.False(color1OneComponent.Equals((object)colorDifferentOneComponent), + $"Object.Equals should return false for different colors. Color1: {color1OneComponent}, ColorDifferent: {colorDifferentOneComponent}"); + + // 3. Hash code consistency + Assert.Equal(color1OneComponent.GetHashCode(), color2OneComponent.GetHashCode()); + Assert.NotEqual(color1OneComponent.GetHashCode(), colorDifferentOneComponent.GetHashCode()); + } + + // Test equality methods + + // 1. IEquatable.Equals + Assert.True(color1.Equals(color2), + $"IEquatable.Equals should return true for identical colors. Color1: {color1}, Color2: {color2}"); + Assert.False(color1.Equals(colorDifferent), + $"IEquatable.Equals should return false for different colors. Color1: {color1}, ColorDifferent: {colorDifferent}"); + + // 2. Object.Equals override + Assert.True(color1.Equals((object)color2), + $"Object.Equals should return true for identical colors. Color1: {color1}, Color2: {color2}"); + Assert.False(color1.Equals((object)colorDifferent), + $"Object.Equals should return false for different colors. Color1: {color1}, ColorDifferent: {colorDifferent}"); + Assert.False(color1.Equals(null), + "Object.Equals should return false for null"); + + // 3. Hash code consistency + Assert.Equal(color1.GetHashCode(), color2.GetHashCode()); + Assert.NotEqual(color1.GetHashCode(), colorDifferent.GetHashCode()); + + // 4. Test with default color + var defaultColor = default(TColor); + + // For colors default and zero-initialized should be equal + Assert.Equal(defaultColor, zeroColor); + Assert.Equal(defaultColor.GetHashCode(), zeroColor.GetHashCode()); + } + + + [Theory] + [MemberData(nameof(GetAllPackedColorTypes))] + public void TestPackedColors(ColorTypeInfo typeInfo, TPacked _) + where TColor : unmanaged, IColor, IEquatable, IColorPacked + where TRaw : unmanaged, INumber + where TPacked : unmanaged, INumber, IBinaryInteger + { + testOutputHelper.WriteLine($"Testing packed color operations for {typeInfo.Name}"); + + PackedRgbaMask channelMask = TColor.ChannelMask; + // Check that channel mask is a subset of the color type name + Assert.Contains(channelMask.ToString(), typeInfo.Name, StringComparison.OrdinalIgnoreCase); + + TColor colorFull = new TColor(); + + colorFull.Data = TPacked.CreateTruncating((ulong)0xFFFF_FFFF_FFFF_FFFF); + + var components = GetComponents(typeInfo).ToArray(); + var redComponent = components.FirstOrDefault(c => c.ComponentName == "R"); + var greenComponent = components.FirstOrDefault(c => c.ComponentName == "G"); + var blueComponent = components.FirstOrDefault(c => c.ComponentName == "B"); + var alphaComponent = components.FirstOrDefault(c => c.ComponentName == "A"); + + // Should have at least red component + Assert.NotNull(redComponent); + + Assert.Equal(channelMask.HasRed, redComponent != null); + Assert.Equal(channelMask.HasGreen, greenComponent != null); + Assert.Equal(channelMask.HasBlue, blueComponent != null); + Assert.Equal(channelMask.HasAlpha, alphaComponent != null); + + if (channelMask.HasRed) + { + TColor fullNoRed = colorFull; + Assert.Equal(channelMask.RedMaxValue, ulong.CreateTruncating(redComponent.GetRawValue(fullNoRed))); + Assert.Equal(channelMask.GetRed(fullNoRed.Data), TPacked.CreateTruncating(redComponent.GetRawValue(fullNoRed))); + + fullNoRed = redComponent.SetValue(fullNoRed, 0f); + + TPacked rMask = TPacked.CreateTruncating(channelMask.RedMask); + TPacked nRMask = ~rMask; + + Assert.Equal(0UL, ulong.CreateTruncating(fullNoRed.Data & rMask)); + Assert.Equal(nRMask, fullNoRed.Data); + } + } + + } +} diff --git a/BCnEncTests/Colors/ColorUtilTests.cs b/BCnEncTests/Colors/ColorUtilTests.cs new file mode 100644 index 0000000..78049c2 --- /dev/null +++ b/BCnEncTests/Colors/ColorUtilTests.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Text; +using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; +using BCnEncoder.TextureFormats; +using Xunit; +using Xunit.Abstractions; + +namespace BCnEncTests.Colors +{ + public class ColorUtilTests + { + private ITestOutputHelper output; + + public ColorUtilTests(ITestOutputHelper output) + { + this.output = output; + } + + [Theory] + [InlineData(0.1f, 0.4f, 0.3f, 9, 5, 15, 31, 2)] + [InlineData(1f, 0.4f, 0.1f, 9, 5, 15, 31, 1)] + [InlineData(3f, 1f, 1.5f, 9, 5, 15, 31, 1)] + [InlineData(0.002f, 0.01f, 0.05f, 9, 5, 15, 31, 3)] + [InlineData(100f, 10f, 30f, 9, 5, 15, 31, 1)] + public void TestFloatToSharedExponent( + float r, float g, float b, + int mantissaBits, int exponentBits, int exponentBias, int exponentMax, + int testPrecision) + { + var testFloat = new ColorRgbaFloat(r, g, b); + var shared = ColorUtils.RgbToSharedExponent(r, g, b, mantissaBits, exponentBias, exponentMax); + + Assert.Equal(shared.exponent & ((1 << exponentBits) - 1), shared.exponent); + Assert.Equal(shared.red & ((1 << mantissaBits) - 1), shared.red); + Assert.Equal(shared.green & ((1 << mantissaBits) - 1), shared.green); + Assert.Equal(shared.blue & ((1 << mantissaBits) - 1), shared.blue); + + var outputFloat = ColorUtils.SharedExponentToRgb(shared, mantissaBits, exponentBias); + + Assert.Equal(testFloat.r, outputFloat.red, testPrecision); + Assert.Equal(testFloat.g, outputFloat.green, testPrecision); + Assert.Equal(testFloat.b, outputFloat.blue, testPrecision); + } + + + [Theory] + [InlineData(0.1f, 0.4f, 0.3f, .01f)] + [InlineData(1f, 0.4f, 0.1f, .05f)] + [InlineData(3f, 1f, 1.5f, .1f)] + [InlineData(0.002f, 0.01f, 0.05f, .001f)] + [InlineData(100f, 10f, 30f, 1)] + public void TestSharedExponentRgbe( + float r, float g, float b, + float resultWithin) + { + var testFloat = new ColorRgbaFloat(r, g, b); + + var shared = ColorUtils.RgbToSharedExponent(r, g, b, + 8, 128, 255); + + Assert.Equal(shared.exponent & 255, shared.exponent); + Assert.Equal(shared.red & 255, shared.red); + Assert.Equal(shared.green & 255, shared.green); + Assert.Equal(shared.blue & 255, shared.blue); + + var rgbe = new ColorRgbe((byte)shared.red, (byte)shared.green, (byte)shared.blue, (byte)shared.exponent); + + var outputFloat = rgbe.ToColorRgbaFloat(); + + output.WriteLine($"Original: {new ColorRgbaFloat(r, g, b)}"); + output.WriteLine($"Output : {outputFloat}"); + + Assert.True(MathF.Abs(r - outputFloat.r) < resultWithin, $"R Expected {r}, got {outputFloat.r}"); + Assert.True(MathF.Abs(g - outputFloat.g) < resultWithin, $"G Expected {g}, got {outputFloat.g}"); + Assert.True(MathF.Abs(b - outputFloat.b) < resultWithin, $"B Expected {b}, got {outputFloat.b}"); + } + } +} diff --git a/BCnEncTests/Colors/FloatingPointConversionTests.cs b/BCnEncTests/Colors/FloatingPointConversionTests.cs new file mode 100644 index 0000000..a5b7d5d --- /dev/null +++ b/BCnEncTests/Colors/FloatingPointConversionTests.cs @@ -0,0 +1,214 @@ +using System; +using BCnEncoder.Shared.Colors; +using Xunit; +using Xunit.Abstractions; + +namespace BCnEncTests.Colors +{ + /// + /// Tests conformance to the Khronos Data Format Specification for + /// 11-bit and 10-bit floating point formats. + /// See: https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#_packed_formats + /// + public class FloatingPointConversionTests + { + private readonly ITestOutputHelper output; + + public FloatingPointConversionTests(ITestOutputHelper output) + { + this.output = output; + } + + private float ParseFloat11(uint bits) + { + bits = bits & 0x7FF; + int exponent = (int)((bits >> 6) & 0x1F); + int testExponent = (int)(bits / 64); + int mantissa = (int)(bits & 0x3F); + int testMantissa = (int)(bits % 64); + + Assert.Equal(exponent, testExponent); + Assert.Equal(mantissa, testMantissa); + + if (exponent == 0 && mantissa == 0) + return 0.0f; + if (exponent == 0 && mantissa != 0) + return (float)(Math.Pow(2, -14) * (mantissa / 64.0)); + if (exponent > 0 && exponent < 31) + return (float)(Math.Pow(2, exponent - 15) * (1.0 + mantissa / 64.0)); + if (exponent == 31 && mantissa == 0) + return float.PositiveInfinity; + return float.NaN; + } + + private float ParseFloat10(uint bits) + { + bits = bits & 0x3FF; + int exponent = (int)((bits >> 5) & 0x1F); + int testExponent = (int)(bits / 32); + int mantissa = (int)(bits & 0x1F); + int testMantissa = (int)(bits % 32); + + Assert.Equal(exponent, testExponent); + Assert.Equal(mantissa, testMantissa); + + if (exponent == 0 && mantissa == 0) + return 0.0f; + if (exponent == 0 && mantissa != 0) + return (float)(Math.Pow(2, -14) * (mantissa / 32.0)); + if (exponent > 0 && exponent < 31) + return (float)(Math.Pow(2, exponent - 15) * (1.0 + mantissa / 32.0)); + if (exponent == 31 && mantissa == 0) + return float.PositiveInfinity; + return float.NaN; + } + + [Fact] + public void Float11RoundingTest() + { + for (float expected = 0.001f; expected <= 1000f; expected *= 1.5f) + { + uint bits = ColorBitConversionHelpers.FloatToFloat11(expected); + float value = ColorBitConversionHelpers.Float11ToFloat(bits); + float reference = ParseFloat11(bits); // Khronos reference + + uint exponent = (bits >> 6) & 0x1F; + uint mantissa = bits & 0x3F; + + double allowedError = 0.000001 * Math.Pow(2, exponent); + + output.WriteLine($"Expected: {expected}, Actual: {value}, Exponent: {exponent}, Mantissa: {mantissa}, AllowedError: {allowedError}"); + Assert.Equal(expected, value, allowedError); + Assert.Equal(expected, reference, allowedError); + } + } + + [Fact] + public void Float10RoundingTest() + { + for (float expected = 0.001f; expected <= 1000f; expected *= 1.5f) + { + uint bits = ColorBitConversionHelpers.FloatToFloat10(expected); + float value = ColorBitConversionHelpers.Float10ToFloat(bits); + float reference = ParseFloat10(bits); // Khronos reference + + uint exponent = (bits >> 5) & 0x1F; + uint mantissa = bits & 0x1F; + + double allowedError = 0.000001 * Math.Pow(2, exponent); + + output.WriteLine($"Expected: {expected}, Actual: {value}, Reference: {reference}, Exponent: {exponent}, Mantissa: {mantissa}, AllowedError: {allowedError}"); + Assert.Equal(expected, value, allowedError); + Assert.Equal(expected, reference, allowedError); + } + } + + [Fact] + public void Float11Test() + { + // Go through all possible exponents + for (uint exponent = 0; exponent <= 31; exponent++) + { + // Go through all possible mantissas + for (uint mantissa = 0; mantissa <= 63; mantissa++) + { + double allowedError = 0.00001 * Math.Pow(2, exponent); + + uint bits = (exponent << 6) | mantissa; + float value = ColorBitConversionHelpers.Float11ToFloat(bits); + float expected = ParseFloat11(bits); + + output.WriteLine($"Expected: {expected}, Actual: {value}, Exponent: {exponent}, Mantissa: {mantissa}, AllowedError: {allowedError}"); + Assert.Equal(expected, value, allowedError); + + // Verify round-trip + uint roundTrip = ColorBitConversionHelpers.FloatToFloat11(value); + + if (exponent < 31) + { + Assert.True(bits == roundTrip, + $"Expected: 0x{bits:x8}, RoundTrip: 0x{roundTrip:x8}, Exponent: {exponent}, Mantissa: {mantissa}"); + }else if (exponent == 31 && mantissa == 0) + { + Assert.Equal((exponent << 6), roundTrip); + } + else + { + Assert.Equal((exponent << 6) | 1, roundTrip); + } + } + } + } + + [Fact] + public void Float10Test() + { + // Go through all possible exponents + for (uint exponent = 0; exponent <= 31; exponent++) + { + // Go through all possible mantissas + for (uint mantissa = 0; mantissa <= 31; mantissa++) + { + double allowedError = 0.00001 * Math.Pow(2, exponent); + uint bits = (exponent << 5) | mantissa; + float value = ColorBitConversionHelpers.Float10ToFloat(bits); + float expected = ParseFloat10(bits); + + output.WriteLine($"Expected: {expected}, Actual: {value}, Exponent: {exponent}, Mantissa: {mantissa}, AllowedError: {allowedError}"); + Assert.Equal(expected, value, allowedError); + + uint roundTrip = ColorBitConversionHelpers.FloatToFloat10(value); + // Verify round-trip + if (exponent < 31) + { + Assert.True(bits == roundTrip, + $"Expected: 0x{bits:x8}, RoundTrip: 0x{roundTrip:x8}, Exponent: {exponent}, Mantissa: {mantissa}"); + }else if (exponent == 31 && mantissa == 0) + { + Assert.Equal((exponent << 5), roundTrip); + } + else + { + Assert.Equal((exponent << 5) | 1, roundTrip); + } + } + } + } + + [Theory] + [InlineData(0, 0, 0.0f)] // Zero + [InlineData(31, 0, float.PositiveInfinity)] // Positive Infinity + [InlineData(31, 1, float.NaN)] // NaN + public void Float11EdgeCases(uint exponent, uint mantissa, float expected) + { + uint bits = (exponent << 6) | mantissa; + float value = ColorBitConversionHelpers.Float11ToFloat(bits); + + output.WriteLine($"Expected: {expected}, Actual: {value}, Exponent: {exponent}, Mantissa: {mantissa}"); + Assert.Equal(expected, value); + + // Verify round-trip + uint roundTrip = ColorBitConversionHelpers.FloatToFloat11(value); + Assert.True(bits == roundTrip, + $"Expected: {bits}, RoundTrip: {roundTrip}, Exponent: {exponent}, Mantissa: {mantissa}"); + } + + [Theory] + [InlineData(0, 0, 0.0f)] // Zero + [InlineData(31, 0, float.PositiveInfinity)] // Positive Infinity + [InlineData(31, 1, float.NaN)] // NaN + public void Float10EdgeCases(uint exponent, uint mantissa, float expected) + { + uint bits = (exponent << 5) | mantissa; + float value = ColorBitConversionHelpers.Float10ToFloat(bits); + + output.WriteLine($"Expected: {expected}, Actual: {value}, Exponent: {exponent}, Mantissa: {mantissa}"); + Assert.Equal(expected, value); + + // Verify round-trip + uint roundTrip = ColorBitConversionHelpers.FloatToFloat10(value); + Assert.True(bits == roundTrip, + $"Expected: {bits}, RoundTrip: {roundTrip}, Exponent: {exponent}, Mantissa: {mantissa}"); + } + } +} diff --git a/BCnEncTests/Colors/SharedExponentTests.cs b/BCnEncTests/Colors/SharedExponentTests.cs new file mode 100644 index 0000000..72bf120 --- /dev/null +++ b/BCnEncTests/Colors/SharedExponentTests.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Text; +using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; +using Xunit; +using Xunit.Abstractions; + +namespace BCnEncTests.Colors +{ + public class SharedExponentTests + { + private ITestOutputHelper output; + + public SharedExponentTests(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void RgbeToRgbaFloat() + { + var inc = 0.01f; + for (var b = 0f; b < 256f; b+=inc) + { + var orig = new ColorRgbaFloat(b, b, b); + var rgbe = new ColorRgbe(); + rgbe.FromColorRgbaFloat(orig); + + var rgba = rgbe.ToColorRgbaFloat(); + + Assert.True(MathF.Abs(orig.r - rgba.r) < inc / 2); + Assert.True(MathF.Abs(orig.g - rgba.g) < inc / 2); + Assert.True(MathF.Abs(orig.b - rgba.b) < inc / 2); + + inc *= 2; + + output.WriteLine($"Original: {orig}"); + output.WriteLine($"New : {rgba}"); + } + } + + [Fact] + public void XyzeToRgbaFloat() + { + var inc = 0.02f; + for (var b = 0.02f; b < 256f; b += inc) + { + var orig = new ColorRgbaFloat(b, b, b); + var xyze = new ColorXyze(); + xyze.FromColorRgbaFloat(orig); + + var rgba = xyze.ToColorRgbaFloat(); + + Assert.True(MathF.Abs(orig.r - rgba.r) < inc / 2, $"Expected {orig.r}, got {rgba.r}"); + Assert.True(MathF.Abs(orig.g - rgba.g) < inc / 2, $"Expected {orig.g}, got {rgba.g}"); + Assert.True(MathF.Abs(orig.b - rgba.b) < inc / 2, $"Expected {orig.b}, got {rgba.b}"); + + inc *= 2; + + output.WriteLine($"Original: {orig}"); + output.WriteLine($"New : {rgba}"); + } + } + + [Fact] + public void XyzToRgbaFloat() + { + var inc = 0.02f; + for (var b = 0.02f; b < 256f; b += inc) + { + var orig = new ColorRgbaFloat(b, b, b); + var xyz = new ColorXyz(); + xyz.FromColorRgbaFloat(orig); + + var rgba = xyz.ToColorRgbaFloat(); + + Assert.True(MathF.Abs(orig.r - rgba.r) < inc / 2, $"Expected {orig.r}, got {rgba.r}"); + Assert.True(MathF.Abs(orig.g - rgba.g) < inc / 2, $"Expected {orig.g}, got {rgba.g}"); + Assert.True(MathF.Abs(orig.b - rgba.b) < inc / 2, $"Expected {orig.b}, got {rgba.b}"); + + inc *= 2; + + output.WriteLine($"Original: {orig}"); + output.WriteLine($"New : {rgba}"); + } + } + } +} diff --git a/BCnEncTests/DdsReadTests.cs b/BCnEncTests/DdsReadTests.cs deleted file mode 100644 index 9a5ba2e..0000000 --- a/BCnEncTests/DdsReadTests.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.IO; -using BCnEncoder.Decoder; -using BCnEncoder.ImageSharp; -using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; -using BCnEncTests.Support; -using SixLabors.ImageSharp; -using Xunit; - -namespace BCnEncTests -{ - public class DdsReadTests - { - [Fact] - public void ReadRgba() - { - TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressRgba, DxgiFormat.DxgiFormatR8G8B8A8Unorm, "decoding_test_dds_rgba_mip{0}.png"); - } - - [Fact] - public void ReadBc1() - { - TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressBc1, DxgiFormat.DxgiFormatBc1Unorm, "decoding_test_dds_bc1_mip{0}.png"); - } - - [Fact] - public void ReadBc1A() - { - TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressBc1A, DxgiFormat.DxgiFormatBc1Unorm, "decoding_test_dds_bc1a_mip{0}.png"); - } - - [Fact] - public void ReadBc7() - { - TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressBc7, DxgiFormat.DxgiFormatUnknown, "decoding_test_dds_bc7_mip{0}.png"); - } - - [Fact] - public void ReadFromStream() - { - using var fs = File.OpenRead(DdsLoader.TestDecompressBc1Name); - - var decoder = new BcDecoder(); - var images = decoder.DecodeAllMipMapsToImageRgba32(fs); - - for (var i = 0; i < images.Length; i++) - { - using var outFs = File.OpenWrite($"decoding_test_dds_stream_bc1_mip{i}.png"); - images[i].SaveAsPng(outFs); - images[i].Dispose(); - } - } - } -} diff --git a/BCnEncTests/DdsWritingTests.cs b/BCnEncTests/DdsWritingTests.cs deleted file mode 100644 index 44a0616..0000000 --- a/BCnEncTests/DdsWritingTests.cs +++ /dev/null @@ -1,57 +0,0 @@ -using BCnEncoder.Shared; -using BCnEncTests.Support; -using Xunit; - -namespace BCnEncTests -{ - public class DdsWritingTests - { - [Fact] - public void DdsWriteRgba() - { - TestHelper.ExecuteDdsWritingTest(ImageLoader.TestLenna, CompressionFormat.Rgba, "encoding_dds_rgba.dds"); - } - - [Fact] - public void DdsWriteBc1() - { - TestHelper.ExecuteDdsWritingTest(ImageLoader.TestLenna, CompressionFormat.Bc1, "encoding_dds_bc1.dds"); - } - - [Fact] - public void DdsWriteBc2() - { - TestHelper.ExecuteDdsWritingTest(ImageLoader.TestAlpha1, CompressionFormat.Bc2, "encoding_dds_bc2.dds"); - } - - [Fact] - public void DdsWriteBc3() - { - TestHelper.ExecuteDdsWritingTest(ImageLoader.TestAlpha1, CompressionFormat.Bc3, "encoding_dds_bc3.dds"); - } - - [Fact] - public void DdsWriteBc4() - { - TestHelper.ExecuteDdsWritingTest(ImageLoader.TestHeight1, CompressionFormat.Bc4, "encoding_dds_bc4.dds"); - } - - [Fact] - public void DdsWriteBc5() - { - TestHelper.ExecuteDdsWritingTest(ImageLoader.TestRedGreen1, CompressionFormat.Bc5, "encoding_dds_bc5.dds"); - } - - [Fact] - public void DdsWriteBc7() - { - TestHelper.ExecuteDdsWritingTest(ImageLoader.TestLenna, CompressionFormat.Bc7, "encoding_dds_bc7.dds"); - } - - [Fact] - public void DdsWriteCubemap() - { - TestHelper.ExecuteDdsWritingTest(ImageLoader.TestCubemap, CompressionFormat.Bc1, "encoding_dds_cubemap_bc1.dds"); - } - } -} diff --git a/BCnEncTests/DecodingAsyncTests.cs b/BCnEncTests/DecodingAsyncTests.cs deleted file mode 100644 index d4993bc..0000000 --- a/BCnEncTests/DecodingAsyncTests.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.IO; -using BCnEncoder.Decoder; -using BCnEncoder.Encoder; -using BCnEncoder.ImageSharp; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using Xunit; - -namespace BCnEncTests -{ - public class DecodingAsyncTests - { - [Fact] - public async void DecodeAsync() - { - var decoder = new BcDecoder(); - var encoder = new BcEncoder(); - var original = ImageLoader.TestGradient1; - - var file = encoder.EncodeToKtx(original); - var image = await decoder.DecodeToImageRgba32Async(file); - - TestHelper.AssertImagesEqual(original, image,encoder.OutputOptions.Quality); - image.Dispose(); - } - - [Fact] - public async void DecodeAllMipMapsAsync() - { - var decoder = new BcDecoder(); - var encoder = new BcEncoder(); - var original = ImageLoader.TestGradient1; - - var file = encoder.EncodeToKtx(original); - var images = await decoder.DecodeAllMipMapsToImageRgba32Async(file); - - TestHelper.AssertImagesEqual(original, images[0], encoder.OutputOptions.Quality); - foreach(var img in images) - img.Dispose(); - } - - [Fact] - public async void DecodeRawAsync() - { - var decoder = new BcDecoder(); - var encoder = new BcEncoder(); - var original = ImageLoader.TestGradient1; - - var file = encoder.EncodeToKtx(original); - - var ms = new MemoryStream(file.MipMaps[0].Faces[0].Data); - - var image = await decoder.DecodeRawToImageRgba32Async(ms, - (int) file.MipMaps[0].Width, (int) file.MipMaps[0].Height, CompressionFormat.Bc1); - - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - image.Dispose(); - } - } -} diff --git a/BCnEncTests/DecodingTests.cs b/BCnEncTests/DecodingTests.cs deleted file mode 100644 index c2fc08c..0000000 --- a/BCnEncTests/DecodingTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -using BCnEncTests.Support; -using Xunit; - -namespace BCnEncTests -{ - public class DecodingTests - { - [Fact] - public void Bc1Decode() - { - TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc1, "decoding_test_bc1.png"); - } - - [Fact] - public void Bc1AlphaDecode() - { - TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc1A, "decoding_test_bc1a.png"); - } - - [Fact] - public void Bc2Decode() - { - TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc2, "decoding_test_bc2.png"); - } - - [Fact] - public void Bc3Decode() - { - TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc3, "decoding_test_bc3.png"); - } - - [Fact] - public void Bc4Decode() - { - TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc4Unorm, "decoding_test_bc4.png"); - } - - [Fact] - public void Bc5Decode() - { - TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc5Unorm, "decoding_test_bc5.png"); - } - - [Fact] - public void Bc7DecodeRgb() - { - TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc7Rgb, "decoding_test_bc7_rgb.png"); - } - - [Fact] - public void Bc7DecodeUnorm() - { - TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc7Unorm, "decoding_test_bc7_unorm.png"); - } - - [Fact] - public void Bc7DecodeEveryBlockType() - { - TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc7Types, "decoding_test_bc7_types.png"); - } - } -} diff --git a/BCnEncTests/EncodeByteOrderTests.cs b/BCnEncTests/EncodeByteOrderTests.cs deleted file mode 100644 index 928fbc9..0000000 --- a/BCnEncTests/EncodeByteOrderTests.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; -using BCnEncoder.Decoder; -using BCnEncoder.Encoder; -using BCnEncoder.ImageSharp; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -namespace BCnEncTests -{ - public class EncodeByteOrderTests - { - - private void EncodeByteOrderTest(PixelFormat pixelFormat) where T : unmanaged, IPixel - { - var imageOrig = ImageLoader.TestAlpha1; - var testImage = imageOrig.CloneAs(); - - var pixels = testImage.GetPixelMemoryGroup()[0]; - var bytes = MemoryMarshal.AsBytes(pixels.Span); - - var encoder = new BcEncoder(CompressionFormat.Bc3); - var decoder = new BcDecoder(); - - using var ms = new MemoryStream(); - - encoder.EncodeToStream(bytes, testImage.Width, testImage.Height, pixelFormat, ms); - - ms.Position = 0; - - var decoded = decoder.DecodeToImageRgba32(ms); - - var hasNoAlpha = pixelFormat == PixelFormat.Rgb24 || pixelFormat == PixelFormat.Bgr24; - - TestHelper.AssertImagesEqual(imageOrig, decoded, encoder.OutputOptions.Quality, - !hasNoAlpha); - } - - [Fact] - public void EncodeByteOrderBgra() - { - EncodeByteOrderTest(PixelFormat.Bgra32); - } - - [Fact] - public void EncodeByteOrderBgr() - { - EncodeByteOrderTest(PixelFormat.Bgr24); - } - - [Fact] - public void EncodeByteOrderArgb() - { - EncodeByteOrderTest(PixelFormat.Argb32); - } - - [Fact] - public void EncodeByteOrderRgba() - { - EncodeByteOrderTest(PixelFormat.Rgba32); - } - - [Fact] - public void EncodeByteOrderRgb24() - { - EncodeByteOrderTest(PixelFormat.Rgb24); - } - } -} diff --git a/BCnEncTests/EncoderOptionsTests.cs b/BCnEncTests/EncoderOptionsTests.cs deleted file mode 100644 index 677fce4..0000000 --- a/BCnEncTests/EncoderOptionsTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using BCnEncoder.Encoder; -using BCnEncTests.Support; -using Xunit; -using BCnEncoder.ImageSharp; - -namespace BCnEncTests -{ - public class EncoderOptionsTests - { - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(5)] - [InlineData(10)] - public void MaxMipMaps(int requestedMipMaps) - { - var testImage = ImageLoader.TestBlur1; - var encoder = new BcEncoder() - { - OutputOptions = - { - GenerateMipMaps = true, - MaxMipMapLevel = requestedMipMaps - } - }; - - Assert.Equal(requestedMipMaps, encoder.CalculateNumberOfMipLevels(testImage)); - - var ktx = encoder.EncodeToKtx(testImage); - - Assert.Equal(requestedMipMaps, (int)ktx.header.NumberOfMipmapLevels); - Assert.Equal(requestedMipMaps, ktx.MipMaps.Count); - - var dds = encoder.EncodeToDds(testImage); - - Assert.Equal(requestedMipMaps, (int)dds.header.dwMipMapCount); - Assert.Equal(requestedMipMaps, dds.Faces[0].MipMaps.Length); - } - - [Fact] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Assertions", "xUnit2013:Do not use equality check to check for collection size.", Justification = "")] - public void GenerateMipMaps() - { - var testImage = ImageLoader.TestBlur1; - const int requestedMipMaps = 1; - var encoder = new BcEncoder() - { - OutputOptions = - { - GenerateMipMaps = false - } - }; - - Assert.Equal(requestedMipMaps, encoder.CalculateNumberOfMipLevels(testImage)); - - var ktx = encoder.EncodeToKtx(testImage); - - Assert.Equal(requestedMipMaps, (int)ktx.header.NumberOfMipmapLevels); - Assert.Equal(requestedMipMaps, (int)ktx.MipMaps.Count); - - var dds = encoder.EncodeToDds(testImage); - - Assert.Equal(requestedMipMaps, (int)dds.header.dwMipMapCount); - Assert.Equal(requestedMipMaps, dds.Faces[0].MipMaps.Length); - } - } -} diff --git a/BCnEncTests/EncodingAsyncTest.cs b/BCnEncTests/EncodingAsyncTest.cs deleted file mode 100644 index 9542e46..0000000 --- a/BCnEncTests/EncodingAsyncTest.cs +++ /dev/null @@ -1,87 +0,0 @@ -using BCnEncoder.Decoder; -using BCnEncoder.Encoder; -using BCnEncoder.ImageSharp; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -namespace BCnEncTests -{ - public class EncodingAsyncTest - { - private readonly BcEncoder encoder; - private readonly BcDecoder decoder; - private readonly Image originalImage; - private readonly Image[] originalCubeMap; - - public EncodingAsyncTest() - { - encoder = new BcEncoder(); - decoder = new BcDecoder(); - originalImage = ImageLoader.TestGradient1; - originalCubeMap = ImageLoader.TestCubemap; - } - - [Fact] - public async void EncodeToDdsAsync() - { - var file = await encoder.EncodeToDdsAsync(originalImage); - var image = decoder.DecodeToImageRgba32(file); - - TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality); - image.Dispose(); - } - - [Fact] - public async void EncodeToKtxAsync() - { - var file = await encoder.EncodeToKtxAsync(originalImage); - var image = decoder.DecodeToImageRgba32(file); - - TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality); - image.Dispose(); - } - - [Fact] - public async void EncodeCubemapToDdsAsync() - { - var file = await encoder.EncodeCubeMapToDdsAsync(originalCubeMap[0], originalCubeMap[1], originalCubeMap[2], - originalCubeMap[3], originalCubeMap[4], originalCubeMap[5]); - - for (var i = 0; i < 6; i++) - { - var image = decoder.DecodeRawToImageRgba32(file.Faces[i].MipMaps[0].Data, (int)file.Faces[i].Width, (int)file.Faces[i].Height, CompressionFormat.Bc1); - - TestHelper.AssertImagesEqual(originalCubeMap[i], image, encoder.OutputOptions.Quality); - image.Dispose(); - } - } - - [Fact] - public async void EncodeCubemapToKtxAsync() - { - var file = await encoder.EncodeCubeMapToKtxAsync(originalCubeMap[0], originalCubeMap[1], originalCubeMap[2], - originalCubeMap[3], originalCubeMap[4], originalCubeMap[5]); - - for (var i = 0; i < 6; i++) - { - var image = decoder.DecodeRawToImageRgba32(file.MipMaps[0].Faces[i].Data, (int)file.MipMaps[0].Faces[i].Width, (int)file.MipMaps[0].Faces[i].Height, CompressionFormat.Bc1); - - TestHelper.AssertImagesEqual(originalCubeMap[i], image, encoder.OutputOptions.Quality); - image.Dispose(); - } - } - - [Fact] - public async void EncodeToRawBytesAsync() - { - var data = await encoder.EncodeToRawBytesAsync(originalImage); - var image = decoder.DecodeRawToImageRgba32(data[0], originalImage.Width, originalImage.Height, CompressionFormat.Bc1); - - TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality); - image.Dispose(); - } - } -} diff --git a/BCnEncTests/EncodingTest.cs b/BCnEncTests/EncodingTest.cs deleted file mode 100644 index 00e46f2..0000000 --- a/BCnEncTests/EncodingTest.cs +++ /dev/null @@ -1,501 +0,0 @@ -using System.IO; -using BCnEncoder.Encoder; -using BCnEncoder.ImageSharp; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using Xunit; -using Xunit.Abstractions; - -namespace BCnEncTests -{ - public class Bc1GradientTest - { - private readonly ITestOutputHelper output; - - public Bc1GradientTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc1GradientBestQuality() - { - var image = ImageLoader.TestGradient1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1, - CompressionQuality.BestQuality, - "encoding_bc1_gradient_bestQuality.ktx", - output); - } - - [Fact] - public void Bc1GradientBalanced() - { - var image = ImageLoader.TestGradient1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1, - CompressionQuality.Balanced, - "encoding_bc1_gradient_balanced.ktx", - output); - } - - [Fact] - public void Bc1GradientFast() - { - var image = ImageLoader.TestGradient1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1, - CompressionQuality.Fast, - "encoding_bc1_gradient_fast.ktx", - output); - } - } - - public class Bc1DiffuseTest - { - private readonly ITestOutputHelper output; - - public Bc1DiffuseTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc1DiffuseBestQuality() - { - var image = ImageLoader.TestDiffuse1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1, - CompressionQuality.BestQuality, - "encoding_bc1_diffuse_bestQuality.ktx", - output); - } - - [Fact] - public void Bc1DiffuseBalanced() - { - var image = ImageLoader.TestDiffuse1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1, - CompressionQuality.Balanced, - "encoding_bc1_diffuse_balanced.ktx", - output); - } - - [Fact] - public void Bc1DiffuseFast() - { - var image = ImageLoader.TestDiffuse1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1, - CompressionQuality.Fast, - "encoding_bc1_diffuse_fast.ktx", - output); - } - } - - public class Bc1BlurryTest - { - private readonly ITestOutputHelper output; - - public Bc1BlurryTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc1BlurBestQuality() - { - var image = ImageLoader.TestBlur1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1, - CompressionQuality.BestQuality, - "encoding_bc1_blur_bestQuality.ktx", - output); - } - - [Fact] - public void Bc1BlurBalanced() - { - var image = ImageLoader.TestBlur1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1, - CompressionQuality.Balanced, - "encoding_bc1_blur_balanced.ktx", - output); - } - - [Fact] - public void Bc1BlurFast() - { - var image = ImageLoader.TestBlur1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1, - CompressionQuality.Fast, - "encoding_bc1_blur_fast.ktx", - output); - } - } - - public class Bc1ASpriteTest - { - private readonly ITestOutputHelper output; - - public Bc1ASpriteTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc1ASpriteBestQuality() - { - var image = ImageLoader.TestTransparentSprite1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1WithAlpha, - CompressionQuality.BestQuality, - "encoding_bc1a_sprite_bestQuality.ktx", - output); - } - - [Fact] - public void Bc1ASpriteBalanced() - { - var image = ImageLoader.TestTransparentSprite1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1WithAlpha, - CompressionQuality.Balanced, - "encoding_bc1a_sprite_balanced.ktx", - output); - } - - [Fact] - public void Bc1ASpriteFast() - { - var image = ImageLoader.TestTransparentSprite1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1WithAlpha, - CompressionQuality.Fast, - "encoding_bc1a_sprite_fast.ktx", - output); - } - } - - public class Bc2GradientTest - { - private readonly ITestOutputHelper output; - - public Bc2GradientTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc2GradientBestQuality() - { - var image = ImageLoader.TestAlphaGradient1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc2, - CompressionQuality.BestQuality, - "encoding_bc2_gradient_bestQuality.ktx", - output); - } - - [Fact] - public void Bc2GradientBalanced() - { - var image = ImageLoader.TestAlphaGradient1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc2, - CompressionQuality.Balanced, - "encoding_bc2_gradient_balanced.ktx", - output); - } - - [Fact] - public void Bc2GradientFast() - { - var image = ImageLoader.TestAlphaGradient1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc2, - CompressionQuality.Fast, - "encoding_bc2_gradient_fast.ktx", - output); - } - } - - public class Bc3GradientTest - { - private readonly ITestOutputHelper output; - - public Bc3GradientTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc3GradientBestQuality() - { - var image = ImageLoader.TestAlphaGradient1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc3, - CompressionQuality.BestQuality, - "encoding_bc3_gradient_bestQuality.ktx", - output); - } - - [Fact] - public void Bc3GradientBalanced() - { - var image = ImageLoader.TestAlphaGradient1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc3, - CompressionQuality.Balanced, - "encoding_bc3_gradient_balanced.ktx", - output); - } - - [Fact] - public void Bc3GradientFast() - { - var image = ImageLoader.TestAlphaGradient1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc3, - CompressionQuality.Fast, - "encoding_bc3_gradient_fast.ktx", - output); - } - } - - public class Bc4RedTest - { - private readonly ITestOutputHelper output; - - public Bc4RedTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc4RedBestQuality() - { - var image = ImageLoader.TestHeight1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc4, - CompressionQuality.BestQuality, - "encoding_bc4_red_bestQuality.ktx", - output); - } - - [Fact] - public void Bc4RedBalanced() - { - var image = ImageLoader.TestHeight1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc4, - CompressionQuality.Balanced, - "encoding_bc4_red_balanced.ktx", - output); - } - - [Fact] - public void Bc4RedFast() - { - var image = ImageLoader.TestHeight1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc4, - CompressionQuality.Fast, - "encoding_bc4_red_fast.ktx", - output); - } - } - - public class Bc5RedGreenTest - { - private readonly ITestOutputHelper output; - - public Bc5RedGreenTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc5RedGreenBestQuality() - { - var image = ImageLoader.TestRedGreen1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc5, - CompressionQuality.BestQuality, - "encoding_bc5_red_green_bestQuality.ktx", - output); - } - - [Fact] - public void Bc5RedGreenBalanced() - { - var image = ImageLoader.TestRedGreen1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc5, - CompressionQuality.Balanced, - "encoding_bc5_red_green_balanced.ktx", - output); - } - - [Fact] - public void Bc5RedGreenFast() - { - var image = ImageLoader.TestRedGreen1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc5, - CompressionQuality.Fast, - "encoding_bc5_red_green_fast.ktx", - output); - } - } - - public class Bc7RgbTest - { - private readonly ITestOutputHelper output; - - public Bc7RgbTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc7RgbBestQuality() - { - var image = ImageLoader.TestRgbHard1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc7, - CompressionQuality.BestQuality, - "encoding_bc7_rgb_bestQuality.ktx", - output); - } - - [Fact] - public void Bc7RgbBalanced() - { - var image = ImageLoader.TestRgbHard1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc7, - CompressionQuality.Balanced, - "encoding_bc7_rgb_balanced.ktx", - output); - } - - [Fact] - public void Bc7LennaBalanced() - { - var image = ImageLoader.TestLenna; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc7, - CompressionQuality.Balanced, - "encoding_bc7_lenna_balanced.ktx", - output); - } - - [Fact] - public void Bc7RgbFast() - { - var image = ImageLoader.TestRgbHard1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc7, - CompressionQuality.Fast, - "encoding_bc7_rgb_fast.ktx", - output); - } - } - - public class Bc7RgbaTest - { - private readonly ITestOutputHelper output; - - public Bc7RgbaTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc7RgbaBestQuality() - { - var image = ImageLoader.TestAlpha1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc7, - CompressionQuality.BestQuality, - "encoding_bc7_rgba_bestQuality.ktx", - output); - } - - [Fact] - public void Bc7RgbaBalanced() - { - var image = ImageLoader.TestAlpha1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc7, - CompressionQuality.Balanced, - "encoding_bc7_rgba_balanced.ktx", - output); - } - - [Fact] - public void Bc7RgbaFast() - { - var image = ImageLoader.TestAlpha1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc7, - CompressionQuality.Fast, - "encoding_bc7_rgba_fast.ktx", - output); - } - } - - public class CubemapTest - { - [Fact] - public void WriteCubeMapFile() - { - var images = ImageLoader.TestCubemap; - - var filename = "encoding_bc1_cubemap.ktx"; - - var encoder = new BcEncoder(); - encoder.OutputOptions.Quality = CompressionQuality.Fast; - encoder.OutputOptions.GenerateMipMaps = true; - encoder.OutputOptions.Format = CompressionFormat.Bc1; - - using var fs = File.OpenWrite(filename); - encoder.EncodeCubeMapToStream(images[0], images[1], images[2], images[3], images[4], images[5], fs); - } - } -} diff --git a/BCnEncTests/Examples.cs b/BCnEncTests/Examples.cs index b3103bd..8d5c3ce 100644 --- a/BCnEncTests/Examples.cs +++ b/BCnEncTests/Examples.cs @@ -6,16 +6,19 @@ using BCnEncoder.Encoder; using BCnEncoder.ImageSharp; using BCnEncoder.Shared; -using Microsoft.Toolkit.HighPerformance; +using BCnEncoder.TextureFormats; +using CommunityToolkit.HighPerformance; using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; using Xunit; +using EncoderExtensions = BCnEncoder.ImageSharp.EncoderExtensions; namespace BCnEncTests { public class Examples { - + public void EncodeImageSharp() { using Image image = Image.Load("example.png"); @@ -25,37 +28,36 @@ public void EncodeImageSharp() encoder.OutputOptions.GenerateMipMaps = true; encoder.OutputOptions.Quality = CompressionQuality.Balanced; encoder.OutputOptions.Format = CompressionFormat.Bc1; - encoder.OutputOptions.FileFormat = OutputFileFormat.Ktx; //Change to Dds for a dds file. - using FileStream fs = File.OpenWrite("example.ktx"); - encoder.EncodeToStream(image, fs); + + encoder.EncodeToStream(image, fs); } - + public void DecodeImageSharp() { using FileStream fs = File.OpenRead("compressed_bc1.ktx"); BcDecoder decoder = new BcDecoder(); - using Image image = decoder.DecodeToImageRgba32(fs); - + using Image image = decoder.Decode(fs).AsImageRgba32(); + using FileStream outFs = File.OpenWrite("decoding_test_bc1.png"); image.SaveAsPng(outFs); } public void EncodeHdr() { - HdrImage image = HdrImage.Read("example.hdr"); - + using FileStream ifs = File.OpenRead("example.hdr"); + RadianceFile image = RadianceFile.Load(ifs); + BcEncoder encoder = new BcEncoder(); encoder.OutputOptions.GenerateMipMaps = true; encoder.OutputOptions.Quality = CompressionQuality.Balanced; encoder.OutputOptions.Format = CompressionFormat.Bc6U; - encoder.OutputOptions.FileFormat = OutputFileFormat.Ktx; //Change to Dds for a dds file. - using FileStream fs = File.OpenWrite("example.ktx"); - encoder.EncodeToStreamHdr(image.PixelMemory, fs); + using FileStream fs = File.OpenWrite("example.dds"); + encoder.EncodeToStream(image.ToTextureData(), fs); } public void DecodeHdr() @@ -63,12 +65,14 @@ public void DecodeHdr() using FileStream fs = File.OpenRead("compressed_bc6.ktx"); BcDecoder decoder = new BcDecoder(); - Memory2D pixels = decoder.DecodeHdr2D(fs); + BCnTextureData decodedData = decoder.Decode(fs); + + BCnTextureData rgbeData = decodedData.ConvertTo(CompressionFormat.Rgbe32); - HdrImage image = new HdrImage(pixels.Span); + RadianceFile image = rgbeData.AsTexture(); using FileStream outFs = File.OpenWrite("decoded.hdr"); - image.Write(outFs); + image.WriteToStream(outFs); } } } diff --git a/BCnEncTests/FileFormats/FileFormatTests.cs b/BCnEncTests/FileFormats/FileFormatTests.cs new file mode 100644 index 0000000..8ee7465 --- /dev/null +++ b/BCnEncTests/FileFormats/FileFormatTests.cs @@ -0,0 +1,65 @@ +using System.IO; +using BCnEncoder.TextureFormats; +using Xunit; + +namespace BCnEncTests.FileFormats +{ + public class FileFormatTests + { + [Theory] + [InlineData("../../../testImages/encoded/raw_r8_unorm.ktx")] + [InlineData("../../../testImages/encoded/raw_r8g8_unorm.ktx")] + [InlineData("../../../testImages/encoded/raw_r8g8b8_unorm.ktx")] + [InlineData("../../../testImages/encoded/raw_r16g16b16_sfloat.ktx")] + [InlineData("../../../testImages/encoded/bc1a_unorm.ktx")] + [InlineData("../../../testImages/encoded/bc2_unorm.ktx")] + public void TestReadWriteKtxBitExact(string testImageFile) + { + var originalStream = new MemoryStream(); + using var ofs = File.OpenRead(testImageFile); + ofs.CopyTo(originalStream); + + originalStream.Seek(0, SeekOrigin.Begin); + var ktx1 = KtxFile.Load(originalStream); + + var ktx2 = ktx1.ToTextureData().AsTexture(); + + using var newStream = new MemoryStream(); + ktx2.WriteToStream(newStream); + + newStream.Seek(0, SeekOrigin.Begin); + originalStream.Seek(0, SeekOrigin.Begin); + + Assert.Equal(newStream.Length, originalStream.Length); + Assert.Equal(newStream.ToArray(), originalStream.ToArray()); + } + + [Theory] + [InlineData("../../../testImages/encoded/alpha_1_bc1a.dds")] + [InlineData("../../../testImages/encoded/alpha_1_bc3.dds")] + [InlineData("../../../testImages/encoded/alpha_1_bgra.dds")] + [InlineData("../../../testImages/encoded/alpha_2_rgba.dds")] + [InlineData("../../../testImages/encoded/alpha_2_bc3.dds")] + public void TestReadWriteDdsBitExact(string testImageFile) + { + var originalStream = new MemoryStream(); + using var ofs = File.OpenRead(testImageFile); + ofs.CopyTo(originalStream); + + originalStream.Seek(0, SeekOrigin.Begin); + var dds1 = DdsFile.Load(originalStream); + + var dds2 = new DdsFile(); + dds2.FromTextureData(dds1.ToTextureData()); + + using var newStream = new MemoryStream(); + dds2.WriteToStream(newStream); + + newStream.Seek(0, SeekOrigin.Begin); + originalStream.Seek(0, SeekOrigin.Begin); + + Assert.Equal(newStream.Length, originalStream.Length); + Assert.Equal(newStream.ToArray(), originalStream.ToArray()); + } + } +} diff --git a/BCnEncTests/FileFormats/KhrDataFormatDescriptorTests.cs b/BCnEncTests/FileFormats/KhrDataFormatDescriptorTests.cs new file mode 100644 index 0000000..152a441 --- /dev/null +++ b/BCnEncTests/FileFormats/KhrDataFormatDescriptorTests.cs @@ -0,0 +1,6 @@ +namespace BCnEncTests.FileFormats +{ + public class KhrDataFormatDescriptorTests + { + } +} diff --git a/BCnEncTests/GenerateReferenceEncodes.ps1 b/BCnEncTests/GenerateReferenceEncodes.ps1 new file mode 100644 index 0000000..b946409 --- /dev/null +++ b/BCnEncTests/GenerateReferenceEncodes.ps1 @@ -0,0 +1,261 @@ +# Quality benchmark image generator +# Generates compressed test images for benchmarking quality metrics + +# Define hardcoded paths +$inputDir = "testImages\raw" +$outputDir = "testImages\Quality" + +# Create output directory if needed +if (-not (Test-Path $outputDir)) { + New-Item -ItemType Directory -Path $outputDir | Out-Null + Write-Output "Created directory $outputDir" +} + +# Source files to use +$rgbFile = "$inputDir\rgb_hard.png" +$normalFile = "$inputDir\normal_1.png" +$specularFile = "$inputDir\height_1.png" # Using as stand-in for specular map + +# Copy reference images +Copy-Item -Path $rgbFile -Destination "$outputDir\albedo_reference.png" -Force +Copy-Item -Path $normalFile -Destination "$outputDir\normal_reference.png" -Force +Copy-Item -Path $specularFile -Destination "$outputDir\specular_reference.png" -Force + +Write-Output "Copied reference images" + +# Function to extract the base filename from a path +function Get-BaseFileName { + param([string]$path) + # Get just the filename without the path + $filename = Split-Path -Leaf $path + # Get the filename without the extension + $basename = [System.IO.Path]::GetFileNameWithoutExtension($filename) + return $basename +} + +# Define all compression tasks in a single list +$compressionTasks = @( + # BC1 albedo image + @{ + Name = "BC1 albedo image" + InputFile = $rgbFile + # Base output name - the tool name will be inserted + BaseName = "albedo" + # Format-specific configuration for each tool + ToolConfig = @{ + DirectXTex = @{ + Format = "BC1_UNORM" + # Use sRGB for albedo textures + AdditionalFlags = "-srgbi -srgbo -bc d" + } + PVRTexTool = @{ + Format = "BC1,UBN,sRGB" + # Specify input colorspace as sRGB with -ics sRGB + AdditionalFlags = "-ics sRGB -q pvrtcbest" + } + } + }, + # BC3 albedo image + @{ + Name = "BC3 albedo image" + InputFile = $rgbFile + # Base output name - the tool name will be inserted + BaseName = "albedo" + # Format-specific configuration for each tool + ToolConfig = @{ + DirectXTex = @{ + Format = "BC3_UNORM" + # Use sRGB for albedo textures + AdditionalFlags = "-srgbi -srgbo -bc d" + } + PVRTexTool = @{ + Format = "BC3,UBN,sRGB" + # Specify input colorspace as sRGB with -ics sRGB + AdditionalFlags = "-ics sRGB -q pvrtcbest" + } + } + }, + # BC6H albedo image + @{ + Name = "BC6H albedo image" + InputFile = $rgbFile + # Base output name - the tool name will be inserted + BaseName = "albedo" + # Format-specific configuration for each tool + ToolConfig = @{ + DirectXTex = @{ + Format = "BC6H_UF16" + # Use sRGB for albedo textures + AdditionalFlags = "-srgbi -srgbo" + } + } + }, + # BC6HS albedo image + @{ + Name = "BC6H Signed albedo image" + InputFile = $rgbFile + # Base output name - the tool name will be inserted + BaseName = "albedo" + # Format-specific configuration for each tool + ToolConfig = @{ + DirectXTex = @{ + Format = "BC6H_SF16" + # Use sRGB for albedo textures + AdditionalFlags = "-srgbi -srgbo" + } + } + }, + # BC7 albedo image + @{ + Name = "BC7 albedo image" + InputFile = $rgbFile + # Base output name - the tool name will be inserted + BaseName = "albedo" + # Format-specific configuration for each tool + ToolConfig = @{ + DirectXTex = @{ + Format = "BC7_UNORM" + # Use sRGB for albedo textures + AdditionalFlags = "-srgbi -srgbo -bc x" + } + } + }, + # BC5 normal map image + @{ + Name = "BC5 normal map" + InputFile = $normalFile + # Base output name - the tool name will be inserted + BaseName = "normal" + # Format-specific configuration for each tool + ToolConfig = @{ + DirectXTex = @{ + Format = "BC5_UNORM" + # For normal maps, we need DX10 header and linear color space + AdditionalFlags = "-dx10" + } + PVRTexTool = @{ + Format = "BC5,UBN,lRGB" + # For normal maps, explicitly tell it the input is linear with -ics lRGB flag + AdditionalFlags = "-ics lRGB -q pvrtcbest" + } + } + }, + # BC1 specular map image + @{ + Name = "BC1 specular map" + InputFile = $specularFile + # Base output name - the tool name will be inserted + BaseName = "specular" + # Format-specific configuration for each tool + ToolConfig = @{ + DirectXTex = @{ + Format = "BC1_UNORM" + # For specular maps, keep in linear color space + AdditionalFlags = "" + } + PVRTexTool = @{ + Format = "BC1,UBN,lRGB" + # For specular maps, use premultiplied alpha with -p for BC1 format + AdditionalFlags = "-ics lRGB -p -q pvrtcbest" + } + } + }, + # BC4 specular map image + @{ + Name = "BC4 specular map" + InputFile = $specularFile + # Base output name - the tool name will be inserted + BaseName = "specular" + # Format-specific configuration for each tool + ToolConfig = @{ + DirectXTex = @{ + Format = "BC4_UNORM" + # For specular maps, keep in linear color space + AdditionalFlags = "" + } + PVRTexTool = @{ + Format = "BC4,UBN,lRGB" + # For specular maps, use premultiplied alpha with -p for BC1 format + AdditionalFlags = "-ics lRGB -p -q pvrtcbest" + } + } + } +) + +# Define the tools we'll use for compression +$compressionTools = @( + @{ + Name = "DirectXTex" + }, + @{ + Name = "PVRTexTool" + } +) + +# Process each compression tool +foreach ($tool in $compressionTools) { + $toolName = $tool.Name + + # Generate compressed images + Write-Output " + +Generating $toolName compressed images..." + + # Process each compression task + foreach ($task in $compressionTasks) { + Write-Output "Processing $($task.Name)..." + + # Get tool-specific configuration + $toolConfig = $task.ToolConfig[$toolName] + + if ($null -eq $toolConfig) { + Write-Output "No configuration found for $toolName and $($task.Name)" + continue + } + + # Determine output filename + # Format: basename_toolname_format.dds + # Example: albedo_directxtex_bc1.dds or albedo_pvrtextool_bc3.dds + $taskFormat = $toolConfig.Format -replace "_UNORM", "" # Remove _UNORM for filename + $taskFormat = $taskFormat.Split(',')[0].ToLower() # Take first part and lowercase it + $toolNameLower = $toolName.ToLower() + $outputFile = "$outputDir\$($task.BaseName)_${toolNameLower}_$taskFormat.dds" + + if ($toolName -eq "DirectXTex") { + # texconv expects the -o parameter to be a directory, not a file + $cmd = "texconv -f $($toolConfig.Format) -y -nologo $($toolConfig.AdditionalFlags) `"$($task.InputFile)`" -o `"$outputDir`"" + Write-Output "Running: $cmd" + Invoke-Expression "$cmd" + + # Calculate the temporary file name that texconv creates (input filename with .dds extension) + $baseName = Get-BaseFileName $task.InputFile + $tempFile = "$outputDir\$baseName.dds" + + # Rename the temporary file to the desired output file + if (Test-Path $tempFile) { + Move-Item -Path $tempFile -Destination $outputFile -Force + Write-Output "Created $outputFile" + } + } + elseif ($toolName -eq "PVRTexTool") { + # PVRTexTool can output directly to the filename + $cmd = "PVRTexToolCLI -i `"$($task.InputFile)`" -o `"$outputFile`" -f $($toolConfig.Format) $($toolConfig.AdditionalFlags)" + Write-Output "Running: $cmd" + try { + Invoke-Expression "$cmd" + if (Test-Path $outputFile) { + Write-Output "Created $outputFile" + } + } catch { + Write-Output "Error running PVRTexToolCLI: $_" + } + } + } +} + +# Note: PVRTexToolCLI doesn't support BC7 format + +Write-Output " + +Test images generated successfully!" +Write-Output "You can now run the ImageQualityBenchmark test to analyze these images." diff --git a/BCnEncTests/GenerateReferenceEncodes.sh b/BCnEncTests/GenerateReferenceEncodes.sh new file mode 100755 index 0000000..b50954e --- /dev/null +++ b/BCnEncTests/GenerateReferenceEncodes.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# Quality benchmark image generator +# Generates compressed test images for benchmarking quality metrics using cuttlefish + +set -euo pipefail + +# Define hardcoded paths +INPUT_DIR="testImages/raw" +OUTPUT_DIR="testImages/Quality" + +# Create output directory if needed +mkdir -p "$OUTPUT_DIR" + +# Source files to use +RGB_FILE="$INPUT_DIR/rgb_hard.png" +NORMAL_FILE="$INPUT_DIR/normal_1.png" +SPECULAR_FILE="$INPUT_DIR/height_1.png" # Using as stand-in for specular map + +# Copy reference images +cp -f "$RGB_FILE" "$OUTPUT_DIR/albedo_reference.png" +cp -f "$NORMAL_FILE" "$OUTPUT_DIR/normal_reference.png" +cp -f "$SPECULAR_FILE" "$OUTPUT_DIR/specular_reference.png" + +echo "Copied reference images" + +run_cuttlefish() { + local input="$1" + local output="$2" + shift 2 + echo "Running: cuttlefish -i \"$input\" -o \"$output\" $*" + cuttlefish -i "$input" -o "$output" "$@" + if [[ -f "$output" ]]; then + echo "Created $output" + fi +} + +echo "" +echo "Generating cuttlefish compressed images..." + +# BC1 albedo image (sRGB) +run_cuttlefish "$RGB_FILE" "$OUTPUT_DIR/albedo_cuttlefish_bc1.dds" \ + -f BC1_RGB --srgb -Q highest + +# BC3 albedo image (sRGB) +run_cuttlefish "$RGB_FILE" "$OUTPUT_DIR/albedo_cuttlefish_bc3.dds" \ + -f BC3 --srgb -Q highest + +# BC6H albedo image (unsigned, sRGB input) +run_cuttlefish "$RGB_FILE" "$OUTPUT_DIR/albedo_cuttlefish_bc6h.dds" \ + -f BC6H -t ufloat --srgb -Q highest + +# BC6H Signed albedo image (signed, sRGB input) +run_cuttlefish "$RGB_FILE" "$OUTPUT_DIR/albedo_cuttlefish_bc6h_sf16.dds" \ + -f BC6H -t float --srgb -Q highest + +# BC7 albedo image (sRGB) +run_cuttlefish "$RGB_FILE" "$OUTPUT_DIR/albedo_cuttlefish_bc7.dds" \ + -f BC7 --srgb -Q highest + +# BC5 normal map (linear) +run_cuttlefish "$NORMAL_FILE" "$OUTPUT_DIR/normal_cuttlefish_bc5.dds" \ + -f BC5 -Q highest + +# BC1 specular map (linear) +run_cuttlefish "$SPECULAR_FILE" "$OUTPUT_DIR/specular_cuttlefish_bc1.dds" \ + -f BC1_RGB -Q highest + +# BC4 specular map (linear) +run_cuttlefish "$SPECULAR_FILE" "$OUTPUT_DIR/specular_cuttlefish_bc4.dds" \ + -f BC4 -Q highest + +echo "" +echo "Test images generated successfully!" +echo "You can now run the ImageQualityBenchmark test to analyze these images." diff --git a/BCnEncTests/HdrImageTests.cs b/BCnEncTests/HdrImageTests.cs deleted file mode 100644 index 9e05ab2..0000000 --- a/BCnEncTests/HdrImageTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using BCnEncoder.Encoder; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -namespace BCnEncTests -{ - public class HdrImageTests - { - [Fact] - public void LoadHdr() - { - using var stream = File.OpenRead("../../../testImages/test_hdr_kiara.hdr"); - var hdrImg = HdrImage.Read(stream); - Assert.True(hdrImg.width > 0); - Assert.True(hdrImg.height > 0); - Assert.True(hdrImg.pixels.Length == hdrImg.width * hdrImg.height); - - var img = new Image(hdrImg.width, hdrImg.height); - - for (var y = 0; y < hdrImg.height; y++) - { - for (var x = 0; x < hdrImg.width; x++) - { - var i = y * hdrImg.width + x; - img[x, y] = new RgbaVector(hdrImg.pixels[i].r, hdrImg.pixels[i].g, hdrImg.pixels[i].b); - } - } - - img.SaveAsPng("test_hdr_load.png"); - - var img2 = img.CloneAs(); - - TestHelper.AssertImagesEqual(HdrLoader.ReferenceKiara, img2, CompressionQuality.BestQuality); - } - } -} diff --git a/BCnEncTests/Helpers/ClusterTests.cs b/BCnEncTests/Helpers/ClusterTests.cs new file mode 100644 index 0000000..9ff85e1 --- /dev/null +++ b/BCnEncTests/Helpers/ClusterTests.cs @@ -0,0 +1,43 @@ +namespace BCnEncTests.Helpers +{ + // public class ClusterTests + // { + // [Fact] + // public void Clusterize() + // { + // var testImage = ImageLoader.TestRawImages["diffuse_1"].Clone(); + // + // var pix = TestHelper.GetSinglePixelArray(testImage); + // + // var pixels = MemoryMarshal.Cast(pix).ToArray(); + // + // var numClusters = (testImage.Width / 32) * (testImage.Height / 32); + // var clusters = LinearClustering.ClusterPixels(pixels, testImage.Width, testImage.Height, numClusters, 10, 10); + // + // var pixC = new ColorYCbCr[numClusters]; + // var counts = new int[numClusters]; + // + // for (var i = 0; i < pixels.Length; i++) + // { + // pixC[clusters[i]] += pixels[i].As(); + // counts[clusters[i]]++; + // } + // + // for (var i = 0; i < numClusters; i++) + // { + // pixC[i] /= counts[i]; + // } + // + // for (var i = 0; i < pixels.Length; i++) + // { + // pixels[i] = pixC[clusters[i]].As(); + // pix[i] = new Rgba32(pixels[i].r, pixels[i].g, pixels[i].b, pixels[i].a); + // } + // + // TestHelper.SetSinglePixelArray(testImage, pix); + // + // using var fs = File.OpenWrite("test_cluster.png"); + // testImage.SaveAsPng(fs); + // } + // } +} diff --git a/BCnEncTests/Helpers/ImageToBlocksTests.cs b/BCnEncTests/Helpers/ImageToBlocksTests.cs new file mode 100644 index 0000000..1fb7f1b --- /dev/null +++ b/BCnEncTests/Helpers/ImageToBlocksTests.cs @@ -0,0 +1,94 @@ +using System; +using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; +using Xunit; + +namespace BCnEncTests.Helpers +{ + public class ImageToBlocksTests + { + [Fact] + public void CreateBlocksExact() + { + var testImage = new ColorRgbaFloat[16 * 16]; + + var blocks = ImageToBlocks.ImageTo4X4(testImage, 16, 16, out var blocksWidth, out var blocksHeight); + + Assert.Equal(16, blocks.Length); + Assert.Equal(4, blocksWidth); + Assert.Equal(4, blocksHeight); + } + + [Fact] + public void CreateBlocksPadding() + { + var testImage = new ColorRgbaFloat[15 * 11]; + + var blocks = ImageToBlocks.ImageTo4X4(testImage, 11, 15, out var blocksWidth, out var blocksHeight); + + Assert.Equal(12, blocks.Length); + Assert.Equal(3, blocksWidth); + Assert.Equal(4, blocksHeight); + } + + [Fact] + public void PaddingColor() + { + var testImage = new ColorRgbaFloat[15 * 11]; + + var pixels = testImage.AsSpan(); + + for (var i = 0; i < pixels.Length; i++) { + pixels[i] = new ColorRgbaFloat(0, .5f, .5f); + } + + var blocks = ImageToBlocks.ImageTo4X4(testImage, 11, 15, out var blocksWidth, out var blocksHeight); + + Assert.Equal(12, blocks.Length); + Assert.Equal(3, blocksWidth); + Assert.Equal(4, blocksHeight); + + for (var x = 0; x < blocksWidth; x++) { + for (var y = 0; y < blocksHeight; y++) { + foreach (var color in blocks[x + y * blocksWidth].AsSpan) { + Assert.Equal(new ColorRgbaFloat(0, .5f, .5f), color); + } + } + } + } + + [Fact] + public void BlocksToImage() + { + var r = new Random(0); + var testImage = new ColorRgbaFloat[16 * 16]; + + var pixels = testImage.AsSpan(); + + for (var i = 0; i < pixels.Length; i++) { + pixels[i] = new ColorRgbaFloat( + r.NextSingle(), + r.NextSingle(), + r.NextSingle(), + r.NextSingle()); + } + + var blocks = ImageToBlocks.ImageTo4X4(testImage, 16, 16, out var blocksWidth, out var blocksHeight); + + Assert.Equal(16, blocks.Length); + Assert.Equal(4, blocksWidth); + Assert.Equal(4, blocksHeight); + + var output = new ColorRgbaFloat[16 * 16]; + + ImageToBlocks.ColorsFromRawBlocks(blocks, output, 16, 16); + + var pixels2 = output.AsSpan(); + + Assert.Equal(pixels.Length, pixels2.Length); + for (var i = 0; i < pixels.Length; i++) { + Assert.Equal(pixels[i], pixels2[i]); + } + } + } +} diff --git a/BCnEncTests/IntHelperTests.cs b/BCnEncTests/Helpers/IntHelperTests.cs similarity index 86% rename from BCnEncTests/IntHelperTests.cs rename to BCnEncTests/Helpers/IntHelperTests.cs index a8e2e49..b51566d 100644 --- a/BCnEncTests/IntHelperTests.cs +++ b/BCnEncTests/Helpers/IntHelperTests.cs @@ -1,10 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Text; using BCnEncoder.Shared; using Xunit; -namespace BCnEncTests +namespace BCnEncTests.Helpers { public class IntHelperTests { diff --git a/BCnEncTests/MathHelperTests.cs b/BCnEncTests/Helpers/MathHelperTests.cs similarity index 90% rename from BCnEncTests/MathHelperTests.cs rename to BCnEncTests/Helpers/MathHelperTests.cs index 0aeac1c..3d53d43 100644 --- a/BCnEncTests/MathHelperTests.cs +++ b/BCnEncTests/Helpers/MathHelperTests.cs @@ -1,10 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Text; using BCnEncoder.Shared; using Xunit; -namespace BCnEncTests +namespace BCnEncTests.Helpers { public class MathHelperTests { diff --git a/BCnEncTests/PcaTests.cs b/BCnEncTests/Helpers/PcaTests.cs similarity index 98% rename from BCnEncTests/PcaTests.cs rename to BCnEncTests/Helpers/PcaTests.cs index 9c2e66d..8cbd1ab 100644 --- a/BCnEncTests/PcaTests.cs +++ b/BCnEncTests/Helpers/PcaTests.cs @@ -3,7 +3,7 @@ using Xunit; using Vector4 = System.Numerics.Vector4; -namespace BCnEncTests +namespace BCnEncTests.Helpers { public class PcaTests { diff --git a/BCnEncTests/ProgressTests.cs b/BCnEncTests/ProgressTests.cs deleted file mode 100644 index 5eae2e9..0000000 --- a/BCnEncTests/ProgressTests.cs +++ /dev/null @@ -1,392 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using BCnEncoder.Decoder; -using BCnEncoder.Encoder; -using BCnEncoder.ImageSharp; -using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; -using BCnEncTests.Support; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; -using Xunit.Abstractions; - -namespace BCnEncTests -{ - public class ProgressTests - { - private ITestOutputHelper output; - - public ProgressTests(ITestOutputHelper output) => this.output = output; - - private async Task ExecuteEncodeProgressReport(BcEncoder encoder, Image testImage, int expectedTotalBlocks) - { - var lastProgress = new ProgressElement(0, 1); - - var processedBlocks = 0; - encoder.Options.Progress = new SynchronousProgress(element => - { - output.WriteLine($"Progress = {element.CurrentBlock} / {element.TotalBlocks}"); - - Assert.Equal(++processedBlocks, element.CurrentBlock); - lastProgress = element; - }); - - await using var ms = new MemoryStream(); - await encoder.EncodeToStreamAsync(testImage, ms); - - output.WriteLine("LastProgress = " + lastProgress); - - Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks); - Assert.Equal(1, lastProgress.Percentage); - } - - private async Task ExecuteDecodeProgressReport(BcDecoder decoder, KtxFile image, int expectedTotalBlocks, bool singleMip) - { - var lastProgress = new ProgressElement(0, 1); - - var processedBlocks = 0; - decoder.Options.Progress = new SynchronousProgress(element => - { - output.WriteLine($"Progress = {element.CurrentBlock} / {element.TotalBlocks}"); - - Assert.Equal(++processedBlocks, element.CurrentBlock); - lastProgress = element; - }); - - if (singleMip) - { - await decoder.DecodeAsync(image); - } - else - { - await decoder.DecodeAllMipMapsAsync(image); - } - - output.WriteLine("LastProgress = " + lastProgress); - - Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks); - Assert.Equal(1, lastProgress.Percentage); - } - - private async Task ExecuteDecodeProgressReport(BcDecoder decoder, DdsFile image, int expectedTotalBlocks, bool singleMip) - { - var lastProgress = new ProgressElement(0, 1); - - var processedBlocks = 0; - decoder.Options.Progress = new SynchronousProgress(element => - { - output.WriteLine($"Progress = {element.CurrentBlock} / {element.TotalBlocks}"); - - Assert.Equal(++processedBlocks, element.CurrentBlock); - lastProgress = element; - }); - - if (singleMip) - { - await decoder.DecodeAsync(image); - } - else - { - await decoder.DecodeAllMipMapsAsync(image); - } - - output.WriteLine("LastProgress = " + lastProgress); - - Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks); - Assert.Equal(1, lastProgress.Percentage); - } - - [Fact] - public async void DecodeProgressReportParallel() - { - var testImage = KtxLoader.TestDecompressBc1; - var decoder = new BcDecoder - { - Options = { IsParallel = true } - }; - - var expectedTotal = 0; - - for (var i = 0; i < testImage.header.NumberOfMipmapLevels; i++) - { - expectedTotal += decoder.GetBlockCount((int)testImage.MipMaps[i].Width, (int)testImage.MipMaps[i].Height); - } - - await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false); - } - - [Fact] - public async void DecodeProgressReportNonParallel() - { - var testImage = KtxLoader.TestDecompressBc1; - var decoder = new BcDecoder - { - Options = { IsParallel = false } - }; - - var expectedTotal = 0; - - for (var i = 0; i < testImage.header.NumberOfMipmapLevels; i++) - { - expectedTotal += decoder.GetBlockCount((int)testImage.MipMaps[i].Width, (int)testImage.MipMaps[i].Height); - } - - await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false); - } - - [Fact] - public async void DecodeProgressReportParallelOneMip() - { - var testImage = KtxLoader.TestDecompressBc1; - var decoder = new BcDecoder - { - Options = { IsParallel = true } - }; - - var expectedTotal = decoder.GetBlockCount((int)testImage.MipMaps[0].Width, (int)testImage.MipMaps[0].Height); - - await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true); - } - - [Fact] - public async void DecodeProgressReportNonParallelOneMip() - { - var testImage = KtxLoader.TestDecompressBc1; - var decoder = new BcDecoder - { - Options = { IsParallel = false } - }; - - var expectedTotal = decoder.GetBlockCount((int)testImage.MipMaps[0].Width, (int)testImage.MipMaps[0].Height); - - await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true); - } - - [Fact] - public async void DecodeProgressReportParallelDds() - { - var testImage = DdsLoader.TestDecompressBc1; - var decoder = new BcDecoder - { - Options = { IsParallel = true } - }; - - var expectedTotal = 0; - - for (var i = 0; i < testImage.header.dwMipMapCount; i++) - { - expectedTotal += decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[i].Width, (int)testImage.Faces[0].MipMaps[i].Height); - } - - await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false); - } - - [Fact] - public async void DecodeProgressReportNonParallelDds() - { - var testImage = DdsLoader.TestDecompressBc1; - var decoder = new BcDecoder - { - Options = { IsParallel = false } - }; - - var expectedTotal = 0; - - for (var i = 0; i < testImage.header.dwMipMapCount; i++) - { - expectedTotal += decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[i].Width, (int)testImage.Faces[0].MipMaps[i].Height); - } - - await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false); - } - - [Fact] - public async void DecodeProgressReportParallelOneMipDds() - { - var testImage = DdsLoader.TestDecompressBc1; - var decoder = new BcDecoder - { - Options = { IsParallel = true } - }; - - var expectedTotal = decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[0].Width, (int)testImage.Faces[0].MipMaps[0].Height); - - await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true); - } - - [Fact] - public async void DecodeProgressReportNonParallelOneMipDds() - { - var testImage = DdsLoader.TestDecompressBc1; - var decoder = new BcDecoder - { - Options = { IsParallel = false } - }; - - var expectedTotal = decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[0].Width, (int)testImage.Faces[0].MipMaps[0].Height); - - await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true); - } - - [Fact] - public async void EncodeProgressReportParallelKtx() - { - var testImage = ImageLoader.TestBlur1; - var encoder = new BcEncoder - { - Options = { IsParallel = true }, - OutputOptions = { FileFormat = OutputFileFormat.Ktx } - }; - - var expectedTotal = 0; - - for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage); i++) - { - encoder.CalculateMipMapSize(testImage, i, out var mW, out var mH); - expectedTotal += encoder.GetBlockCount(mW, mH); - } - - await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); - } - - [Fact] - public async void EncodeProgressReportNonParallelKtx() - { - var testImage = ImageLoader.TestBlur1; - var encoder = new BcEncoder - { - Options = { IsParallel = false }, - OutputOptions = { FileFormat = OutputFileFormat.Ktx } - }; - - var expectedTotal = 0; - - for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage); i++) - { - encoder.CalculateMipMapSize(testImage, i, out var mW, out var mH); - expectedTotal += encoder.GetBlockCount(mW, mH); - } - - await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); - } - - [Fact] - public async void EncodeProgressReportParallelOneMipKtx() - { - var testImage = ImageLoader.TestBlur1; - var encoder = new BcEncoder - { - Options = { IsParallel = true }, - OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Ktx } - }; - - var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height); - - await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); - } - - [Fact] - public async void EncodeProgressReportNonParallelOneMipKtx() - { - var testImage = ImageLoader.TestBlur1; - var encoder = new BcEncoder - { - Options = { IsParallel = false }, - OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Ktx } - }; - - var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height); - - await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); - } - - [Fact] - public async void EncodeProgressReportParallelDds() - { - var testImage = ImageLoader.TestBlur1; - var encoder = new BcEncoder - { - Options = { IsParallel = true }, - OutputOptions = { FileFormat = OutputFileFormat.Dds } - }; - - var expectedTotal = 0; - - for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage); i++) - { - encoder.CalculateMipMapSize(testImage, i, out var mW, out var mH); - expectedTotal += encoder.GetBlockCount(mW, mH); - } - - await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); - } - - [Fact] - public async void EncodeProgressReportNonParallelDds() - { - var testImage = ImageLoader.TestBlur1; - var encoder = new BcEncoder - { - Options = { IsParallel = false }, - OutputOptions = { FileFormat = OutputFileFormat.Dds } - }; - - var expectedTotal = 0; - - for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage); i++) - { - encoder.CalculateMipMapSize(testImage, i, out var mW, out var mH); - expectedTotal += encoder.GetBlockCount(mW, mH); - } - - await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); - } - - [Fact] - public async void EncodeProgressReportParallelOneMipDds() - { - var testImage = ImageLoader.TestBlur1; - var encoder = new BcEncoder - { - Options = { IsParallel = true }, - OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Dds } - }; - - var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height); - - await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); - } - - [Fact] - public async void EncodeProgressReportNonParallelOneMipDds() - { - var testImage = ImageLoader.TestBlur1; - var encoder = new BcEncoder - { - Options = { IsParallel = false }, - OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Dds } - }; - - var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height); - - await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); - } - } - - class SynchronousProgress : IProgress - { - private readonly Action handler; - - public SynchronousProgress(Action handler) - { - this.handler = handler; - } - - public void Report(T value) - { - handler(value); - } - } -} diff --git a/BCnEncTests/RawTests.cs b/BCnEncTests/RawTests.cs deleted file mode 100644 index 8bfc6f8..0000000 --- a/BCnEncTests/RawTests.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System.IO; -using BCnEncoder.Decoder; -using BCnEncoder.Encoder; -using BCnEncoder.ImageSharp; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using SixLabors.ImageSharp.Processing; -using Xunit; - -namespace BCnEncTests -{ - public class RawTests - { - [Fact] - public void EncodeDecode() - { - var inputImage = ImageLoader.TestGradient1; - var decoder = new BcDecoder(); - var encoder = new BcEncoder - { - OutputOptions = { Quality = CompressionQuality.BestQuality } - }; - - var encodedRawBytes = encoder.EncodeToRawBytes(inputImage); - var decodedImage = decoder.DecodeRawToImageRgba32(encodedRawBytes[0], inputImage.Width, inputImage.Height, CompressionFormat.Bc1); - - var originalPixels = TestHelper.GetSinglePixelArray(ImageLoader.TestGradient1); - var decodedPixels = TestHelper.GetSinglePixelArray(decodedImage); - - TestHelper.AssertPixelsEqual(originalPixels, decodedPixels, encoder.OutputOptions.Quality); - } - - [Fact] - public void EncodeDecodeStream() - { - var inputImage = ImageLoader.TestGradient1; - var decoder = new BcDecoder(); - var encoder = new BcEncoder - { - OutputOptions = { Quality = CompressionQuality.BestQuality } - }; - - var encodedRawBytes = encoder.EncodeToRawBytes(inputImage); - - using var ms = new MemoryStream(encodedRawBytes[0]); - - Assert.Equal(0, ms.Position); - - var decodedImage = decoder.DecodeRawToImageRgba32(ms, inputImage.Width, inputImage.Height, CompressionFormat.Bc1); - - var originalPixels = TestHelper.GetSinglePixelArray(inputImage); - var decodedPixels = TestHelper.GetSinglePixelArray(decodedImage); - - TestHelper.AssertPixelsEqual(originalPixels, decodedPixels, encoder.OutputOptions.Quality); - } - - - [Fact] - public void EncodeDecodeAllMipMapsStream() - { - var inputImage = ImageLoader.TestGradient1; - var decoder = new BcDecoder(); - var encoder = new BcEncoder - { - OutputOptions = - { - Quality = CompressionQuality.BestQuality, - GenerateMipMaps = true, - MaxMipMapLevel = 0 - } - }; - - using var ms = new MemoryStream(); - - var encodedRawBytes = encoder.EncodeToRawBytes(inputImage); - - var mipLevels = encoder.CalculateNumberOfMipLevels(inputImage); - Assert.True(mipLevels > 1); - - for (var i = 0; i < mipLevels; i++) - { - ms.Write(encodedRawBytes[i]); - } - - ms.Position = 0; - Assert.Equal(0, ms.Position); - - for (var i = 0; i < mipLevels; i++) - { - encoder.CalculateMipMapSize(inputImage, i, out var mipWidth, out var mipHeight); - using var resized = inputImage.Clone(x => x.Resize(mipWidth, mipHeight)); - - var decodedImage = decoder.DecodeRawToImageRgba32(ms, mipWidth, mipHeight, CompressionFormat.Bc1); - - var originalPixels = TestHelper.GetSinglePixelArray(resized); - var decodedPixels = TestHelper.GetSinglePixelArray(decodedImage); - - TestHelper.AssertPixelsEqual(originalPixels, decodedPixels, encoder.OutputOptions.Quality); - } - - encoder.CalculateMipMapSize(inputImage, mipLevels - 1, out var lastMWidth, out var lastMHeight); - Assert.Equal(1, lastMWidth); - Assert.Equal(1, lastMHeight); - } - } -} diff --git a/BCnEncTests/RepackFiles.cs b/BCnEncTests/RepackFiles.cs new file mode 100644 index 0000000..432ff49 --- /dev/null +++ b/BCnEncTests/RepackFiles.cs @@ -0,0 +1,134 @@ +using System.IO; +using BCnEncoder.TextureFormats; +using BCnEncTests.Support; +using Xunit; + +namespace BCnEncTests; + +public class RepackFiles +{ + [Theory] + [InlineData("testktx/raw-bc1-rgb.ktx", "testktx/raw-bc1-rgb-linear.ktx", GlInternalFormat.GlCompressedRgbS3TcDxt1Ext)] + [InlineData("testktx/raw-bc1-rgb.ktx", "testktx/raw-bc1-rgb-srgb.ktx", GlInternalFormat.GlCompressedSrgbS3TcDxt1Ext)] + [InlineData("testktx/raw-bc1-rgba.ktx", "testktx/raw-bc1-rgba-linear.ktx", GlInternalFormat.GlCompressedRgbaS3TcDxt1Ext)] + [InlineData("testktx/raw-bc1-rgba.ktx", "testktx/raw-bc1-rgba-srgb.ktx", GlInternalFormat.GlCompressedSrgbAlphaS3TcDxt1Ext)] + [InlineData("testktx/raw-bc2.ktx", "testktx/raw-bc2-linear.ktx", GlInternalFormat.GlCompressedRgbaS3TcDxt3Ext)] + [InlineData("testktx/raw-bc2.ktx", "testktx/raw-bc2-srgb.ktx", GlInternalFormat.GlCompressedSrgbAlphaS3TcDxt3Ext)] + [InlineData("testktx/raw-bc3.ktx", "testktx/raw-bc3-linear.ktx", GlInternalFormat.GlCompressedRgbaS3TcDxt5Ext)] + [InlineData("testktx/raw-bc3.ktx", "testktx/raw-bc3-srgb.ktx", GlInternalFormat.GlCompressedSrgbAlphaS3TcDxt5Ext)] + [InlineData("testktx/raw-bc7.ktx", "testktx/raw-bc7-linear.ktx", GlInternalFormat.GlCompressedRgbaBptcUnormArb)] + [InlineData("testktx/raw-bc7.ktx", "testktx/raw-bc7-srgb.ktx", GlInternalFormat.GlCompressedSrgbAlphaBptcUnormArb)] + [InlineData("testktx/raw-etc1.ktx", "testktx/raw-etc1-linear.ktx", GlInternalFormat.GlEtc1Rgb8Oes)] + [InlineData("testktx/raw-etc2-rgb.ktx", "testktx/raw-etc2-rgb-linear.ktx", GlInternalFormat.GlCompressedRgb8Etc2)] + [InlineData("testktx/raw-etc2-rgb.ktx", "testktx/raw-etc2-rgb-srgb.ktx", GlInternalFormat.GlCompressedSrgb8Etc2)] + [InlineData("testktx/raw-etc2-rgb-a1.ktx", "testktx/raw-etc2-rgb-a1-linear.ktx", GlInternalFormat.GlCompressedRgb8PunchthroughAlpha1Etc2)] + [InlineData("testktx/raw-etc2-rgb-a1.ktx", "testktx/raw-etc2-rgb-a1-srgb.ktx", GlInternalFormat.GlCompressedSrgb8PunchthroughAlpha1Etc2)] + [InlineData("testktx/raw-etc2-rgba.ktx", "testktx/raw-etc2-rgba-linear.ktx", GlInternalFormat.GlCompressedRgba8Etc2Eac)] + [InlineData("testktx/raw-etc2-rgba.ktx", "testktx/raw-etc2-rgba-srgb.ktx", GlInternalFormat.GlCompressedSrgb8Alpha8Etc2Eac)] + [InlineData("testktx/raw-astc-4x4.ktx", "testktx/raw-astc-4x4-linear.ktx", GlInternalFormat.GlCompressedRgbaAstc4X4Khr)] + [InlineData("testktx/raw-astc-4x4.ktx", "testktx/raw-astc-4x4-srgb.ktx", GlInternalFormat.GlCompressedSrgb8Alpha8Astc4X4Khr)] + + [InlineData("testktx/raw-astc-4x4.ktx", "testktx/raw-astc-4x4-linear.ktx", GlInternalFormat.GlCompressedRgbaAstc4X4Khr)] + [InlineData("testktx/raw-astc-4x4.ktx", "testktx/raw-astc-4x4-srgb.ktx", GlInternalFormat.GlCompressedSrgb8Alpha8Astc4X4Khr)] + + [InlineData("testktx/raw-astc-5x4.ktx", "testktx/raw-astc-5x4-linear.ktx", GlInternalFormat.GlCompressedRgbaAstc5X4Khr)] + [InlineData("testktx/raw-astc-5x4.ktx", "testktx/raw-astc-5x4-srgb.ktx", GlInternalFormat.GlCompressedSrgb8Alpha8Astc5X4Khr)] + [InlineData("testktx/raw-astc-5x5.ktx", "testktx/raw-astc-5x5-linear.ktx", GlInternalFormat.GlCompressedRgbaAstc5X5Khr)] + [InlineData("testktx/raw-astc-5x5.ktx", "testktx/raw-astc-5x5-srgb.ktx", GlInternalFormat.GlCompressedSrgb8Alpha8Astc5X5Khr)] + + [InlineData("testktx/raw-astc-6x5.ktx", "testktx/raw-astc-6x5-linear.ktx", GlInternalFormat.GlCompressedRgbaAstc6X5Khr)] + [InlineData("testktx/raw-astc-6x5.ktx", "testktx/raw-astc-6x5-srgb.ktx", GlInternalFormat.GlCompressedSrgb8Alpha8Astc6X5Khr)] + [InlineData("testktx/raw-astc-6x6.ktx", "testktx/raw-astc-6x6-linear.ktx", GlInternalFormat.GlCompressedRgbaAstc6X6Khr)] + [InlineData("testktx/raw-astc-6x6.ktx", "testktx/raw-astc-6x6-srgb.ktx", GlInternalFormat.GlCompressedSrgb8Alpha8Astc6X6Khr)] + + [InlineData("testktx/raw-astc-8x5.ktx", "testktx/raw-astc-8x5-linear.ktx", GlInternalFormat.GlCompressedRgbaAstc8X5Khr)] + [InlineData("testktx/raw-astc-8x5.ktx", "testktx/raw-astc-8x5-srgb.ktx", GlInternalFormat.GlCompressedSrgb8Alpha8Astc8X5Khr)] + [InlineData("testktx/raw-astc-8x6.ktx", "testktx/raw-astc-8x6-linear.ktx", GlInternalFormat.GlCompressedRgbaAstc8X6Khr)] + [InlineData("testktx/raw-astc-8x6.ktx", "testktx/raw-astc-8x6-srgb.ktx", GlInternalFormat.GlCompressedSrgb8Alpha8Astc8X6Khr)] + [InlineData("testktx/raw-astc-8x8.ktx", "testktx/raw-astc-8x8-linear.ktx", GlInternalFormat.GlCompressedRgbaAstc8X8Khr)] + [InlineData("testktx/raw-astc-8x8.ktx", "testktx/raw-astc-8x8-srgb.ktx", GlInternalFormat.GlCompressedSrgb8Alpha8Astc8X8Khr)] + + [InlineData("testktx/raw-astc-10x5.ktx", "testktx/raw-astc-10x5-linear.ktx", GlInternalFormat.GlCompressedRgbaAstc10X5Khr)] + [InlineData("testktx/raw-astc-10x5.ktx", "testktx/raw-astc-10x5-srgb.ktx", GlInternalFormat.GlCompressedSrgb8Alpha8Astc10X5Khr)] + [InlineData("testktx/raw-astc-10x6.ktx", "testktx/raw-astc-10x6-linear.ktx", GlInternalFormat.GlCompressedRgbaAstc10X6Khr)] + [InlineData("testktx/raw-astc-10x6.ktx", "testktx/raw-astc-10x6-srgb.ktx", GlInternalFormat.GlCompressedSrgb8Alpha8Astc10X6Khr)] + [InlineData("testktx/raw-astc-10x8.ktx", "testktx/raw-astc-10x8-linear.ktx", GlInternalFormat.GlCompressedRgbaAstc10X8Khr)] + [InlineData("testktx/raw-astc-10x8.ktx", "testktx/raw-astc-10x8-srgb.ktx", GlInternalFormat.GlCompressedSrgb8Alpha8Astc10X8Khr)] + [InlineData("testktx/raw-astc-10x10.ktx", "testktx/raw-astc-10x10-linear.ktx", GlInternalFormat.GlCompressedRgbaAstc10X10Khr)] + [InlineData("testktx/raw-astc-10x10.ktx", "testktx/raw-astc-10x10-srgb.ktx", GlInternalFormat.GlCompressedSrgb8Alpha8Astc10X10Khr)] + + [InlineData("testktx/raw-astc-12x10.ktx", "testktx/raw-astc-12x10-linear.ktx", GlInternalFormat.GlCompressedRgbaAstc12X10Khr)] + [InlineData("testktx/raw-astc-12x10.ktx", "testktx/raw-astc-12x10-srgb.ktx", GlInternalFormat.GlCompressedSrgb8Alpha8Astc12X10Khr)] + [InlineData("testktx/raw-astc-12x12.ktx", "testktx/raw-astc-12x12-linear.ktx", GlInternalFormat.GlCompressedRgbaAstc12X12Khr)] + [InlineData("testktx/raw-astc-12x12.ktx", "testktx/raw-astc-12x12-srgb.ktx", GlInternalFormat.GlCompressedSrgb8Alpha8Astc12X12Khr)] + + [InlineData("testktx/raw-r8g8b8.ktx", "testktx/raw-r8g8b8-linear.ktx", GlInternalFormat.GlRgb8)] + [InlineData("testktx/raw-r8g8b8.ktx", "testktx/raw-r8g8b8-srgb.ktx", GlInternalFormat.GlSrgb8)] + [InlineData("testktx/raw-r8g8b8a8.ktx", "testktx/raw-r8g8b8a8-linear.ktx", GlInternalFormat.GlRgba8)] + [InlineData("testktx/raw-r8g8b8a8.ktx", "testktx/raw-r8g8b8a8-srgb.ktx", GlInternalFormat.GlSrgb8Alpha8)] + public void RepackKtx(string input, string output, GlInternalFormat newFormat) + { + string iPath = Path.Combine(TestHelper.GetProjectRoot(), "testImages", input); + string oPath = Path.Combine(TestHelper.GetProjectRoot(), "testImages", output); + + KtxFile ktx; + using (var ifs = File.OpenRead(iPath)) + { + ktx = KtxFile.Load(ifs); + } + ktx.header.GlInternalFormat = newFormat; + + using var ofs = File.OpenWrite(oPath); + ktx.WriteToStream(ofs); + } + + [Theory] + [InlineData("testdds/raw-bc1-rgb-dx10.dds")] + [InlineData("testdds/raw-bc1-rgba-dx10.dds")] + [InlineData("testdds/raw-bc2-dx10.dds")] + [InlineData("testdds/raw-bc3-dx10.dds")] + [InlineData("testdds/raw-bc4-dx10.dds")] + [InlineData("testdds/raw-bc4s-dx10.dds")] + [InlineData("testdds/raw-bc5-dx10.dds")] + [InlineData("testdds/raw-bc5s-dx10.dds")] + [InlineData("testdds/raw-r4g4-dx10.dds")] + [InlineData("testdds/raw-r5g6b5-dx10.dds")] + [InlineData("testdds/raw-r8-dx10.dds")] + [InlineData("testdds/raw-r8g8-dx10.dds")] + [InlineData("testdds/raw-r8g8b8a8-dx10.dds")] + [InlineData("testdds/raw-b8g8r8a8-dx10.dds")] + [InlineData("testdds/raw-a1r5g5b5-dx10.dds")] + [InlineData("testdds/raw-a2b10g10r10-dx10.dds")] + [InlineData("testdds/raw-a4r4g4b4-dx10.dds")] + [InlineData("testdds/raw-r16-dx10.dds")] + [InlineData("testdds/raw-r16-float-dx10.dds")] + [InlineData("testdds/raw-r16g16-dx10.dds")] + [InlineData("testdds/raw-r16g16-float-dx10.dds")] + [InlineData("testdds/raw-r16g16b16a16-dx10.dds")] + [InlineData("testdds/raw-r16g16b16a16-float-dx10.dds")] + [InlineData("testdds/raw-r32-float-dx10.dds")] + [InlineData("testdds/raw-r32g32-float-dx10.dds")] + [InlineData("testdds/raw-r32g32b32a32-float-dx10.dds")] + + public void RepackDdsDx9(string input) + { + string output = input.Replace("dx10", "dx9"); + Assert.NotEqual(input, output); + + string iPath = Path.Combine(TestHelper.GetProjectRoot(), "testImages", input); + string oPath = Path.Combine(TestHelper.GetProjectRoot(), "testImages", output); + + using var ifs = File.OpenRead(iPath); + var dds = DdsFile.Load(ifs); + var texData = dds.ToTextureData(); + + DdsFile.PreferredMinApiLevel = DdsFile.DxApiLevel.Dx9; + + var dx9dds = texData.AsTexture(); + + Assert.False(dx9dds.header.ddsPixelFormat.IsDxt10Format); + + using var ofs = File.OpenWrite(oPath); + dx9dds.WriteToStream(ofs); + } +} diff --git a/BCnEncTests/SingleBlockTests.cs b/BCnEncTests/SingleBlockTests.cs deleted file mode 100644 index 29eab5f..0000000 --- a/BCnEncTests/SingleBlockTests.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; -using BCnEncoder.Decoder; -using BCnEncoder.Encoder; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using Microsoft.Toolkit.HighPerformance; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -namespace BCnEncTests -{ - public class SingleBlockTests - { - - [Theory] - [InlineData(CompressionFormat.Bc1, CompressionQuality.Fast)] - [InlineData(CompressionFormat.Bc1, CompressionQuality.Balanced)] - [InlineData(CompressionFormat.Bc3, CompressionQuality.Fast)] - [InlineData(CompressionFormat.Bc3, CompressionQuality.Balanced)] - [InlineData(CompressionFormat.Bc7, CompressionQuality.Fast)] - public void SingleBlockEncodeDecodeStream(CompressionFormat format, CompressionQuality quality) - { - var testImage = ImageLoader.TestAlpha1; - - var encoder = new BcEncoder() - { - OutputOptions = - { - Format = format, - Quality = quality - } - }; - - var pixels = TestHelper.GetSinglePixelArray(testImage); - var colors = MemoryMarshal.Cast(pixels) - .AsSpan2D(testImage.Height, testImage.Width); - - var ms = new MemoryStream(); - - for (var y = 0; y < testImage.Height; y+=4) - { - for (var x = 0; x < testImage.Width; x+=4) - { - encoder.EncodeBlock(colors.Slice(y, x, 4, 4), ms); - } - } - - Assert.Equal(ms.Position, encoder.GetBlockSize() * encoder.GetBlockCount(testImage.Width, testImage.Height)); - - ms.Position = 0; - - var decoder = new BcDecoder(); - - var decoded = new ColorRgba32[testImage.Height, testImage.Width]; - - for (var y = 0; y < testImage.Height; y += 4) - { - for (var x = 0; x < testImage.Width; x += 4) - { - decoder.DecodeBlock(ms, format, - decoded.AsSpan2D().Slice(y, x, 4, 4)); - } - } - - colors.TryGetSpan(out var oPixels); - decoded.AsSpan2D().TryGetSpan(out var dPixels); - var psnr = ImageQuality.PeakSignalToNoiseRatio(oPixels, dPixels, - format != CompressionFormat.Bc1); - - TestHelper.AssertPSNR(psnr, quality); - } - - [Theory] - [InlineData(CompressionFormat.Bc1, CompressionQuality.Fast)] - [InlineData(CompressionFormat.Bc1, CompressionQuality.Balanced)] - [InlineData(CompressionFormat.Bc3, CompressionQuality.Fast)] - [InlineData(CompressionFormat.Bc3, CompressionQuality.Balanced)] - [InlineData(CompressionFormat.Bc7, CompressionQuality.Fast)] - public void SingleBlockEncodeDecode(CompressionFormat format, CompressionQuality quality) - { - var testImage = ImageLoader.TestAlpha1; - - var encoder = new BcEncoder() - { - OutputOptions = - { - Format = format, - Quality = quality - } - }; - - var pixels = TestHelper.GetSinglePixelArray(testImage); - var colors = MemoryMarshal.Cast(pixels) - .AsSpan2D(testImage.Height, testImage.Width); - - Span buffer = new byte[encoder.GetBlockSize() * encoder.GetBlockCount(testImage.Width, testImage.Height)]; - - var blockIndex = 0; - for (var y = 0; y < testImage.Height; y += 4) - { - for (var x = 0; x < testImage.Width; x += 4) - { - var bytes = encoder.EncodeBlock(colors.Slice(y, x, 4, 4)); - bytes.CopyTo(buffer.Slice(blockIndex * encoder.GetBlockSize())); - blockIndex++; - } - } - - var decoder = new BcDecoder(); - - var decoded = new ColorRgba32[testImage.Height, testImage.Width]; - - blockIndex = 0; - for (var y = 0; y < testImage.Height; y += 4) - { - for (var x = 0; x < testImage.Width; x += 4) - { - - decoder.DecodeBlock( - buffer.Slice( - blockIndex * decoder.GetBlockSize(format), - decoder.GetBlockSize(format) - ), - format, - decoded.AsSpan2D().Slice(y, x, 4, 4)); - blockIndex++; - } - } - - colors.TryGetSpan(out var oPixels); - decoded.AsSpan2D().TryGetSpan(out var dPixels); - var psnr = ImageQuality.PeakSignalToNoiseRatio(oPixels, dPixels, - format != CompressionFormat.Bc1); - - TestHelper.AssertPSNR(psnr, quality); - } - } -} diff --git a/BCnEncTests/Support/HdrLoader.cs b/BCnEncTests/Support/HdrLoader.cs deleted file mode 100644 index 519f02e..0000000 --- a/BCnEncTests/Support/HdrLoader.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; - -namespace BCnEncTests.Support -{ - public static class HdrLoader - { - public static HdrImage TestHdrKiara { get; } = HdrImage.Read("../../../testImages/test_hdr_kiara.hdr"); - public static HdrImage TestHdrProbe { get; } = HdrImage.Read("../../../testImages/test_hdr_probe.hdr"); - public static Image ReferenceKiara { get; } = ImageLoader.LoadTestImage("../../../testImages/test_hdr_kiara.png"); - public static DdsFile TestHdrKiaraDds { get; } = - DdsLoader.LoadDdsFile("../../../testImages/test_hdr_kiara_bc6h.dds"); - public static KtxFile TestHdrKiaraKtx { get; } = - KtxLoader.LoadKtxFile("../../../testImages/test_hdr_kiara_bc6h_ktx.ktx"); - - } -} diff --git a/BCnEncTests/Support/ImageLoader.cs b/BCnEncTests/Support/ImageLoader.cs index 3a825d2..ff5702d 100644 --- a/BCnEncTests/Support/ImageLoader.cs +++ b/BCnEncTests/Support/ImageLoader.cs @@ -1,26 +1,113 @@ +using System; +using System.Collections.Generic; using System.IO; +using System.Linq; +using BCnEncoder.ImageSharp; using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; +using BCnEncoder.TextureFormats; using SixLabors.ImageSharp; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; namespace BCnEncTests.Support { public static class ImageLoader { - public static Image TestDiffuse1 { get; } = LoadTestImage("../../../testImages/test_diffuse_1_512.jpg"); - public static Image TestBlur1 { get; } = LoadTestImage("../../../testImages/test_blur_1_512.jpg"); - public static Image TestNormal1 { get; } = LoadTestImage("../../../testImages/test_normal_1_512.jpg"); - public static Image TestHeight1 { get; } = LoadTestImage("../../../testImages/test_height_1_512.jpg"); - public static Image TestGradient1 { get; } = LoadTestImage("../../../testImages/test_gradient_1_512.jpg"); - - public static Image TestTransparentSprite1 { get; } = LoadTestImage("../../../testImages/test_transparent.png"); - public static Image TestAlphaGradient1 { get; } = LoadTestImage("../../../testImages/test_alphagradient_1_512.png"); - public static Image TestAlpha1 { get; } = LoadTestImage("../../../testImages/test_alpha_1_512.png"); - public static Image TestRedGreen1 { get; } = LoadTestImage("../../../testImages/test_red_green_1_64.png"); - public static Image TestRgbHard1 { get; } = LoadTestImage("../../../testImages/test_rgb_hard_1.png"); - public static Image TestLenna { get; } = LoadTestImage("../../../testImages/test_lenna_512.png"); - public static Image TestDecodingBc5Reference { get; } = LoadTestImage("../../../testImages/decoding_dds_bc5_reference.png"); + public const string TestImageRawFolder = "../../../testImages/raw"; + public const string TestImageEncodedFolder = "../../../testImages/encoded"; + public const string TestImageReferenceFolder = "../../../testImages/reference"; + + public static (string, System.Type)[] EncodedFileEndingPatterns => new (string, Type)[] + { + ("ktx", typeof(KtxFile)), + ("dds", typeof(DdsFile)), + ("hdr", typeof(RadianceFile)) + }; + + public static readonly List<(string, Exception)> ReadThrownExceptions = new List<(string, Exception)>(); + + public static readonly IReadOnlyDictionary> TestRawImages = FindRawTestImages(); + public static readonly IReadOnlyDictionary)> TestEncodedImages = FindEncodedTestImages(); + + + private static Dictionary> FindRawTestImages() + { + var rawImages = new Dictionary>(); + + if (!new DirectoryInfo(TestImageRawFolder).Exists) + throw new FileNotFoundException($"Raw test folder does not exist! {TestImageRawFolder}"); + + foreach (var file in Directory.EnumerateFiles(TestImageRawFolder, "*.png", new EnumerationOptions + { + IgnoreInaccessible = true, + RecurseSubdirectories = false, + AttributesToSkip = FileAttributes.Directory | FileAttributes.System | FileAttributes.Hidden, + })) + { + try + { + var img = Image.Load(file); + + var name = Path.GetFileNameWithoutExtension(file); + rawImages.Add(name, img); + } + catch (Exception e) + { + ReadThrownExceptions.Add((file, e)); + } + } + + return rawImages; + } + + private static Dictionary)> FindEncodedTestImages() + { + var encodedImages = new Dictionary)>(); + + if (!new DirectoryInfo(TestImageEncodedFolder).Exists) + throw new FileNotFoundException($"Encoded test folder does not exist! {TestImageEncodedFolder}"); + + if (!new DirectoryInfo(TestImageReferenceFolder).Exists) + throw new FileNotFoundException($"Reference test folder does not exist! {TestImageReferenceFolder}"); + + foreach (var file in Directory.EnumerateFiles(TestImageEncodedFolder, "*.*", new EnumerationOptions + { + IgnoreInaccessible = true, + RecurseSubdirectories = false, + AttributesToSkip = FileAttributes.Directory | FileAttributes.System | FileAttributes.Hidden + })) + { + try + { + var fileType = EncodedFileEndingPatterns.FirstOrDefault(x => file.EndsWith(x.Item1)).Item2; + if (fileType == null) + { + continue; + } + + var texture = Activator.CreateInstance(fileType) as ITextureFileFormat; + using var fs = File.OpenRead(file); + + texture.ReadFromStream(fs); + + var referenceFileName = Path.Join(TestImageReferenceFolder, + Path.GetFileNameWithoutExtension(file) + ".png"); + + var reference = Image.Load(referenceFileName); + var name = Path.GetFileNameWithoutExtension(file); + + encodedImages.Add(name, (texture, reference)); + } + catch (Exception e) + { + ReadThrownExceptions.Add((file, e)); + } + } + + return encodedImages; + } public static Image[] TestCubemap { get; } = { LoadTestImage("../../../testImages/cubemap/right.png"), @@ -31,49 +118,30 @@ public static class ImageLoader LoadTestImage("../../../testImages/cubemap/forward.png") }; + public static Image[] TestCubemap2 { get; } = { + LoadTestImage("../../../testImages/cubemap/cube_2_posx.jpg"), + LoadTestImage("../../../testImages/cubemap/cube_2_negx.jpg"), + LoadTestImage("../../../testImages/cubemap/cube_2_posy.jpg"), + LoadTestImage("../../../testImages/cubemap/cube_2_negy.jpg"), + LoadTestImage("../../../testImages/cubemap/cube_2_posz.jpg"), + LoadTestImage("../../../testImages/cubemap/cube_2_negz.jpg") + }; + + public static RadianceFile TestHdr1 => TestEncodedImages["hdr_1_rgbe"].Item1 as RadianceFile; + public static RadianceFile TestHdr2 => TestEncodedImages["hdr_2_rgbe"].Item1 as RadianceFile; + + public static Image TestLdrRgba => TestRawImages["blocks"]; + internal static Image LoadTestImage(string filename) { return Image.Load(filename); } - } - public static class DdsLoader - { - public const string TestDecompressBc1Name = "../../../testImages/test_decompress_bc1.dds"; - public const string TestDecompressBc1AName = "../../../testImages/test_decompress_bc1a.dds"; - public const string TestDecompressBc7Name = "../../../testImages/test_decompress_bc7.dds"; - public const string TestDecompressBc5Name = "../../../testImages/decoding_dds_bc5.dds"; - public const string TestDecompressRgbaName = "../../../testImages/test_decompress_rgba.dds"; - - public static DdsFile TestDecompressBc1 { get; } = LoadDdsFile(TestDecompressBc1Name); - public static DdsFile TestDecompressBc1A { get; } = LoadDdsFile(TestDecompressBc1AName); - public static DdsFile TestDecompressBc5 { get; } = LoadDdsFile(TestDecompressBc5Name); - public static DdsFile TestDecompressBc7 { get; } = LoadDdsFile(TestDecompressBc7Name); - public static DdsFile TestDecompressRgba { get; } = LoadDdsFile(TestDecompressRgbaName); - - internal static DdsFile LoadDdsFile(string filename) - { - using var fs = File.OpenRead(filename); - return DdsFile.Load(fs); - } - } - - public static class KtxLoader - { - public static KtxFile TestDecompressBc1 { get; } = LoadKtxFile("../../../testImages/test_decompress_bc1.ktx"); - public static KtxFile TestDecompressBc1A { get; } = LoadKtxFile("../../../testImages/test_decompress_bc1a.ktx"); - public static KtxFile TestDecompressBc2 { get; } = LoadKtxFile("../../../testImages/test_decompress_bc2.ktx"); - public static KtxFile TestDecompressBc3 { get; } = LoadKtxFile("../../../testImages/test_decompress_bc3.ktx"); - public static KtxFile TestDecompressBc4Unorm { get; } = LoadKtxFile("../../../testImages/test_decompress_bc4_unorm.ktx"); - public static KtxFile TestDecompressBc5Unorm { get; } = LoadKtxFile("../../../testImages/test_decompress_bc5_unorm.ktx"); - public static KtxFile TestDecompressBc7Rgb { get; } = LoadKtxFile("../../../testImages/test_decompress_bc7_rgb.ktx"); - public static KtxFile TestDecompressBc7Types { get; } = LoadKtxFile("../../../testImages/test_decompress_bc7_types.ktx"); - public static KtxFile TestDecompressBc7Unorm { get; } = LoadKtxFile("../../../testImages/test_decompress_bc7_unorm.ktx"); - - internal static KtxFile LoadKtxFile(string filename) + internal static BCnTextureData LoadTestData(string name) { - using var fs = File.OpenRead(filename); - return KtxFile.Load(fs); + return TestRawImages.ContainsKey(name) ? + TestRawImages[name].ToBCnTextureData() : + TestEncodedImages[name].Item1.ToTextureData(); } } } diff --git a/BCnEncTests/Support/ImageQuality.cs b/BCnEncTests/Support/ImageQuality.cs new file mode 100644 index 0000000..274b4fc --- /dev/null +++ b/BCnEncTests/Support/ImageQuality.cs @@ -0,0 +1,606 @@ +using System; +using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp; +using Xunit.Abstractions; +using BCnEncoder.Encoder; +using Xunit; + +namespace BCnEncTests.Support +{ + /// + /// Defines different texture types with their specific quality requirements. + /// + public enum TextureType + { + /// + /// Standard color/albedo maps. More tolerant of compression artifacts. + /// + Albedo, + + /// + /// Normal maps containing surface normal vectors. Very sensitive to compression. + /// + Normal, + + /// + /// Height/displacement maps. Sensitive to precision loss. + /// + Height, + + /// + /// Specular/metallic/roughness maps + /// + Specular, + + /// + /// High dynamic range textures. + /// + Hdr + } + + public class ImageQuality + { + /// + /// Calculates the Root Mean Square Error between two images. + /// + /// Original image + /// Processed/compressed image to compare + /// String indicating which channels to include in calculation ("rgba") + /// RMSE value (lower is better) + public static float CalculateRMSE(Image original, Image compressed, string channelMask = "rgb") + { + if (original.Width != compressed.Width || original.Height != compressed.Height) + { + throw new ArgumentException("Both images must have the same dimensions"); + } + + int length = original.Width * original.Height; + + bool useRed = channelMask.Contains("r", StringComparison.OrdinalIgnoreCase); + bool useGreen = channelMask.Contains("g", StringComparison.OrdinalIgnoreCase); + bool useBlue = channelMask.Contains("b", StringComparison.OrdinalIgnoreCase); + bool useAlpha = channelMask.Contains("a", StringComparison.OrdinalIgnoreCase); + + int activeChannels = 0; + if (useRed) activeChannels++; + if (useGreen) activeChannels++; + if (useBlue) activeChannels++; + if (useAlpha) activeChannels++; + + if (activeChannels == 0) + { + throw new ArgumentException("Channel mask must contain at least one valid channel (r, g, b, or a)", nameof(channelMask)); + } + + float error = 0; + for (int y = 0; y < original.Height; y++) + { + for (int x = 0; x < original.Width; x++) + { + var pixel = original[x, y]; + var otherPixel = compressed[x, y]; + + if (useRed) + { + var dr = otherPixel.R - pixel.R; + error += dr * dr; + } + if (useGreen) + { + var dg = otherPixel.G - pixel.G; + error += dg * dg; + } + if (useBlue) + { + var db = otherPixel.B - pixel.B; + error += db * db; + } + if (useAlpha) + { + var da = otherPixel.A - pixel.A; + error += da * da; + } + } + } + + return MathF.Sqrt(error / (activeChannels * length)); + } + + /// + /// Calculates the Peak Signal-to-Noise Ratio between two images. + /// PSNR = 20 * log10(1 / RMSE) for normalized [0,1] pixel values. + /// + /// Original image + /// Processed/compressed image to compare + /// String indicating which channels to include in calculation ("rgba") + /// PSNR in dB (higher is better). Returns positive infinity for identical images. + public static double CalculatePsnr(Image original, Image compressed, string channelMask = "rgb") + { + var rmse = CalculateRMSE(original, compressed, channelMask); + if (rmse == 0f) return double.PositiveInfinity; + return 20.0 * Math.Log10(1.0 / rmse); + } + + /// + /// Calculates the logarithmic Root Mean Square Error between two images. + /// This is especially useful for HDR content as it accounts for the perceptual differences at different luminance levels. + /// + /// Original image + /// Processed/compressed image to compare + /// String indicating which channels to include in calculation ("rgba") + /// Log-RMSE value (lower is better) + public static float CalculateLogRMSE(Image original, Image compressed, string channelMask = "rgb") + { + if (original.Width != compressed.Width || original.Height != compressed.Height) + { + throw new ArgumentException("Both images must have the same dimensions"); + } + + int length = original.Width * original.Height; + + bool useRed = channelMask.Contains("r", StringComparison.OrdinalIgnoreCase); + bool useGreen = channelMask.Contains("g", StringComparison.OrdinalIgnoreCase); + bool useBlue = channelMask.Contains("b", StringComparison.OrdinalIgnoreCase); + bool useAlpha = channelMask.Contains("a", StringComparison.OrdinalIgnoreCase); + + int activeChannels = 0; + if (useRed) activeChannels++; + if (useGreen) activeChannels++; + if (useBlue) activeChannels++; + if (useAlpha) activeChannels++; + + if (activeChannels == 0) + { + throw new ArgumentException("Channel mask must contain at least one valid channel (r, g, b, or a)", nameof(channelMask)); + } + + float error = 0; + for (int y = 0; y < original.Height; y++) + { + for (int x = 0; x < original.Width; x++) + { + var pixel = original[x, y]; + var otherPixel = compressed[x, y]; + + if (useRed) + { + var dr = Math.Sign(otherPixel.R) * MathF.Log(1 + MathF.Abs(otherPixel.R)) - + Math.Sign(pixel.R) * MathF.Log(1 + MathF.Abs(pixel.R)); + error += dr * dr; + } + + if (useGreen) + { + var dg = Math.Sign(otherPixel.G) * MathF.Log(1 + MathF.Abs(otherPixel.G)) - + Math.Sign(pixel.G) * MathF.Log(1 + MathF.Abs(pixel.G)); + error += dg * dg; + } + + if (useBlue) + { + var db = Math.Sign(otherPixel.B) * MathF.Log(1 + MathF.Abs(otherPixel.B)) - + Math.Sign(pixel.B) * MathF.Log(1 + MathF.Abs(pixel.B)); + error += db * db; + } + + if (useAlpha) + { + var da = Math.Sign(otherPixel.A) * MathF.Log(1 + MathF.Abs(otherPixel.A)) - + Math.Sign(pixel.A) * MathF.Log(1 + MathF.Abs(pixel.A)); + error += da * da; + } + } + } + + return MathF.Sqrt(error / (activeChannels * length)); + } + + /// + /// Calculates the L₂ norm (Euclidean distance) between normalized normal vectors. + /// This is more appropriate for normal maps than standard RMSE as it considers the directional nature of normals. + /// + /// Original normal map data + /// Compressed normal map data + /// Whether to use all three channels for the vector calculation + /// Average angular error between the normal vectors + public static float CalculateNormalVectorDifference(Image original, Image compressed, bool useRGB = true) + { + if (original.Width != compressed.Width || original.Height != compressed.Height) + { + throw new ArgumentException("Both images must have the same dimensions"); + } + + int length = original.Width * original.Height; + + float totalError = 0; + for (int y = 0; y < original.Height; y++) + { + for (int x = 0; x < original.Width; x++) + { + // Extract vector components - we need to transform from [0,1] to [-1,1] range + // Normal maps typically store normals as: RGB = (Normal.x+1, Normal.y+1, Normal.z+1)/2 + float origX = original[x, y].R * 2 - 1; + float origY = original[x, y].G * 2 - 1; + float origZ = useRGB ? (original[x, y].B * 2 - 1) : 0; + + float compX = compressed[x, y].R * 2 - 1; + float compY = compressed[x, y].G * 2 - 1; + float compZ = useRGB ? (compressed[x, y].B * 2 - 1) : 0; + + // If Z isn't used, we need to recompute it (assuming unit length vector) + if (!useRGB) + { + if (MathF.Abs(origX) <= 1.0f && MathF.Abs(origY) <= 1.0f) + { + origZ = MathF.Sqrt(MathF.Max(0, 1 - origX * origX - origY * origY)); + } + else + { + origZ = 0; // Invalid normal + } + + if (MathF.Abs(compX) <= 1.0f && MathF.Abs(compY) <= 1.0f) + { + compZ = MathF.Sqrt(MathF.Max(0, 1 - compX * compX - compY * compY)); + } + else + { + compZ = 0; // Invalid normal + } + } + + // Normalize vectors (they should already be normalized, but compression can introduce errors) + float origLength = MathF.Sqrt(origX * origX + origY * origY + origZ * origZ); + float compLength = MathF.Sqrt(compX * compX + compY * compY + compZ * compZ); + + // Avoid division by zero + if (origLength > float.Epsilon && compLength > float.Epsilon) + { + origX /= origLength; + origY /= origLength; + origZ /= origLength; + + compX /= compLength; + compY /= compLength; + compZ /= compLength; + + // Calculate squared L₂ norm (Euclidean distance) between the normalized vectors + float dx = origX - compX; + float dy = origY - compY; + float dz = origZ - compZ; + float vectorDiff = dx * dx + dy * dy + dz * dz; + + totalError += vectorDiff; + } + else + { + // If either vector is zero length, consider it a significant error + totalError += 2.0f; // Max possible squared distance between unit vectors + } + } + } + + // Return average error (square root of mean squared error) + // The theoretical maximum L₂ distance between two unit vectors is 2.0 + return MathF.Sqrt(totalError / length); + } + + /// + /// Asserts that the quality between two images meets the threshold for the specified texture type, + /// compression quality, and format. Automatically selects the appropriate metric based on texture type. + /// For lossless formats, enforces higher quality standards. + /// + /// The original image + /// The compressed/processed image to compare + /// Type of texture, which determines the quality metric and threshold + /// Compression quality used, which determines the expected threshold + /// Optional mask specifying which channels to include ("rgba"). Default is all channels. + /// The compression format used, which may affect quality expectations + /// Optional test output helper for writing results + public static void AssertImageQuality( + Image original, + Image compressed, + TextureType textureType, + CompressionQuality quality, + CompressionFormat compressionFormat, + string channelMask, + ITestOutputHelper output = null) + { + // Verify images have same dimensions + if (original.Width != compressed.Width || original.Height != compressed.Height) + { + throw new ArgumentException("Both images must have the same dimensions"); + } + + // Check if this is a lossless format - requires near-perfect quality + // Raw pixel formats (non-block-compressed) should be lossless + bool isLossless = !compressionFormat.IsBlockCompressedFormat(); + + if (isLossless) + { + output?.WriteLine($"Using lossless quality check for format: {compressionFormat}"); + } + + // Select appropriate metric and threshold based on texture type + switch (textureType) + { + case TextureType.Normal: + AssertNormalMapQuality(original, compressed, quality, channelMask, output, isLossless); + break; + + case TextureType.Height: + AssertHeightMapQuality(original, compressed, quality, channelMask, output, isLossless); + break; + + case TextureType.Hdr: + AssertHdrQuality(original, compressed, quality, channelMask, output, isLossless); + break; + + case TextureType.Specular: + AssertSpecularMapQuality(original, compressed, quality, channelMask, output, isLossless); + break; + + case TextureType.Albedo: + default: + AssertAlbedoQuality(original, compressed, quality, channelMask, output, isLossless); + break; + } + } + + // Individual quality assessment methods for each texture type + + private static void AssertAlbedoQuality( + Image original, + Image compressed, + CompressionQuality quality, + string channelMask, + ITestOutputHelper output, + bool isLossless = false) + { + bool useOkLab = channelMask == "rgb"; + if (useOkLab) + { + output.WriteLine("Using OkLab in MS-SSIM"); + } + + // For standard albedo maps, MS-SSIM is a good perceptual metric + StructuralSimilarityResult msssim = StructuralSimilarity.MultiScaleStructuralSimilarity(original, compressed, channelMask, useOkLab, false); + + // Thresholds with pattern matching - special case for lossless formats + // For average MS-SSIM + float avgThreshold = quality switch + { + _ when isLossless => 0.99f, // Near-perfect quality for lossless formats + CompressionQuality.Fast => 0.85f, // DirectXTex BC1: 0.971, PVRTexTool BC1: 0.956 + CompressionQuality.Balanced => 0.92f, // Higher than Fast, lower than BestQuality + CompressionQuality.BestQuality => 0.95f, // DirectXTex BC7: 0.984 + _ => 0.92f + }; + + // 5th percentile threshold - albedo textures can tolerate more artifacts than specular maps + float p5Threshold = quality switch + { + _ when isLossless => 0.97f, // Allow for small areas with minimal differences + CompressionQuality.Fast => 0.75f, // PVRTexTool BC1: 0.774 (with some margin) + CompressionQuality.Balanced => 0.82f, // Between Fast and BestQuality + CompressionQuality.BestQuality => 0.86f, // DirectXTex BC1: 0.865, BC7: 0.913 + _ => 0.82f + }; + + // 10th percentile threshold + float p10Threshold = quality switch + { + _ when isLossless => 0.98f, // Strict threshold for lossless formats + CompressionQuality.Fast => 0.83f, // PVRTexTool BC1: 0.847 (with some margin) + CompressionQuality.Balanced => 0.87f, // Between Fast and BestQuality + CompressionQuality.BestQuality => 0.90f, // DirectXTex BC1: 0.900, BC7: 0.939 + _ => 0.87f + }; + + if (isLossless) + { + output?.WriteLine("Using lossless format thresholds for albedo texture"); + } + + output?.WriteLine($"Albedo MS-SSIM: {msssim}, quality: {quality}"); + output?.WriteLine($"Thresholds - Average: {avgThreshold:F3}, 5th percentile: {p5Threshold:F3}, 10th percentile: {p10Threshold:F3}"); + + // Check average quality + Assert.True(msssim.Average >= avgThreshold, + $"Albedo average quality below threshold. MS-SSIM Avg: {msssim.Average:F4}, required: {avgThreshold:F4}"); + + // Check for bad spots using percentiles + Assert.True(msssim.Percentile5 >= p5Threshold, + $"Albedo has bad spots (5th percentile). MS-SSIM 5th percentile: {msssim.Percentile5:F4}, required: {p5Threshold:F4}"); + + Assert.True(msssim.Percentile10 >= p10Threshold, + $"Albedo has significant low-quality regions (10th percentile). MS-SSIM 10th percentile: {msssim.Percentile10:F4}, required: {p10Threshold:F4}"); + } + + private static void AssertNormalMapQuality( + Image original, + Image compressed, + CompressionQuality quality, + string channelMask, + ITestOutputHelper output, + bool isLossless = false) + { + // Determine if we should use all three channels (RGB) based on the channel mask + bool useRGB = channelMask.Contains("b", StringComparison.OrdinalIgnoreCase); + + // Calculate L₂ norm of difference between normalized vectors + float vectorDiff = CalculateNormalVectorDifference(original, compressed, useRGB); + + // For reference, also calculate standard RMSE + float rmse = CalculateRMSE(original, compressed, channelMask); + + // Threshold for vector difference in normal maps + // The theoretical maximum L₂ distance between unit vectors is 2.0 (opposite directions) + float threshold = quality switch + { + _ when isLossless => 0.01f, // Very strict threshold for lossless formats + CompressionQuality.Fast => 0.12f, // More permissive for fast quality + CompressionQuality.Balanced => 0.09f, // Between Fast and BestQuality + CompressionQuality.BestQuality => 0.075f, // DirectXTex BC5: 0.067, PVRTexTool BC5: 0.070 (with margin) + _ => 0.09f + }; + + if (isLossless) + { + output?.WriteLine("Using lossless format threshold for normal map (vector diff max: 0.01)"); + } + + output?.WriteLine($"Normal map Vector Difference: {vectorDiff:F4}, threshold: {threshold:F4}"); + output?.WriteLine($"Normal map RMSE (for reference): {rmse:F4}, quality: {quality}"); + + Assert.True(vectorDiff <= threshold, + $"Normal map quality below threshold. Vector Difference: {vectorDiff:F4}, max allowed: {threshold:F4}"); + } + + private static void AssertHeightMapQuality( + Image original, + Image compressed, + CompressionQuality quality, + string channelMask, + ITestOutputHelper output, + bool isLossless = false) + { + // For height maps, combine RMSE and SSIM for both precision and structure + // SSIM for structure preservation + StructuralSimilarityResult ssim = StructuralSimilarity.SingleScaleStructuralSimilarity(original, compressed, channelMask, true, false); + + // RMSE for precision - use the direct Image overload + float rmse = CalculateRMSE(original, compressed, channelMask); + + // Thresholds for height maps with pattern matching for lossless formats + // SSIM thresholds (structural similarity) + float ssimThreshold = quality switch + { + _ when isLossless => 0.99f, // Near-perfect structural similarity for lossless formats + CompressionQuality.Fast => 0.90f, + CompressionQuality.Balanced => 0.93f, + CompressionQuality.BestQuality => 0.96f, + _ => 0.93f + }; + + // RMSE thresholds (precision) + float rmseThreshold = quality switch + { + _ when isLossless => 0.001f, // Very minimal error tolerance for lossless formats + CompressionQuality.Fast => 0.04f, + CompressionQuality.Balanced => 0.02f, + CompressionQuality.BestQuality => 0.01f, + _ => 0.025f + }; + + if (isLossless) + { + output?.WriteLine("Using lossless format thresholds for height map"); + } + + output?.WriteLine($"Height map SSIM: {ssim:F4}, threshold: {ssimThreshold:F4}"); + output?.WriteLine($"Height map RMSE: {rmse:F4}, threshold: {rmseThreshold:F4}, quality: {quality}"); + + Assert.True(ssim.Average >= ssimThreshold, + $"Height map structure quality below threshold. SSIM: {ssim:F4}, required: {ssimThreshold:F4}"); + Assert.True(rmse <= rmseThreshold, + $"Height map precision below threshold. RMSE: {rmse:F4}, max allowed: {rmseThreshold:F4}"); + } + + private static void AssertHdrQuality( + Image original, + Image compressed, + CompressionQuality quality, + string channelMask, + ITestOutputHelper output, + bool isLossless = false) + { + // For HDR textures, log-based RMSE is most appropriate - use direct Image overload + float logRmse = CalculateLogRMSE(original, compressed, channelMask); + + // Thresholds for HDR content with pattern matching for lossless formats + float threshold = quality switch + { + _ when isLossless => 0.003f, // Extremely strict threshold for lossless HDR formats + CompressionQuality.Fast => 0.11f, // Higher than reference encoders to be more permissive + CompressionQuality.Balanced => 0.09f, // Between Fast and BestQuality + CompressionQuality.BestQuality => 0.08f, // DirectXTex BC6H SF16: 0.077, UF16: 0.078 (with margin) + _ => 0.09f + }; + + if (isLossless) + { + output?.WriteLine("Using lossless format threshold for HDR content"); + } + + output?.WriteLine($"HDR Log-RMSE: {logRmse:F4}, threshold: {threshold:F4}, quality: {quality}"); + Assert.True(logRmse <= threshold, + $"HDR quality below threshold. Log-RMSE: {logRmse:F4}, max allowed: {threshold:F4}"); + } + + private static void AssertSpecularMapQuality( + Image original, + Image compressed, + CompressionQuality quality, + string channelMask, + ITestOutputHelper output, + bool isLossless = false) + { + // For specular/roughness/metallic maps, use MS-SSIM with moderate thresholds + StructuralSimilarityResult msssim = StructuralSimilarity.MultiScaleStructuralSimilarity(original, compressed, channelMask, true, false); + + // Thresholds with pattern matching - special case for lossless formats + // For average MS-SSIM + float avgThreshold = quality switch + { + _ when isLossless => 0.995f, // Near-perfect quality for lossless formats (especially important for specular maps) + CompressionQuality.Fast => 0.98f, // PVRTexTool BC1: 0.997, DirectXTex BC1: 0.997 + CompressionQuality.Balanced => 0.985f, // Between Fast and BestQuality + CompressionQuality.BestQuality => 0.99f, // DirectXTex BC4: 1.000, PVRTexTool BC4: 1.000 + _ => 0.985f + }; + + // 5th percentile threshold + float p5Threshold = quality switch + { + _ when isLossless => 0.98f, // Allow for small areas with minimal differences + CompressionQuality.Fast => 0.95f, // PVRTexTool BC1: 0.992, DirectXTex BC1: 0.994 + CompressionQuality.Balanced => 0.96f, // Between Fast and BestQuality + CompressionQuality.BestQuality => 0.97f, // DirectXTex BC4: 1.000, PVRTexTool BC4: 1.000 + _ => 0.96f + }; + + // 10th percentile threshold + float p10Threshold = quality switch + { + _ when isLossless => 0.99f, // Strict threshold for lossless formats + CompressionQuality.Fast => 0.96f, // PVRTexTool BC1: 0.993, DirectXTex BC1: 0.995 + CompressionQuality.Balanced => 0.97f, // Between Fast and BestQuality + CompressionQuality.BestQuality => 0.98f, // DirectXTex BC4: 1.000, PVRTexTool BC4: 1.000 + _ => 0.97f + }; + + if (isLossless) + { + output?.WriteLine("Using lossless format thresholds for specular texture"); + } + + output?.WriteLine($"Specular map MS-SSIM: {msssim}, quality: {quality}"); + output?.WriteLine($"Thresholds - Average: {avgThreshold:F3}, 5th percentile: {p5Threshold:F3}, 10th percentile: {p10Threshold:F3}"); + + // Check average quality + Assert.True(msssim.Average >= avgThreshold, + $"Specular map average quality below threshold. MS-SSIM Avg: {msssim.Average:F4}, required: {avgThreshold:F4}"); + + // Check for bad spots using percentiles + Assert.True(msssim.Percentile5 >= p5Threshold, + $"Specular map has bad spots (5th percentile). MS-SSIM 5th percentile: {msssim.Percentile5:F4}, required: {p5Threshold:F4}"); + + Assert.True(msssim.Percentile10 >= p10Threshold, + $"Specular map has significant low-quality regions (10th percentile). MS-SSIM 10th percentile: {msssim.Percentile10:F4}, required: {p10Threshold:F4}"); + } + + } +} diff --git a/BCnEncTests/Support/ImageQualityBenchmark.cs b/BCnEncTests/Support/ImageQualityBenchmark.cs new file mode 100644 index 0000000..1931105 --- /dev/null +++ b/BCnEncTests/Support/ImageQualityBenchmark.cs @@ -0,0 +1,185 @@ +using System; +using System.IO; +using System.Linq; +using BCnEncoder.Decoder; +using BCnEncoder.ImageSharp; +using BCnEncoder.Shared; +using BCnEncoder.TextureFormats; +using BCnEncTests.Support; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using Xunit; +using Xunit.Abstractions; + +namespace BCnEncTests +{ + /// + /// This test utility measures image quality between a reference image and a pre-encoded DDS image. + /// + public class ImageQualityBenchmark + { + private readonly ITestOutputHelper _output; + + public ImageQualityBenchmark(ITestOutputHelper output) + { + _output = output; + } + + /// + /// Compares a reference image with a pre-encoded DDS image and prints out quality metrics. + /// + /// Path to the original reference image + /// Path to the DDS file created with another encoding tool + /// The type of texture being compared + /// Which channels to include in the comparison (default: "rgb") + /// A result object containing all the quality metrics + public QualityResult BenchmarkQuality( + string referenceImagePath, + string encodedDdsPath, + TextureType textureType, + string channelMask = "rgb") + { + // Load reference image + using var referenceImage = Image.Load(referenceImagePath); + _output.WriteLine($"Loaded reference image: {Path.GetFileName(referenceImagePath)}"); + + // Load encoded DDS image + var decoder = new BcDecoder() + { + OutputOptions = { Bc5ComponentCalculated = ColorComponent.B } + }; + + using var fs = new FileStream(encodedDdsPath, FileMode.Open, FileAccess.Read); + + DdsFile ddsFile = ITextureFileFormat.Read(fs); + ddsFile.GetBCnCompressionFormat(out CompressionFormat format, out _); + + using var ddsImage = decoder.Decode(ddsFile, CompressionFormat.RgbaFloat).AsImageRgbaVector(); + + _output.WriteLine($"Loaded DDS image: {Path.GetFileName(encodedDdsPath)}"); + _output.WriteLine($"Format: {format}"); + + // Ensure images have the same dimensions + if (referenceImage.Width != ddsImage.Width || referenceImage.Height != ddsImage.Height) + { + throw new ArgumentException("Both images must have the same dimensions"); + } + + // Calculate metrics + var result = new QualityResult(); + + // Calculate RMSE + result.Rmse = ImageQuality.CalculateRMSE(referenceImage, ddsImage, channelMask); + _output.WriteLine($"RMSE: {result.Rmse:F6}"); + + // Calculate LogRMSE (for HDR textures) + result.LogRmse = ImageQuality.CalculateLogRMSE(referenceImage, ddsImage, channelMask); + _output.WriteLine($"LogRMSE: {result.LogRmse:F6}"); + + // Calculate StructuralSimilarity (Single-Scale SSIM and Multi-Scale SSIM) + result.SsimResult = StructuralSimilarity.SingleScaleStructuralSimilarity(referenceImage, ddsImage, channelMask, false, false); + result.MsSsimResult = StructuralSimilarity.MultiScaleStructuralSimilarity(referenceImage, ddsImage, channelMask, false, false); + _output.WriteLine($"SSIM: {result.SsimResult}"); + _output.WriteLine($"MS-SSIM: {result.MsSsimResult}"); + + // For normal maps, calculate normal vector difference + if (textureType == TextureType.Normal) + { + result.NormalVectorDiff = ImageQuality.CalculateNormalVectorDifference(referenceImage, ddsImage, channelMask.Contains("b")); + _output.WriteLine($"Normal Vector Difference: {result.NormalVectorDiff:F6}"); + } + + return result; + } + + /// + /// Run this method to batch test multiple images and formats + /// + [Fact] + public void CompareDdsWithReferenceImages() + { + // Use the actual generated test images from the testImages/Quality directory + string testImagesDir = Path.Combine(TestHelper.GetProjectRoot(),"testImages", "Quality"); + if (!Directory.Exists(testImagesDir)) + { + _output.WriteLine("Test images directory not found. Please run QualityTester.ps1 first to generate test images."); + return; + } + + // Get all DDS files in the directory + var ddsFiles = Directory.GetFiles(testImagesDir, "*.dds"); + _output.WriteLine($"Found {ddsFiles.Length} DDS files to test"); + + if (ddsFiles.Length == 0) + { + _output.WriteLine("No DDS files found. Please run GenerateReferenceEncodes.ps1 first to generate test images."); + return; + } + + // Process each DDS file + foreach (var ddsFile in ddsFiles) + { + // Parse the filename to extract information + // Format is: basename_toolname_format.dds (e.g., albedo_directxtex_bc1.dds) + string fileName = Path.GetFileNameWithoutExtension(ddsFile); + string[] parts = fileName.Split('_'); + + if (parts.Length < 3) + { + _output.WriteLine($"Skipping file with unexpected format: {fileName}"); + continue; + } + + string baseName = parts[0]; // e.g., albedo, normal, specular + string toolName = parts[1]; // e.g., directxtex, pvrtextool + string formatName = parts[2]; // e.g., bc1, bc3, bc5, bc7 + + var textureType = GetTextureTypeFromBaseName(baseName); + + // Find corresponding reference image + string referenceFile = Path.Combine(testImagesDir, $"{baseName}_reference.png"); + if (!File.Exists(referenceFile)) + { + _output.WriteLine($"Reference file not found for {fileName}: {referenceFile}"); + continue; + } + + // Run the benchmark + _output.WriteLine($"\nTesting {formatName.ToUpper()} compressed {baseName} image ({toolName})..."); + BenchmarkQuality( + referenceFile, + ddsFile, + textureType.Value, + "rgb" + ); + } + } + + /// + /// Maps a base name to a TextureType + /// + private TextureType? GetTextureTypeFromBaseName(string baseName) + { + foreach (var texType in Enum.GetValues()) + { + if (baseName.Contains(texType.ToString(), StringComparison.OrdinalIgnoreCase)) + return texType; + } + + throw new ArgumentException($"Could not find matching texture type for: {baseName}"); + } + } + + /// + /// Container for all quality metrics to be returned from benchmarking + /// + public class QualityResult + { + public float Rmse { get; set; } + public float LogRmse { get; set; } + public float NormalVectorDiff { get; set; } + public StructuralSimilarityResult SsimResult { get; set; } + public StructuralSimilarityResult MsSsimResult { get; set; } + } +} diff --git a/BCnEncTests/Support/ImageQualityTests.cs b/BCnEncTests/Support/ImageQualityTests.cs new file mode 100644 index 0000000..48fddb8 --- /dev/null +++ b/BCnEncTests/Support/ImageQualityTests.cs @@ -0,0 +1,179 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Text.RegularExpressions; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; +using Xunit.Abstractions; + +namespace BCnEncTests.Support +{ + /// + /// Validates the PSNR and SSIM implementations against ImageMagick's reference output. + /// Tests are skipped when ImageMagick is not available on the system. + /// + public class ImageQualityTests + { + private readonly ITestOutputHelper _output; + + public ImageQualityTests(ITestOutputHelper output) + { + _output = output; + } + + private static bool IsImageMagickAvailable() + { + try + { + using var proc = Process.Start(new ProcessStartInfo("compare", "--version") + { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false + }); + proc?.WaitForExit(3000); + return proc?.ExitCode == 0; + } + catch + { + return false; + } + } + + /// + /// Runs ImageMagick compare with the given metric and returns combined stdout+stderr output. + /// ImageMagick writes metric values to stderr. + /// -alpha off disables alpha premultiplication so only RGB channels are compared, + /// matching our channelMask="rgb" straight-alpha computation. + /// + private static string RunImageMagickMetric(string metric, string img1, string img2) + { + var psi = new ProcessStartInfo("compare", $"-alpha off -metric {metric} \"{img1}\" \"{img2}\" null:") + { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false + }; + + using var proc = Process.Start(psi)!; + string stdout = proc.StandardOutput.ReadToEnd(); + string stderr = proc.StandardError.ReadToEnd(); + proc.WaitForExit(); + + // ImageMagick writes the value to stderr; stdout may be empty + return stderr.Length > 0 ? stderr : stdout; + } + + /// + /// Parses the first floating-point number from ImageMagick output. + /// Used for PSNR which outputs the dB value directly as the first token. + /// + private static double ParseFirstDouble(string output) + { + var match = Regex.Match(output.Trim(), @"[\d]+(?:\.[\d]+)?"); + if (!match.Success) + throw new InvalidOperationException($"Could not parse metric value from ImageMagick output: '{output}'"); + return double.Parse(match.Value, System.Globalization.CultureInfo.InvariantCulture); + } + + /// + /// Parses the parenthetical (normalized) value from ImageMagick output. + /// Used for SSIM: ImageMagick outputs DSSIM in parentheses, so SSIM = 1 - parenthetical. + /// Example output: "3607.72 (0.0550502)" + /// + private static double ParseParenthetical(string output) + { + var match = Regex.Match(output.Trim(), @"\(([\d]+(?:\.[\d]+)?)\)"); + if (!match.Success) + throw new InvalidOperationException($"Could not parse parenthetical value from ImageMagick output: '{output}'"); + return double.Parse(match.Groups[1].Value, System.Globalization.CultureInfo.InvariantCulture); + } + + [SkippableFact] + public void PsnrBc1Matches() + { + Skip.If(!IsImageMagickAvailable(), "ImageMagick compare not available"); + + var rawPath = Path.Combine(TestHelper.GetProjectRoot(), "testImages", "raw.png"); + var refPath = Path.Combine(TestHelper.GetProjectRoot(), "testImages", "reference", "bc1_unorm.png"); + + var imOutput = RunImageMagickMetric("PSNR", rawPath, refPath); + var imPsnr = ParseFirstDouble(imOutput); + + using var rawImage = Image.Load(rawPath); + using var refImage = Image.Load(refPath); + var ourPsnr = ImageQuality.CalculatePsnr(rawImage, refImage, "rgb"); + + _output.WriteLine($"BC1 PSNR — ImageMagick: {imPsnr:F4} dB, Ours: {ourPsnr:F4} dB"); + + Assert.True(Math.Abs(ourPsnr - imPsnr) < 1.5, + $"PSNR mismatch: ours={ourPsnr:F4} dB, ImageMagick={imPsnr:F4} dB (tolerance 1.5 dB)"); + } + + [SkippableFact] + public void PsnrBc7Matches() + { + Skip.If(!IsImageMagickAvailable(), "ImageMagick compare not available"); + + var rawPath = Path.Combine(TestHelper.GetProjectRoot(), "testImages", "raw.png"); + var refPath = Path.Combine(TestHelper.GetProjectRoot(), "testImages", "reference", "bc7_unorm.png"); + + var imOutput = RunImageMagickMetric("PSNR", rawPath, refPath); + var imPsnr = ParseFirstDouble(imOutput); + + using var rawImage = Image.Load(rawPath); + using var refImage = Image.Load(refPath); + var ourPsnr = ImageQuality.CalculatePsnr(rawImage, refImage, "rgb"); + + _output.WriteLine($"BC7 PSNR — ImageMagick: {imPsnr:F4} dB, Ours: {ourPsnr:F4} dB"); + + Assert.True(Math.Abs(ourPsnr - imPsnr) < 1.5, + $"PSNR mismatch: ours={ourPsnr:F4} dB, ImageMagick={imPsnr:F4} dB (tolerance 1.5 dB)"); + } + + [SkippableFact] + public void SsimBc1Matches() + { + Skip.If(!IsImageMagickAvailable(), "ImageMagick compare not available"); + + var rawPath = Path.Combine(TestHelper.GetProjectRoot(), "testImages", "raw.png"); + var refPath = Path.Combine(TestHelper.GetProjectRoot(), "testImages", "reference", "bc1_unorm.png"); + + // ImageMagick outputs DSSIM (dissimilarity); SSIM = 1 - DSSIM + var imOutput = RunImageMagickMetric("SSIM", rawPath, refPath); + var imSsim = 1.0 - ParseParenthetical(imOutput); + + using var rawImage = Image.Load(rawPath); + using var refImage = Image.Load(refPath); + var ourSsim = StructuralSimilarity.SingleScaleStructuralSimilarity(rawImage, refImage, "rgb", false, false).Average; + + _output.WriteLine($"BC1 SSIM — ImageMagick: {imSsim:F6}, Ours: {ourSsim:F6}"); + + Assert.True(Math.Abs(ourSsim - imSsim) < 0.05, + $"SSIM mismatch: ours={ourSsim:F6}, ImageMagick={imSsim:F6} (tolerance 0.05)"); + } + + [SkippableFact] + public void SsimBc7Matches() + { + Skip.If(!IsImageMagickAvailable(), "ImageMagick compare not available"); + + var rawPath = Path.Combine(TestHelper.GetProjectRoot(), "testImages", "raw.png"); + var refPath = Path.Combine(TestHelper.GetProjectRoot(), "testImages", "reference", "bc7_unorm.png"); + + // ImageMagick outputs DSSIM (dissimilarity); SSIM = 1 - DSSIM + var imOutput = RunImageMagickMetric("SSIM", rawPath, refPath); + var imSsim = 1.0 - ParseParenthetical(imOutput); + + using var rawImage = Image.Load(rawPath); + using var refImage = Image.Load(refPath); + var ourSsim = StructuralSimilarity.SingleScaleStructuralSimilarity(rawImage, refImage, "rgb", false, false).Average; + + _output.WriteLine($"BC7 SSIM — ImageMagick: {imSsim:F6}, Ours: {ourSsim:F6}"); + + Assert.True(Math.Abs(ourSsim - imSsim) < 0.05, + $"SSIM mismatch: ours={ourSsim:F6}, ImageMagick={imSsim:F6} (tolerance 0.05)"); + } + } +} diff --git a/BCnEncTests/Support/StructuralSimilarity.cs b/BCnEncTests/Support/StructuralSimilarity.cs new file mode 100644 index 0000000..8b860ed --- /dev/null +++ b/BCnEncTests/Support/StructuralSimilarity.cs @@ -0,0 +1,944 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; +using CommunityToolkit.HighPerformance; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.ImageSharp.Processing.Processors.Filters; + +namespace BCnEncTests.Support +{ + public class StructuralSimilarityResult + { + public float Average { get; set; } = 1f; + public float Percentile5 { get; set; } = 1f; + public float Percentile10 { get; set; } = 1f; + public float Percentile20 { get; set; } = 1f; + + public override string ToString() + { + return + $"{nameof(Average)}: {Average:F3}, 5th percentile: {Percentile5:F3}, 10th percentile: {Percentile10:F3}, 20th percentile: {Percentile20:F3}"; + } + } + + /// + /// Implementation of Multi-Scale Structural Similarity Index based on: + /// Z. Wang, E. P. Simoncelli and A. C. Bovik, "Multiscale structural similarity for image quality assessment," + /// The Thirty-Seventh Asilomar Conference on Signals, Systems & Computers, 2003. + /// + public static class StructuralSimilarity + { + // Standard channel weights to approximate human perception + private static float[] StandardRgbaWeights = new float[]{ + 0.25f, // R + 0.54f, // G + 0.11f, // B + 0.10f // A + }; + private static float[] StandardLinearRgbaWeights = new float[]{ + 1f, // R + 1f, // G + 1f, // B + 1f // A + }; + private static float[] StandardOklabWeights = new float[]{ + 0.25f, // R + 0.54f, // G + 0.11f, // B + 1f // L + }; + + + /// + /// Calculates the Multi-Scale Structural Similarity Index (MS-SSIM) between two images. + /// + /// The original image. + /// The comparison image. + /// String indicating which channels to include ("rgba"). Case-insensitive. + /// Whether to use Oklab color space when calculating SSIM. + /// Whether the input images are in sRGB color space + /// Number of scales to use in calculation (default: 5). + /// MS-SSIM value between 0-1, where 1 indicates identical images. + public static StructuralSimilarityResult MultiScaleStructuralSimilarity( + Image original, + Image other, + string channelMask, + bool useOklab, + bool isSrgb, + int scales = 5) + { + float[] weights = channelMask.ToLowerInvariant() switch + { + _ when useOklab => StandardOklabWeights, + "rgba" => StandardRgbaWeights, + "rgb" => StandardRgbaWeights, + _ => StandardLinearRgbaWeights + }; + + if (useOklab) + { + try + { + var config = new Configuration() + { + PreferContiguousImageBuffers = true, + }; + original = original.Clone(config); + other = other.Clone(config); + OkLabToAlpha(original, isSrgb); + OkLabToAlpha(other, isSrgb); + + channelMask = "rgba"; + + return MultiScaleStructuralSimilarity_Simd(original, other, channelMask, weights, scales); + } + finally + { + // original?.Dispose(); + // other?.Dispose(); + } + } + + return MultiScaleStructuralSimilarity_Simd(original, other, channelMask, weights, scales); + } + + /// + /// Calculates the standard Structural Similarity Index (SSIM) between two images. + /// + /// The original image. + /// The comparison image. + /// Whether to use Oklab color space when calculating SSIM. + /// Whether the input images are in sRGB color space + /// String indicating which channels to include ("rgba"). Case-insensitive. + /// SSIM value between 0-1, where 1 indicates identical images. + /// + /// SSIM is designed to better match human perception of image quality than metrics like PSNR. + /// It measures structural information changes, perceived as variations in image structure. + /// + internal static StructuralSimilarityResult SingleScaleStructuralSimilarity( + Image original, + Image other, + string channelMask, + bool useOklab, + bool isSrgb) + { + float[] weights = channelMask.ToLowerInvariant() switch + { + _ when useOklab => StandardOklabWeights, + "rgba" => StandardRgbaWeights, + "rgb" => StandardRgbaWeights, + _ => StandardLinearRgbaWeights + }; + + if (useOklab) + { + try + { + var config = new Configuration() + { + PreferContiguousImageBuffers = true, + }; + original = original.Clone(config); + other = other.Clone(config); + OkLabToAlpha(original, isSrgb); + OkLabToAlpha(other, isSrgb); + + channelMask = "rgba"; + + return MultiScaleStructuralSimilarity_Simd(original, other, channelMask, weights, scales: 1); + } + finally + { + original?.Dispose(); + other?.Dispose(); + } + } + + // Single-scale SSIM is just MS-SSIM with one scale + return MultiScaleStructuralSimilarity_Simd(original, other, channelMask, weights, scales: 1); + } + + /// + /// Gets the channel value from a ColorRgbaFloat based on index. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float GetChannelValue(RgbaVector color, int channel) + { + switch (channel) + { + case 0: return color.R; + case 1: return color.G; + case 2: return color.B; + case 3: return color.A; + default: throw new ArgumentOutOfRangeException(nameof(channel)); + } + } + + /// + /// Downsamples both images using Gaussian filtering. + /// + /// + /// Uses ImageSharp's Gaussian filtering for high-quality downsampling that reduces aliasing artifacts. + /// This produces better results than a simple box filter when calculating structural similarity. + /// + private static void DownsampleImage( + Image original) + { + int newWidth = original.Width / 2; + int newHeight = original.Height / 2; + + var rgb = original; + using var alpha = original.Clone(); + + for (int y = 0; y < original.Height; y++) + { + for (int x = 0; x < original.Width; x++) + { + var oPixel = original[x, y]; + rgb[x, y] = new RgbaVector(oPixel.R, oPixel.G, oPixel.B); + alpha[x, y] = new RgbaVector(oPixel.A, oPixel.A, oPixel.A); + } + } + + // Configure resize options with Gaussian resampling + var resizeOptions = new ResizeOptions + { + Size = new Size(newWidth, newHeight), + Mode = ResizeMode.Stretch, + Sampler = new BicubicResampler(), + PremultiplyAlpha = false, + }; + + // Apply Gaussian blur before resizing to reduce aliasing + float sigma = 0.8f; // Standard deviation for Gaussian kernel + + rgb.Mutate(ctx => + { + ctx + .GaussianBlur(sigma) + .Resize(resizeOptions); + }); + + alpha.Mutate(ctx => ctx + .GaussianBlur(sigma) + .Resize(resizeOptions)); + + for (int y = 0; y < newHeight; y++) + { + for (int x = 0; x < newWidth; x++) + { + rgb[x, y] = rgb[x, y] with{ A = alpha[x, y].R }; + } + } + } + + private static void OkLabToAlpha(Image image, bool isSrgb) + { + // for (int y = 0; y < image.Height; y++) + // { + // for (int x = 0; x < image.Width; x++) + // { + // + // var oPixel = image[x, y]; + // Vector4 rgb = oPixel.ToVector4(); + // + // if (isSrgb) + // rgb = ColorSpace.Srgb.ToLrgb(rgb); // Convert from sRGB to linear RGB + // Vector4 oklab = ColorSpace.Oklab.LrgbToOklab(rgb); + // + // RgbaVector newPixel = oPixel; + // newPixel.A = oklab.X; + // image[x, y] = newPixel; + // } + // } + + image.Mutate(ctx => + { + ctx.ProcessPixelRowsAsVector4((row, point) => + { + for (int x = 0; x < row.Length; x++) + { + Vector4 rgb = row[x]; + + if (isSrgb) + rgb = ColorSpace.Srgb.ToLrgb(rgb); // Convert from sRGB to linear RGB + Vector4 oklab = ColorSpace.Oklab.LrgbToOklab(rgb); + + row[x].W = oklab.X; + } + }); + }); + } + + /// + /// Calculate a percentile value from a list of values. + /// The list must be sorted in ascending order. + /// + /// + /// + /// + private static float CalculatePercentile(List values, float percentile) + { + int count = values.Count; + + if (count == 0) + { + return 0; + } + + // Calculate the index for the percentile + int index = (int)(percentile * count); + + // Ensure index is valid + index = Math.Max(0, Math.Min(count - 1, index)); + + // Return the value at the percentile position + return values[index]; + } + + private static float CalculateAverage(List values) + { + int count = values.Count; + + if (count == 0) + { + return 0; + } + + float sum = 0; + foreach (var value in values) + { + sum += value; + } + + return sum / count; + } + + private static float[] CreateGaussianKernel(int windowSize, int windowRadius) + { + // Before your nested window loops, create a Gaussian kernel: + float[] gaussianKernel = new float[windowSize * windowSize]; + float sigma = windowSize / 6.0f; // Standard deviation - typically 1/6 of the window size + float sum = 0; + + // Pre-compute the 2D Gaussian kernel + for (int wy = -windowRadius; wy <= windowRadius; wy++) + { + for (int wx = -windowRadius; wx <= windowRadius; wx++) + { + int idx = (wy + windowRadius) * windowSize + (wx + windowRadius); + // Gaussian function: exp(-(x² + y²)/(2*σ²)) + gaussianKernel[idx] = MathF.Exp(-(wx * wx + wy * wy) / (2 * sigma * sigma)); + sum += gaussianKernel[idx]; + } + } + + // Normalize the kernel + for (int i = 0; i < gaussianKernel.Length; i++) + { + gaussianKernel[i] /= sum; + } + + return gaussianKernel; + } + + #region Simd + + private class SsimScaleValues + { + public List SsimValues { get; } = new List(); + } + + /// + internal static StructuralSimilarityResult MultiScaleStructuralSimilarity_Simd( + Image original, + Image other, + string channelMask, + float[] channelWeights, + int scales) + { + if (original.Width != other.Width || original.Height != other.Height) + { + throw new ArgumentException("Both images must have the same dimensions"); + } + + if (string.IsNullOrEmpty(channelMask)) + { + throw new ArgumentException("Channel mask must contain at least one channel", nameof(channelMask)); + } + + if (channelWeights == null || channelWeights.Length != 4) + { + throw new ArgumentException("Channel weights must be an array of length 4", nameof(channelWeights)); + } + + // Validate scales based on image dimensions + int minDimension = Math.Min(original.Width, original.Height); + int maxScales = (int)Math.Log2(minDimension) - 3; // Ensure window size is at least 16x16 + scales = Math.Min(scales, maxScales); + + if (scales < 1) + { + throw new ArgumentException("Image is too small for MS-SSIM calculation"); + } + + channelMask = channelMask.ToLowerInvariant(); + + // Prepare channel flags + bool useRed = channelMask.Contains('r'); + bool useGreen = channelMask.Contains('g'); + bool useBlue = channelMask.Contains('b'); + bool useAlpha = channelMask.Contains('a'); + + int activeChannels = 0; + if (useRed) activeChannels++; + if (useGreen) activeChannels++; + if (useBlue) activeChannels++; + if (useAlpha) activeChannels++; + + if (activeChannels == 0) + { + throw new ArgumentException("Channel mask must contain at least one valid channel (r, g, b, or a)", + nameof(channelMask)); + } + + // Standard scale weights from the paper + float[] standardWeights = + [ + 0.0448f * 5, + 0.2856f * 5, + 0.3001f * 5, + 0.2363f * 5, + 0.1333f * 5 + ]; + + // Define MS-SSIM weights (from Wang et al. paper) + float[] scaleWeights = new float[scales]; + + // Fill with standard weights + for (int i = 0; i < scales; i++) + { + // If 5 scales or less, use standard scales + if (scales <= 5) + { + scaleWeights[i] = standardWeights[i] / scales; + } + else // Otherwise, use uniform scales + { + scaleWeights[i] = 1f / scales; + } + } + + // Constants to stabilize division with weak denominators + const float c1 = 0.01f * 0.01f; // (K1*L)^2, K1=0.01, L=1 (assumed dynamic range) + const float c2 = 0.03f * 0.03f; // (K2*L)^2, K2=0.03, L=1 + + Vector128 c1Vector = Vector128.Create(c1); + Vector128 c2Vector = Vector128.Create(c2); + + var config = new Configuration() + { + PreferContiguousImageBuffers = true, + }; + + // Create working copies of the images with contiguous buffers + using var originalCopy = original.Clone(config); + using var otherCopy = other.Clone(config); + + // Base window size for local statistics + const int windowSize = 11; + const int windowRadius = windowSize / 2; + + // Initialize Gaussian kernel + float[] gaussianKernel = CreateGaussianKernel(windowSize, windowRadius); + + StructuralSimilarityResult msssimResult = new StructuralSimilarityResult(); + + int currentWidth = original.Width; + int currentHeight = original.Height; + + // Create a list to store SSIM maps for each scale + var ssimMaps = new SsimScaleValues[scales]; + for (int i = 0; i < scales; i++) + { + ssimMaps[i] = new SsimScaleValues(); + } + + Vector128 channelsOnOffVector = Vector128.Create( + useRed ? 1f : 0, + useGreen ? 1f : 0, + useBlue ? 1f : 0, + useAlpha ? 1f: 0 + ); + + // Create channel weights + Vector128 channelWeightsVec = Vector128.Create( + channelWeights[0], + channelWeights[1], + channelWeights[2], + channelWeights[3] + ); + + // Turn off unused channels + channelWeightsVec *= channelsOnOffVector; + + float totalWeight = Vector128.Sum(channelWeightsVec); + + if (totalWeight == 0) + { + throw new InvalidOperationException("Something is wrong with channel weights. Please fix."); + } + + // Normalize weights + channelWeightsVec /= totalWeight; + + // Process each scale + for (int scale = 0; scale < scales; scale++) + { + if (!originalCopy.DangerousTryGetSinglePixelMemory(out Memory originalMemory) || !otherCopy.DangerousTryGetSinglePixelMemory(out Memory otherMemory)) + { + throw new Exception("Failed to get single pixel memory"); + } + + // Cast pixels to Vector128 + Span> originalSpan = originalMemory.Span.Cast>(); + Span> otherSpan = otherMemory.Span.Cast>(); + + // Calculate local statistics (mean, variance, covariance) + // This is the core of the algorithm - sliding window calculation + // Compute local statistics for valid windows + int validWindows = 0; + + for (int y = windowRadius; y < currentHeight - windowRadius; y++) + { + for (int x = windowRadius; x < currentWidth - windowRadius; x++) + { + // Calculate statistics in window around (x,y) + Vector128 sumX = default, sumY = default; + Vector128 sumXX = default, sumYY = default, sumXY = default; + + // Sample window pixels + for (int wy = -windowRadius; wy <= windowRadius; wy++) + { + for (int wx = -windowRadius; wx <= windowRadius; wx++) + { + int index = (y + wy) * currentWidth + (x + wx); + + // Get channel values (r, g, b, a) + Vector128 xVal = originalSpan[index]; + Vector128 yVal = otherSpan[index]; + + // Get Gaussian weight for this position + // Weights is prenormalized to sum to 1 + int idx = (wy + windowRadius) * windowSize + (wx + windowRadius); + float weight = gaussianKernel[idx]; + + Vector128 weightVector = Vector128.Create(weight); + + // Apply weighted contribution + sumX += xVal * weightVector; + sumY += yVal * weightVector; + sumXX += xVal * xVal * weightVector; + sumYY += yVal * yVal * weightVector; + sumXY += xVal * yVal * weightVector; + } + } + + // Local statistics + Vector128 muX = sumX; + Vector128 muY = sumY; + Vector128 sigmaX2 = sumXX - (muX * muX); + Vector128 sigmaY2 = sumYY - (muY * muY); + Vector128 sigmaXY = sumXY - (muX * muY); + + var zeroVector = Vector128.Create(0f); + var twoVector = Vector128.Create(2f); + + // Guard against negative variances due to floating-point errors + sigmaX2 = Vector128.Max(zeroVector, sigmaX2); + sigmaY2 = Vector128.Max(zeroVector, sigmaY2); + + Vector128 sqrtSigmaX2SigmaY2 = Vector128.Sqrt(sigmaX2 * sigmaY2); + + // Calculate SSIM components + Vector128 l = (twoVector * muX * muY + c1Vector) / (muX * muX + muY * muY + c1Vector); + Vector128 c = (twoVector * sqrtSigmaX2SigmaY2 + c2Vector) / (sigmaX2 + sigmaY2 + c2Vector); + Vector128 s = (sigmaXY + c2Vector / twoVector) / (sqrtSigmaX2SigmaY2 + c2Vector / twoVector); + + // Horizontal add the channels multiplied by their weights (already normalized) + float luminance = Vector128.Sum(l * channelWeightsVec); + float contrast = Vector128.Sum(c * channelWeightsVec); + float structure = Vector128.Sum(s * channelWeightsVec); + + ssimMaps[scale].SsimValues.Add(luminance * contrast * structure); + + validWindows++; + } + } + + if (validWindows == 0) + { + throw new Exception("No valid windows found"); + } + + // Sort the SSIM values (ascending) + ssimMaps[scale].SsimValues.Sort(); + + // Calculate average SSIM + float averageSsim = CalculateAverage(ssimMaps[scale].SsimValues); + float percentile5 = CalculatePercentile(ssimMaps[scale].SsimValues, 0.05f); + float percentile10 = CalculatePercentile(ssimMaps[scale].SsimValues, 0.1f); + float percentile20 = CalculatePercentile(ssimMaps[scale].SsimValues, 0.2f); + + // Ensure values are non-negative before applying power function + // This follows image quality research convention where absolute structural similarity is used + float absAverageSsim = Math.Abs(averageSsim); + float absPercentile5 = Math.Abs(percentile5); + float absPercentile10 = Math.Abs(percentile10); + float absPercentile20 = Math.Abs(percentile20); + + msssimResult.Average *= MathF.Pow(absAverageSsim, scaleWeights[scale]); + msssimResult.Percentile5 *= MathF.Pow(absPercentile5, scaleWeights[scale]); + msssimResult.Percentile10 *= MathF.Pow(absPercentile10, scaleWeights[scale]); + msssimResult.Percentile20 *= MathF.Pow(absPercentile20, scaleWeights[scale]); + + // Downsample for next scale if not at the last scale + if (scale < scales - 1) + { + DownsampleImage(originalCopy); + DownsampleImage(otherCopy); + currentWidth /= 2; + currentHeight /= 2; + } + } + + return msssimResult; + } + + #endregion + + #region Non-SIMD + + private class SsimMapScale + { + public int ValidWindows { get; set; } + + public List SsimValues { get; } = new List(); + public List[] LuminanceValues { get; } + public List[] ContrastValues { get; } + public List[] StructureValues { get; } + + public SsimMapScale() + { + LuminanceValues = new List[4]; + ContrastValues = new List[4]; + StructureValues = new List[4]; + + for (int i = 0; i < 4; i++) + { + LuminanceValues[i] = new List(); + ContrastValues[i] = new List(); + StructureValues[i] = new List(); + } + } + } + + /// + internal static StructuralSimilarityResult MultiScaleStructuralSimilarity_NonSimd( + Image original, + Image other, + string channelMask, + float[] channelWeights, + int scales) + { + if (original.Width != other.Width || original.Height != other.Height) + { + throw new ArgumentException("Both images must have the same dimensions"); + } + + if (string.IsNullOrEmpty(channelMask)) + { + throw new ArgumentException("Channel mask must contain at least one channel", nameof(channelMask)); + } + + if (channelWeights != null && channelWeights.Length != 4) + { + throw new ArgumentException("Channel weights must be an array of length 4", nameof(channelWeights)); + } + + // Validate scales based on image dimensions + int minDimension = Math.Min(original.Width, original.Height); + int maxScales = (int)Math.Log2(minDimension) - 3; // Ensure window size is at least 16x16 + scales = Math.Min(scales, maxScales); + + if (scales < 1) + { + throw new ArgumentException("Image is too small for MS-SSIM calculation"); + } + + channelMask = channelMask.ToLowerInvariant(); + + // Prepare channel flags + bool useRed = channelMask.Contains('r'); + bool useGreen = channelMask.Contains('g'); + bool useBlue = channelMask.Contains('b'); + bool useAlpha = channelMask.Contains('a'); + + int activeChannels = 0; + if (useRed) activeChannels++; + if (useGreen) activeChannels++; + if (useBlue) activeChannels++; + if (useAlpha) activeChannels++; + + if (activeChannels == 0) + { + throw new ArgumentException("Channel mask must contain at least one valid channel (r, g, b, or a)", + nameof(channelMask)); + } + + // Standard scale weights from the paper + float[] standardWeights = + [ + 0.0448f * 5, + 0.2856f * 5, + 0.3001f * 5, + 0.2363f * 5, + 0.1333f * 5 + ]; + + // Standard channel weights to approximate human perception + float[] standardChannelWeights = + [ + 0.25f, + 0.54f, + 0.11f, + 0.10f + ]; + + if (channelWeights != null) + { + standardChannelWeights = channelWeights; + } + + // Define MS-SSIM weights (from Wang et al. paper) + float[] scaleWeights = new float[scales]; + + // Fill with standard weights (can be adjusted) + for (int i = 0; i < scales; i++) + { + // If 5 scales or less, use standard scales + if (scales <= 5) + { + scaleWeights[i] = standardWeights[i] / scales; + } + else // Otherwise, use uniform scales + { + scaleWeights[i] = 1f / scales; + } + } + + // Constants to stabilize division with weak denominators + const float C1 = 0.01f * 0.01f; // (K1*L)^2, K1=0.01, L=1 (assumed dynamic range) + const float C2 = 0.03f * 0.03f; // (K2*L)^2, K2=0.03, L=1 + + // Create working copies of the images + using var originalCopy = original.Clone(); + using var otherCopy = other.Clone(); + + // Base window size for local statistics (typically 11×11) + const int windowSize = 11; + const int windowRadius = windowSize / 2; + + float[] gaussianKernel = CreateGaussianKernel(windowSize, windowRadius); + + // Actual window size will be adapted based on current image dimensions + + StructuralSimilarityResult msssimResult = new StructuralSimilarityResult(); + int currentWidth = original.Width; + int currentHeight = original.Height; + + // Create a list to store SSIM maps for each scale + var ssimMaps = new SsimMapScale[scales]; + for (int i = 0; i < scales; i++) + { + ssimMaps[i] = new SsimMapScale(); + } + + // Process each scale + for (int scale = 0; scale < scales; scale++) + { + // Calculate SSIM at current scale + float luminance = 0; + float contrast = 0; + float structure = 0; + + // For each channel + for (int channel = 0; channel < 4; channel++) + { + // Skip channels not in mask + if ((channel == 0 && !useRed) || + (channel == 1 && !useGreen) || + (channel == 2 && !useBlue) || + (channel == 3 && !useAlpha)) + { + continue; + } + + // Calculate local statistics (mean, variance, covariance) + // This is the core of the algorithm - sliding window calculation + // Compute local statistics for valid windows + int validWindows = 0; + + for (int y = windowRadius; y < currentHeight - windowRadius; y++) + { + for (int x = windowRadius; x < currentWidth - windowRadius; x++) + { + // Calculate statistics in window around (x,y) + float sumX = 0, sumY = 0; + float sumXX = 0, sumYY = 0, sumXY = 0; + + // Sample window pixels + for (int wy = -windowRadius; wy <= windowRadius; wy++) + { + for (int wx = -windowRadius; wx <= windowRadius; wx++) + { + // Get channel value (r, g, b, or a) + float xVal = GetChannelValue(originalCopy[x + wx, y + wy], channel); + float yVal = GetChannelValue(otherCopy[x + wx, y + wy], channel); + + // Get Gaussian weight for this position + int idx = (wy + windowRadius) * windowSize + (wx + windowRadius); + float weight = gaussianKernel[idx]; + + // Apply weighted contribution + sumX += xVal * weight; + sumY += yVal * weight; + sumXX += xVal * xVal * weight; + sumYY += yVal * yVal * weight; + sumXY += xVal * yVal * weight; + } + } + + // Local statistics + float muX = sumX; + float muY = sumY; + float sigmaX2 = sumXX - (muX * muX); + float sigmaY2 = sumYY - (muY * muY); + float sigmaXY = sumXY - (muX * muY); + + // Guard against negative variances due to floating-point errors + sigmaX2 = Math.Max(0, sigmaX2); + sigmaY2 = Math.Max(0, sigmaY2); + + // Calculate SSIM components + float l = (2 * muX * muY + C1) / (muX * muX + muY * muY + C1); + float c = (2 * MathF.Sqrt(sigmaX2) * MathF.Sqrt(sigmaY2) + C2) / (sigmaX2 + sigmaY2 + C2); + float s = (sigmaXY + C2 / 2) / (MathF.Sqrt(sigmaX2 * sigmaY2) + C2 / 2); + + ssimMaps[scale].LuminanceValues[channel].Add(l); + ssimMaps[scale].ContrastValues[channel].Add(c); + ssimMaps[scale].StructureValues[channel].Add(s); + + validWindows++; + } + } + + ssimMaps[scale].ValidWindows = validWindows; + } + + // Apply perceptual weights to all components + for (int w = 0; w < ssimMaps[scale].ValidWindows; w++) + { + float totalWeight = 0; + luminance = 0; + contrast = 0; + structure = 0; + + if (useRed) + { + luminance += ssimMaps[scale].LuminanceValues[0][w] * standardChannelWeights[0]; + contrast += ssimMaps[scale].ContrastValues[0][w] * standardChannelWeights[0]; + structure += ssimMaps[scale].StructureValues[0][w] * standardChannelWeights[0]; + totalWeight += standardChannelWeights[0]; + } + + if (useGreen) + { + luminance += ssimMaps[scale].LuminanceValues[1][w] * standardChannelWeights[1]; + contrast += ssimMaps[scale].ContrastValues[1][w] * standardChannelWeights[1]; + structure += ssimMaps[scale].StructureValues[1][w] * standardChannelWeights[1]; + totalWeight += standardChannelWeights[1]; + } + + if (useBlue) + { + luminance += ssimMaps[scale].LuminanceValues[2][w] * standardChannelWeights[2]; + contrast += ssimMaps[scale].ContrastValues[2][w] * standardChannelWeights[2]; + structure += ssimMaps[scale].StructureValues[2][w] * standardChannelWeights[2]; + totalWeight += standardChannelWeights[2]; + } + + if (useAlpha) + { + luminance += ssimMaps[scale].LuminanceValues[3][w] * standardChannelWeights[3]; + contrast += ssimMaps[scale].ContrastValues[3][w] * standardChannelWeights[3]; + structure += ssimMaps[scale].StructureValues[3][w] * standardChannelWeights[3]; + totalWeight += standardChannelWeights[3]; + } + + if (totalWeight > 0) + { + luminance /= totalWeight; + contrast /= totalWeight; + structure /= totalWeight; + } + + ssimMaps[scale].SsimValues.Add(luminance * contrast * structure); + } + + + // Sort the SSIM values (ascending) + ssimMaps[scale].SsimValues.Sort(); + + // Calculate average SSIM + float averageSsim = CalculateAverage(ssimMaps[scale].SsimValues); + float percentile5 = CalculatePercentile(ssimMaps[scale].SsimValues, 0.05f); + float percentile10 = CalculatePercentile(ssimMaps[scale].SsimValues, 0.1f); + float percentile20 = CalculatePercentile(ssimMaps[scale].SsimValues, 0.2f); + + // Ensure values are non-negative before applying power function + // This follows image quality research convention where absolute structural similarity is used + float absAverageSsim = Math.Abs(averageSsim); + float absPercentile5 = Math.Abs(percentile5); + float absPercentile10 = Math.Abs(percentile10); + float absPercentile20 = Math.Abs(percentile20); + + msssimResult.Average *= MathF.Pow(absAverageSsim, scaleWeights[scale]); + msssimResult.Percentile5 *= MathF.Pow(absPercentile5, scaleWeights[scale]); + msssimResult.Percentile10 *= MathF.Pow(absPercentile10, scaleWeights[scale]); + msssimResult.Percentile20 *= MathF.Pow(absPercentile20, scaleWeights[scale]); + + // Downsample for next scale if not at the last scale + if (scale < scales - 1) + { + DownsampleImage(originalCopy); + DownsampleImage(otherCopy); + currentWidth /= 2; + currentHeight /= 2; + } + } + + return msssimResult; + } + + #endregion + } +} diff --git a/BCnEncTests/Support/StructuralSimilarityTests.cs b/BCnEncTests/Support/StructuralSimilarityTests.cs new file mode 100644 index 0000000..936b31c --- /dev/null +++ b/BCnEncTests/Support/StructuralSimilarityTests.cs @@ -0,0 +1,364 @@ +using System; +using System.IO; +using System.Numerics; +using BCnEncoder.ImageSharp; +using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; +using BCnEncTests.Support; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using Xunit; +using Xunit.Abstractions; + +namespace BCnEncTests.Support +{ + public class StructuralSimilarityTests + { + private readonly ITestOutputHelper _output; + + public StructuralSimilarityTests(ITestOutputHelper output) + { + _output = output; + } + + [Fact] + public void MultiScaleStructuralSimilarity_IdenticalImages_ReturnsOne() + { + // Arrange + using var image = new Image(128, 128); + + // Fill with random data to ensure we're not just testing empty images + var random = new Random(42); // Fixed seed for reproducibility + for (int y = 0; y < image.Height; y++) + { + for (int x = 0; x < image.Width; x++) + { + image[x, y] = new RgbaVector( + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble(), + 1.0f); + } + } + + using var clone = image.Clone(); + string channelMask = "rgb"; // Test only RGB channels + + // Act + StructuralSimilarityResult similarity = StructuralSimilarity.MultiScaleStructuralSimilarity(image, clone, channelMask, false, false); + + // Assert + _output.WriteLine($"MS-SSIM between identical images: {similarity}"); + Assert.Equal(1.0f, similarity.Average, 4); // Should be exactly 1.0 with tolerance of 10^-4 + // Check percentiles also equal 1.0 for identical images + Assert.Equal(1.0f, similarity.Percentile5, 4); + Assert.Equal(1.0f, similarity.Percentile10, 4); + Assert.Equal(1.0f, similarity.Percentile20, 4); + } + + [Fact] + public void MultiScaleStructuralSimilarity_SimilarImages_ReturnsHighValue() + { + // Arrange + using var original = new Image(100, 100); + + // Fill with random data + var random = new Random(42); + for (int y = 0; y < original.Height; y++) + { + for (int x = 0; x < original.Width; x++) + { + original[x, y] = new RgbaVector( + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble()); + } + } + + // Create slightly modified image (add small amount of noise) + using var modified = original.Clone(); + modified.Mutate(ctx => ctx.ProcessPixelRowsAsVector4((row, point) => + { + for (int x = 0; x < row.Length; x++) + { + row[x] = new Vector4( + row[x].X + (float)(random.NextDouble() * 0.05), // Small random noise + row[x].Y + (float)(random.NextDouble() * 0.05), + row[x].Z + (float)(random.NextDouble() * 0.05), + row[x].W); + } + })); + + string channelMask = "rgba"; + + // Act + StructuralSimilarityResult similarity = StructuralSimilarity.MultiScaleStructuralSimilarity(original, modified, channelMask, false, false); + + // Assert + _output.WriteLine($"MS-SSIM between similar images: {similarity}"); + Assert.True(similarity.Average > 0.9f, $"MS-SSIM value {similarity} is lower than expected for similar images"); + Assert.True(similarity.Average < 1.0f, $"MS-SSIM value {similarity} should be less than 1.0 for non-identical images"); + + // Check percentiles follow expected pattern (lower percentiles should be lower than higher ones) + Assert.True(similarity.Percentile5 < similarity.Percentile10, + $"5th percentile ({similarity.Percentile5}) should be lower than 10th percentile ({similarity.Percentile10})"); + Assert.True(similarity.Percentile10 < similarity.Percentile20, + $"10th percentile ({similarity.Percentile10}) should be lower than 20th percentile ({similarity.Percentile20})"); + Assert.True(similarity.Percentile20 < similarity.Average, + $"20th percentile ({similarity.Percentile20}) should be lower than average ({similarity.Average})"); + + // All percentiles should be high for similar images + Assert.True(similarity.Percentile5 > 0.85f, $"5th percentile value {similarity.Percentile5} is lower than expected for similar images"); + } + + [Fact] + public void MultiScaleStructuralSimilarity_SIMD_IsEqualToNonSIMD() + { + // Arrange + using var original = new Image(256, 256); + + // Fill with random data + var random = new Random(42); + for (int y = 0; y < original.Height; y++) + { + for (int x = 0; x < original.Width; x++) + { + original[x, y] = new RgbaVector( + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble()); + } + } + + // Create slightly modified image (add small amount of noise) + using var modified = original.Clone(); + modified.Mutate(ctx => ctx.ProcessPixelRowsAsVector4((row, point) => + { + for (int x = 0; x < row.Length; x++) + { + row[x] = new Vector4( + row[x].X + (float)(random.NextDouble() * 0.05), // Small random noise + row[x].Y + (float)(random.NextDouble() * 0.05), + row[x].Z + (float)(random.NextDouble() * 0.05), + row[x].W); + } + })); + + // Test non-standard channel mask too + string channelMask = "rba"; + + float[] channelWeights = new float[] { 0.2126f, 0.7152f, 0.0722f, 1f }; + + // Act + StructuralSimilarityResult similarity = StructuralSimilarity.MultiScaleStructuralSimilarity_NonSimd(original, modified, channelMask, channelWeights, 5); + StructuralSimilarityResult similaritySimd = StructuralSimilarity.MultiScaleStructuralSimilarity_Simd(original, modified, channelMask, channelWeights, 5); + + // Assert + _output.WriteLine($"MS-SSIM between similar images: {similarity}"); + _output.WriteLine($"MS-SSIM (SIMD) between similar images: {similaritySimd}"); + + Assert.Equal(similarity.Average, similaritySimd.Average, 0.00001); + + // Check percentiles + Assert.Equal(similarity.Percentile5, similaritySimd.Percentile5, 0.00001); + Assert.Equal(similarity.Percentile10, similaritySimd.Percentile10, 0.00001); + Assert.Equal(similarity.Percentile20, similaritySimd.Percentile20, 0.00001); + } + + [Fact] + public void MultiScaleStructuralSimilarity_DifferentImages_ReturnsLowerValue() + { + // Arrange + using var image1 = new Image(100, 100); + using var image2 = new Image(100, 100); + + // Fill with completely different patterns + for (int y = 0; y < image1.Height; y++) + { + for (int x = 0; x < image1.Width; x++) + { + // Image 1: Gradient pattern + float normalizedX = x / (float)image1.Width; + float normalizedY = y / (float)image1.Height; + image1[x, y] = new RgbaVector(normalizedX, normalizedY, 0.5f, 1.0f); + + // Image 2: Checkerboard pattern + bool isEvenX = (x / 10) % 2 == 0; + bool isEvenY = (y / 10) % 2 == 0; + float value = (isEvenX == isEvenY) ? 1.0f : 0.0f; + image2[x, y] = new RgbaVector(value, value, value, 1.0f); + } + } + + string channelMask = "rgba"; + + // Act + StructuralSimilarityResult similarity = StructuralSimilarity.MultiScaleStructuralSimilarity(image1, image2, channelMask, false, false); + + // Assert + _output.WriteLine($"MS-SSIM between different images: {similarity}"); + Assert.True(similarity.Average < 0.8f, $"MS-SSIM value {similarity} is higher than expected for different images"); + + // For different images, percentiles should be significantly different + Assert.True(similarity.Percentile5 < similarity.Average, + $"5th percentile ({similarity.Percentile5}) should be lower than average ({similarity.Average}) for different images"); + // All percentiles should be low for very different images + Assert.True(similarity.Percentile5 < 0.7f, $"5th percentile {similarity.Percentile5} is higher than expected for different images"); + } + + [Fact] + public void MultiScaleStructuralSimilarity_WithDifferentScales_ProducesConsistentResults() + { + // Arrange + using var original = new Image(256, 256); // Power of 2 size for clean downscaling + using var modified = new Image(256, 256); + + // Create patterns with some similarity but clear differences + var random = new Random(42); + for (int y = 0; y < original.Height; y++) + { + for (int x = 0; x < original.Width; x++) + { + // Original has a base pattern + float normalizedX = x / (float)original.Width; + float normalizedY = y / (float)original.Height; + original[x, y] = new RgbaVector(normalizedX, normalizedY, 0.5f, 1.0f); + + // Modified adds noise to that pattern + modified[x, y] = new RgbaVector( + normalizedX + (float)(random.NextDouble() * 0.1 - 0.05), + normalizedY + (float)(random.NextDouble() * 0.1 - 0.05), + 0.5f + (float)(random.NextDouble() * 0.1 - 0.05), + 1.0f); + } + } + + string channelMask = "rgba"; + + // Act - Test with different scale values + StructuralSimilarityResult similarity3 = StructuralSimilarity.MultiScaleStructuralSimilarity(original, modified, channelMask, false, false, scales: 3); + StructuralSimilarityResult similarity5 = StructuralSimilarity.MultiScaleStructuralSimilarity(original, modified, channelMask, false, false, scales: 5); + + // Assert + _output.WriteLine($"MS-SSIM with 3 scales: {similarity3}"); + _output.WriteLine($"MS-SSIM with 5 scales: {similarity5}"); + + // Results should be stable but not identical with different scale counts + Assert.True(Math.Abs(similarity3.Average - similarity5.Average) < 0.1f, + $"MS-SSIM values with different scales differ too much: {similarity3} vs {similarity5}"); + + // Percentiles should also be relatively stable across scales + Assert.True(Math.Abs(similarity3.Percentile5 - similarity5.Percentile5) < 0.15f, + $"5th percentile values differ too much across scales: {similarity3.Percentile5} vs {similarity5.Percentile5}"); + Assert.True(Math.Abs(similarity3.Percentile10 - similarity5.Percentile10) < 0.15f, + $"10th percentile values differ too much across scales: {similarity3.Percentile10} vs {similarity5.Percentile10}"); + Assert.True(Math.Abs(similarity3.Percentile20 - similarity5.Percentile20) < 0.15f, + $"20th percentile values differ too much across scales: {similarity3.Percentile20} vs {similarity5.Percentile20}"); + } + + [Fact] + public void MultiScaleStructuralSimilarity_ChannelMaskVariations_WorkCorrectly() + { + // Arrange + using var original = new Image(128, 128); + + // Create image with R=1, G=0.5, B=0 for all pixels + for (int y = 0; y < original.Height; y++) + { + for (int x = 0; x < original.Width; x++) + { + original[x, y] = new RgbaVector(1.0f, 0.5f, 0.0f, 1.0f); + } + } + + // Create modified image with only the green channel changed + using var greenModified = original.Clone(); + greenModified.Mutate(ctx => ctx.ProcessPixelRowsAsVector4((row, point) => + { + for (int x = 0; x < row.Length; x++) + { + row[x] = new Vector4(row[x].X, 1.0f, row[x].Z, row[x].W); // Change only green to 1.0 + } + })); + + // Act & Assert + StructuralSimilarityResult similarityRgb = StructuralSimilarity.MultiScaleStructuralSimilarity(original, greenModified, "rgb", false, false); + StructuralSimilarityResult similarityRb = StructuralSimilarity.MultiScaleStructuralSimilarity(original, greenModified, "rb", false, false); + StructuralSimilarityResult similarityG = StructuralSimilarity.MultiScaleStructuralSimilarity(original, greenModified, "g", false, false); + + _output.WriteLine($"MS-SSIM RGB channels: {similarityRgb}"); + _output.WriteLine($"MS-SSIM RB channels (unchanged): {similarityRb}"); + _output.WriteLine($"MS-SSIM G channel (changed): {similarityG}"); + + // RB should be close to 1.0 as those channels are unchanged + Assert.True(similarityRb.Average > 0.99f, $"MS-SSIM for unchanged channels should be close to 1.0, got {similarityRb}"); + Assert.True(similarityRb.Percentile5 > 0.99f, $"5th percentile for unchanged channels should be close to 1.0, got {similarityRb.Percentile5}"); + + // G should be lower as that channel was modified + Assert.True(similarityG.Average < 0.95f, $"MS-SSIM for changed channel should be lower, got {similarityG}"); + Assert.True(similarityG.Percentile5 < 0.95f, $"5th percentile for changed channel should be lower, got {similarityG.Percentile5}"); + + // RGB combined should be between the two + Assert.True(similarityRgb.Average < similarityRb.Average, $"MS-SSIM for RGB should be lower than RB, got {similarityRgb} vs {similarityRb}"); + Assert.True(similarityRgb.Percentile5 < similarityRb.Percentile5, + $"5th percentile for RGB should be lower than RB, got {similarityRgb.Percentile5} vs {similarityRb.Percentile5}"); + Assert.True(similarityRgb.Average > similarityG.Average, $"MS-SSIM for RGB should be higher than G, got {similarityRgb} vs {similarityG}"); + Assert.True(similarityRgb.Percentile5 > similarityG.Percentile5, + $"5th percentile for RGB should be higher than G, got {similarityRgb.Percentile5} vs {similarityG.Percentile5}"); + } + + [Fact] + public void MultiScaleStructuralSimilarity_Oklab_WorkCorrectly() + { + // Arrange + using var original = new Image(256, 256); + + // Fill with random data + var random = new Random(42); + for (int y = 0; y < original.Height; y++) + { + for (int x = 0; x < original.Width; x++) + { + original[x, y] = new RgbaVector( + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble(), + (float)random.NextDouble()); + } + } + + using var copy = original.Clone(); + + // Create slightly modified image (add small amount of noise) + using var modified = original.Clone(); + modified.Mutate(ctx => ctx.ProcessPixelRowsAsVector4((row, point) => + { + for (int x = 0; x < row.Length; x++) + { + row[x] = new Vector4( + row[x].X + (float)(random.NextDouble() * 0.05), // Small random noise + row[x].Y + (float)(random.NextDouble() * 0.05), + row[x].Z + (float)(random.NextDouble() * 0.05), + row[x].W); + } + })); + + string channelMask = "rgba"; + + // Act + StructuralSimilarityResult similaritySame = StructuralSimilarity.MultiScaleStructuralSimilarity(original, copy, channelMask, true, false); + StructuralSimilarityResult similarityDiff = StructuralSimilarity.MultiScaleStructuralSimilarity(original, modified, channelMask, true, false); + + // Assert + _output.WriteLine($"MS-SSIM between copy images: {similaritySame}"); + Assert.True(similaritySame.Average > 0.999f, $"MS-SSIM between copy images should be close to 1.0, got {similaritySame}"); + + _output.WriteLine($"MS-SSIM between different images: {similarityDiff}"); + Assert.True(similarityDiff.Average < 1f, $"MS-SSIM between different images should be less than 1.0, got {similarityDiff}"); + Assert.True(similarityDiff.Average > .95f, $"MS-SSIM between different images should be more than 0.95, got {similarityDiff}"); + } + } +} diff --git a/BCnEncTests/Support/TestHelper.cs b/BCnEncTests/Support/TestHelper.cs index 5c92096..0ec8e66 100644 --- a/BCnEncTests/Support/TestHelper.cs +++ b/BCnEncTests/Support/TestHelper.cs @@ -1,18 +1,30 @@ using System; using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using BCnEncoder.Decoder; +using BCnEncoder.Decoder.Options; using BCnEncoder.Encoder; +using BCnEncoder.Encoder.Options; using BCnEncoder.ImageSharp; using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; -using Microsoft.Toolkit.HighPerformance; +using BCnEncoder.Shared.Colors; +using BCnEncoder.TextureFormats; +using BCnEncTests.Api; +using CommunityToolkit.HighPerformance; +using SharpEXR; using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using Xunit; using Xunit.Abstractions; +using Xunit.Sdk; namespace BCnEncTests.Support { @@ -20,218 +32,213 @@ public static class TestHelper { #region Assertions - public static void AssertPixelsEqual(Span originalPixels, Span pixels, CompressionQuality quality) + public static void AssertImagesEqual(Image expected, Image actual, int tolerance = 0) + where TPixel : unmanaged, IPixel { - var psnr = ImageQuality.PeakSignalToNoiseRatio( - MemoryMarshal.Cast(originalPixels), - MemoryMarshal.Cast(pixels)); - AssertPSNR(psnr, quality); + Assert.Equal(expected.Width, actual.Width); + Assert.Equal(expected.Height, actual.Height); + var size = Unsafe.SizeOf(); + var bytes1 = new byte[expected.Height * expected.Width * size]; + var bytes2 = new byte[actual.Height * actual.Width * size]; + var pixels1 = bytes1.AsSpan().Cast(); + var pixels2 = bytes2.AsSpan().Cast(); + + expected.CopyPixelDataTo(bytes1); + actual.CopyPixelDataTo(bytes2); + + for (var i = 0; i < bytes1.Length; i++) + { + if (Math.Abs(bytes1[i] - bytes2[i]) > tolerance) + { + var tPixel1 = pixels1[i / size]; + var tPixel2 = pixels2[i / size]; + throw NotEqualException.ForEqualValues(tPixel1.ToString()!, tPixel2.ToString()!, + $"Pixels were not exactly equal within tolerance of {tolerance}!"); + } + } } - public static void AssertPixelsEqual(Span originalPixels, Span pixels, CompressionQuality quality, ITestOutputHelper output = null) + public static void AssertPixelsEqual(ReadOnlyMemory2D expected, + ReadOnlyMemory2D actual, string channelMask, float tolerance) { - var rmse = ImageQuality.CalculateLogRMSE(originalPixels,pixels); - AssertRMSE(rmse, quality, output); - } + Assert.Equal(expected.Height, actual.Height); + Assert.Equal(expected.Width, actual.Width); - public static void AssertImagesEqual(Image original, Image image, CompressionQuality quality, bool countAlpha = true) - { - var psnr = CalculatePSNR(original, image, countAlpha); - AssertPSNR(psnr, quality); - } + ReadOnlySpan2D expectedSpan = expected.Span; + ReadOnlySpan2D actualSpan = actual.Span; + for (int y = 0; y < expected.Height; y++) + { + for (int x = 0; x < expected.Width; x++) + { + ColorRgbaFloat expectedPixel = expectedSpan[y, x]; + ColorRgbaFloat actualPixel = actualSpan[y, x]; + + if ((channelMask.Contains('r') && Math.Abs(expectedPixel.r - actualPixel.r) > tolerance) || + (channelMask.Contains('g') && Math.Abs(expectedPixel.g - actualPixel.g) > tolerance) || + (channelMask.Contains('b') && Math.Abs(expectedPixel.b - actualPixel.b) > tolerance) || + (channelMask.Contains('a') && Math.Abs(expectedPixel.a - actualPixel.a) > tolerance)) + { + ColorRgba32 expectedPixel32 = expectedPixel.As(); + ColorRgba32 actualPixel32 = actualPixel.As(); + + Assert.Fail($"Pixels != with tolerance: {tolerance} at (x: {x}, y: {y})\nExpected: {expectedPixel}\nActual: {actualPixel}\nExpected32: {expectedPixel32}\nActual32: {actualPixel32}\nMask: {channelMask}"); + } + } + } + } #endregion - #region Execute methods - - public static void ExecuteDecodingTest(KtxFile file, string outputFile) + public static void TestDecoding(string testImage, TTexture texture, ReadOnlyMemory2D reference, bool referenceIsLdr, BcDecoder decoder, string outFileFormat = null, float tolerance = 0) + where TTexture : ITextureFileFormat { - Assert.True(file.header.VerifyHeader()); - Assert.Equal((uint)1, file.header.NumberOfFaces); + var bcnData = texture.ToTextureData(); - var decoder = new BcDecoder(); - using var image = decoder.DecodeToImageRgba32(file); + string channelMask = GetChannelMask(bcnData.Format); - Assert.Equal((uint)image.Width, file.header.PixelWidth); - Assert.Equal((uint)image.Height, file.header.PixelHeight); + Assert.True(bcnData.Format != CompressionFormat.Unknown, $"Unknown format for {testImage}"); + Assert.True(bcnData.Width > 0, $"Invalid width for {testImage}"); + Assert.True(bcnData.Height > 0, $"Invalid height for {testImage}"); + Assert.True(bcnData.IsValid(), $"Invalid data for {testImage}"); + Assert.Equal(testImage.Contains("srgb"), bcnData.Format.IsSRGBFormat()); - using var outFs = File.OpenWrite(outputFile); - image.SaveAsPng(outFs); - } - - #region Dds - - public static void ExecuteDdsWritingTest(Image image, CompressionFormat format, string outputFile) - { - ExecuteDdsWritingTest(new[] { image }, format, outputFile); - } - - public static void ExecuteDdsWritingTest(Image[] images, CompressionFormat format, string outputFile) - { - var encoder = new BcEncoder(); - encoder.OutputOptions.Quality = CompressionQuality.Fast; - encoder.OutputOptions.GenerateMipMaps = true; - encoder.OutputOptions.Format = format; - encoder.OutputOptions.FileFormat = OutputFileFormat.Dds; + BCnTextureData decoded; - using var fs = File.OpenWrite(outputFile); - - if (images.Length == 1) + if (referenceIsLdr) { - encoder.EncodeToStream(images[0], fs); + // Roundtrip from Rgba32, to match reference images. + decoded = decoder.Decode(bcnData, CompressionFormat.Rgba32); + decoded = decoded.ConvertTo(CompressionFormat.RgbaFloat); } else { - encoder.EncodeCubeMapToStream(images[0], images[1], images[2], images[3], images[4], images[5], fs); + decoded = decoder.Decode(bcnData, CompressionFormat.RgbaFloat); } - } - - public static void ExecuteDdsReadingTest(DdsFile file, DxgiFormat format, string outputFile, bool assertAlpha = false) - { - Assert.Equal(format, file.header.ddsPixelFormat.DxgiFormat); - Assert.Equal(file.header.dwMipMapCount, (uint)file.Faces[0].MipMaps.Length); - - var decoder = new BcDecoder(); - decoder.InputOptions.DdsBc1ExpectAlpha = assertAlpha; - var images = decoder.DecodeAllMipMapsToImageRgba32(file); - - Assert.Equal((uint)images[0].Width, file.header.dwWidth); - Assert.Equal((uint)images[0].Height, file.header.dwHeight); - for (var i = 0; i < images.Length; i++) + if (!string.IsNullOrEmpty(outFileFormat)) { - if (assertAlpha) + for (var i = 0; i < decoded.NumMips; i++) { - var pixels = GetSinglePixelArrayAsColors(images[0]); - Assert.Contains(pixels, x => x.a == 0); + var mipDecoded = decoded.AsImage(i); + mipDecoded.SaveAsPng(string.Format(outFileFormat, i)); } - - using var outFs = File.OpenWrite(string.Format(outputFile, i)); - images[i].SaveAsPng(outFs); - images[i].Dispose(); } + + AssertPixelsEqual(reference, decoded.First.AsMemory2D(), channelMask, tolerance); } - #endregion + private static string GetChannelMask(CompressionFormat format) + { + switch (format) + { + case CompressionFormat.R8: + case CompressionFormat.R8S: + case CompressionFormat.R16: + case CompressionFormat.R16S: + case CompressionFormat.R16F: + case CompressionFormat.R32F: + case CompressionFormat.Bc4: + case CompressionFormat.Bc4S: + return "r"; + case CompressionFormat.R8G8: + case CompressionFormat.R8G8S: + case CompressionFormat.R16G16: + case CompressionFormat.R16G16S: + case CompressionFormat.R16G16F: + case CompressionFormat.R32G32F: + case CompressionFormat.Bc5: + case CompressionFormat.Bc5S: + return "rg"; + } - #region Cancellation + return format.SupportsAlpha() ? "rgba" : "rgb"; + } - public static async Task ExecuteCancellationTest(Image image, bool isParallel) + public static void TestDecodingHdr(TTexture texture, Image reference, BcDecoder decoder = null, string outMipFileFormat = null) + where TTexture : ITextureFileFormat { - var encoder = new BcEncoder(CompressionFormat.Bc7); - encoder.OutputOptions.Quality = CompressionQuality.Fast; - encoder.Options.IsParallel = isParallel; + decoder ??= new BcDecoder(); + var decoded = decoder.Decode(texture.ToTextureData(), CompressionFormat.RgbaFloat); - var source = new CancellationTokenSource(TimeSpan.FromMilliseconds(100)); - await Assert.ThrowsAnyAsync(() => - encoder.EncodeToRawBytesAsync(image, 0, source.Token)); - } + Assert.Equal(CompressionFormat.RgbaFloat, decoded.Format); - #endregion + using var imageDecoded = decoded.AsImageRgbaVector(); - #endregion + AssertImagesEqual(reference, imageDecoded); - public static float DecodeKtxCheckPSNR(string filename, Image original) - { - using var fs = File.OpenRead(filename); - var ktx = KtxFile.Load(fs); - var decoder = new BcDecoder() + if (!string.IsNullOrEmpty(outMipFileFormat)) { - OutputOptions = { Bc4Component = ColorComponent.Luminance } - }; - using var img = decoder.DecodeToImageRgba32(ktx); - - return CalculatePSNR(original, img); + for (var i = 0; i < decoded.NumMips; i++) + { + var mipDecoded = decoded.AsImageRgbaVector(i); + mipDecoded.SaveAsPng(string.Format(outMipFileFormat, i)); + } + } } - public static float DecodeKtxCheckRMSEHdr(string filename, HdrImage original) + public static void TestEncodingLdr(Image original, TextureType texType, string outFileName, CompressionFormat format, CompressionQuality quality, ITestOutputHelper output) + where TTexture : class, ITextureFileFormat, new() { - using var fs = File.OpenRead(filename); - var ktx = KtxFile.Load(fs); - var decoder = new BcDecoder() + var encoder = new BcEncoder(format) { + OutputOptions = + { + GenerateMipMaps = true, + Quality = quality, + ColorSpaceHandling = EncoderColorSpaceHandling.KeepAsIs, + AlphaHandling = EncoderAlphaHandling.AsIs, + } }; - - var decoded = decoder.DecodeHdr(ktx); - return ImageQuality.CalculateLogRMSE(original.pixels, decoded); - } + if (!Directory.Exists("encoding")) + Directory.CreateDirectory("encoding"); + var texture = encoder.EncodeToTexture(original.ToBCnTextureData()); + using var fs = File.OpenWrite(Path.Combine("encoding/", outFileName)); + texture.WriteToStream(fs); - public static void ExecuteEncodingTest(Image image, CompressionFormat format, CompressionQuality quality, string filename, ITestOutputHelper output) - { - var encoder = new BcEncoder(); - encoder.OutputOptions.Quality = quality; - encoder.OutputOptions.GenerateMipMaps = true; - encoder.OutputOptions.Format = format; - - var fs = File.OpenWrite(filename); - encoder.EncodeToStream(image, fs); - fs.Close(); - - var psnr = DecodeKtxCheckPSNR(filename, image); - output.WriteLine("RGBA PSNR: " + psnr + "db"); - AssertPSNR(psnr, encoder.OutputOptions.Quality); - } + var decoder = new BcDecoder() + { + OutputOptions = + { + OutputColorSpace = OutputColorSpaceTarget.KeepAsIs, + AlphaHandling = DecoderAlphaHandling.KeepAsIs + } + }; - public static void ExecuteHdrEncodingTest(HdrImage image, CompressionFormat format, CompressionQuality quality, string filename, ITestOutputHelper output) - { - var encoder = new BcEncoder(); - encoder.OutputOptions.Quality = quality; - encoder.OutputOptions.GenerateMipMaps = true; - encoder.OutputOptions.Format = format; - - var fs = File.OpenWrite(filename); - encoder.EncodeToStreamHdr(image.pixels.AsMemory().AsMemory2D(image.height, image.width), fs); - fs.Close(); - - var rmse = DecodeKtxCheckRMSEHdr(filename, image); - output.WriteLine("RGBFloat RMSE: " + rmse); - AssertRMSE(rmse, encoder.OutputOptions.Quality); - } + var decoded = decoder.Decode(texture.ToTextureData(), CompressionFormat.RgbaFloat); - private static float CalculatePSNR(Image original, Image decoded, bool countAlpha = true) - { - var pixels = GetSinglePixelArrayAsColors(original); - var pixels2 = GetSinglePixelArrayAsColors(decoded); - - return ImageQuality.PeakSignalToNoiseRatio(pixels, pixels2, countAlpha); - } + using var imageDecoded = decoded.AsImageRgbaVector(); - public static void AssertPSNR(float psnr, CompressionQuality quality) - { - if (quality == CompressionQuality.Fast) - { - Assert.True(psnr > 25); - } - else - { - Assert.True(psnr > 30); - } + using var fs2 = File.OpenWrite(Path.Combine("encoding/", outFileName+".png")); + imageDecoded.SaveAsPng(fs2); + + ImageQuality.AssertImageQuality(original, imageDecoded, texType, quality, format, GetChannelMask(format), output); } - public static void AssertRMSE(float rmse, CompressionQuality quality, ITestOutputHelper output = null) + public static ColorRgba32[] GetSinglePixelArrayAsColors(Image original) { - output?.WriteLine($"RMSE: {rmse} , quality: {quality}"); - if (quality == CompressionQuality.Fast) - { - Assert.True(rmse < 0.1); - } - else + ColorRgba32[] pixels = new ColorRgba32[original.Width * original.Height]; + for (var y = 0; y < original.Height; y++) { - Assert.True(rmse < 0.04); + for (var x = 0; x < original.Width; x++) + { + var oPixel = original[x, y]; + pixels[y * original.Width + x] = new ColorRgba32(oPixel.R, oPixel.G, oPixel.B, oPixel.A); + } } + return pixels; } - public static ColorRgba32[] GetSinglePixelArrayAsColors(Image original) + public static ColorRgbaFloat[] GetSinglePixelArrayAsColors(Image original) { - ColorRgba32[] pixels = new ColorRgba32[original.Width * original.Height]; + var pixels = new ColorRgbaFloat[original.Width * original.Height]; for (var y = 0; y < original.Height; y++) { for (var x = 0; x < original.Width; x++) { var oPixel = original[x, y]; - pixels[y * original.Width + x] = new ColorRgba32(oPixel.R, oPixel.G, oPixel.B, oPixel.A); + pixels[y * original.Width + x] = new ColorRgbaFloat(oPixel.R, oPixel.G, oPixel.B, oPixel.A); } } return pixels; @@ -261,5 +268,74 @@ public static void SetSinglePixelArray(Image dest, T[] pixels) where T : u } } } + + public static Memory2D GetReferenceImage(string filePath) + { + var imageSharpExtensions = new string[] { ".png", ".jpg", ".jpeg", ".bmp" }; + var openExrExtensions = new string[] { ".exr" }; + var radianceExtensions = new string[] { ".hdr" }; + + var extension = Path.GetExtension(filePath); + + if (imageSharpExtensions.Contains(extension)) + { + var image = Image.Load(filePath); + + // Convert to linear rgb + // for (int y = 0; y < image.Height; y++) + // { + // Memory row = image.DangerousGetPixelRowMemory(y); + // + // for (int x = 0; x < image.Width; x++) + // { + // var pixel = row.Span[x]; + // + // Rgb rgb = new Rgb(pixel.R, pixel.G, pixel.B); + // LinearRgb linearRgb = ColorSpaceConverter.ToLinearRgb(rgb); + // pixel.R = linearRgb.R; + // pixel.G = linearRgb.G; + // pixel.B = linearRgb.B; + // + // row.Span[x] = pixel; + // } + // } + + return image.ToBCnTextureData().First.AsMemory2D(); + } + if (openExrExtensions.Contains(extension)) + { + var exrFile = EXRFile.FromFile(filePath); + var part = exrFile.Parts[0]; + + part.Open(filePath); + + float[] exrData = part.GetFloats(ChannelConfiguration.RGB, false, GammaEncoding.Linear, true); + + Memory rgbaData = exrData.AsMemory().Cast(); + + return rgbaData.AsMemory2D(part.DataWindow.Height, part.DataWindow.Width); + } + + throw new ArgumentException($"Unsupported image format: {extension}"); + } + + public static string GetProjectRoot() + { + // Get the directory where the assembly is located + string assemblyLocation = Path.GetDirectoryName(typeof(DecodingTests).Assembly.Location); + + string projectRoot = assemblyLocation; + + // Go up to the project folder (or however many levels needed) + do + { + if (Path.GetPathRoot(projectRoot) == projectRoot) + throw new Exception("Could not find project root"); + + projectRoot = Path.GetFullPath(Path.Combine(projectRoot, "..")); + } while(!Directory.Exists(Path.Combine(projectRoot, "bin"))); + + return projectRoot; + } } } diff --git a/BCnEncTests/testImages/GenerateTestDds.ps1 b/BCnEncTests/testImages/GenerateTestDds.ps1 new file mode 100644 index 0000000..f1e09ae --- /dev/null +++ b/BCnEncTests/testImages/GenerateTestDds.ps1 @@ -0,0 +1,473 @@ +[CmdletBinding()] +param ( + [Parameter(Mandatory=$true)] + [string]$InputImage, + + [Parameter(Mandatory=$false)] + [ValidateSet('linear', 'srgb')] + [string]$ColorSpace = "linear", # default to linear + + [Parameter(Mandatory=$false)] + [string]$OutputDir = "./testdds", + + [Parameter(Mandatory=$false)] + [string]$TexConvPath, + + [Parameter(Mandatory=$false)] + [switch]$Help +) + +# Set VerbosePreference based on -Verbose parameter +if ($Verbose) { + $VerbosePreference = "Continue" +} else { + $VerbosePreference = "SilentlyContinue" +} + +# Show help if requested +if ($Help -or [string]::IsNullOrEmpty($InputImage)) { + $helpText = @" + DDS Test Image Generator - BCnEncoder.NET + + USAGE: + .\GenerateTestDds.ps1 -InputImage -ColorSpace [OPTIONS] + + PARAMETERS: + -InputImage Path to input image + -ColorSpace Input color space + -OutputDir Output directory (default: current) + -TexConvPath Path to texconv.exe (auto-detected from PATH by default) + -Verbose Show detailed output + -Help Display this help + + EXAMPLE: + .\GenerateTestDds.ps1 -InputImage texture.png -ColorSpace linear +"@ + + Write-Host $helpText + exit 0 +} + +# Input validation when not showing help +if ([string]::IsNullOrEmpty($InputImage)) { + Write-Error "InputImage parameter is required. Use -Help for usage information." + exit 1 +} + +if ([string]::IsNullOrEmpty($ColorSpace)) { + Write-Error "ColorSpace parameter is required. Use -Help for usage information." + exit 1 +} + +# Find texconv.exe in the PATH if not specified +if ([string]::IsNullOrEmpty($TexConvPath)) { + $TexConvPath = Get-Command texconv.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source + + if ([string]::IsNullOrEmpty($TexConvPath)) { + Write-Error "Could not find texconv.exe in PATH. Please specify the path using -TexConvPath." + exit 1 + } else { + Write-Verbose "Found texconv.exe at: $TexConvPath" + } +} + +# Verify texconv exists +if (!(Test-Path -Path $TexConvPath)) { + Write-Error "Could not find texconv.exe at $TexConvPath. Please specify the correct path." + exit 1 +} + +# Ensure output directory exists +if (!(Test-Path -Path $OutputDir)) { + New-Item -ItemType Directory -Path $OutputDir | Out-Null +} + +# Input validation +if (!(Test-Path -Path $InputImage)) { + Write-Error "Input image not found: $InputImage" + exit 1 +} + +$inputBaseName = [System.IO.Path]::GetFileNameWithoutExtension($InputImage) + +# Setup colorspace arguments +$colorSpaceArg = if ($ColorSpace -eq "srgb") { "-srgbi" } else { "" } + +# Define format mappings +$formatMappings = @( + # BC1 (DXT1) formats + @{ + Name = "bc1-linear"; + Format = "BC1_UNORM"; + DX10Support = $true; + LegacySupport = $true; + LegacyFormat = "DXT1"; + ColorSpace = "linear"; + }, + @{ + Name = "bc1-srgb"; + Format = "BC1_UNORM_SRGB"; + DX10Support = $true; + LegacySupport = $false; + ColorSpace = "srgb"; + }, + @{ + Name = "bc1-alpha-linear"; + Format = "BC1_UNORM"; + DX10Support = $true; + LegacySupport = $true; + LegacyFormat = "DXT1"; + ColorSpace = "linear"; + ExtraArgs = "-at 0.5"; + }, + @{ + Name = "bc1-alpha-srgb"; + Format = "BC1_UNORM_SRGB"; + DX10Support = $true; + LegacySupport = $false; + ColorSpace = "srgb"; + ExtraArgs = "-at 0.5"; + }, + + # BC2 (DXT3) formats + @{ + Name = "bc2-linear"; + Format = "BC2_UNORM"; + DX10Support = $true; + LegacySupport = $true; + LegacyFormat = "DXT3"; + ColorSpace = "linear"; + }, + @{ + Name = "bc2-srgb"; + Format = "BC2_UNORM_SRGB"; + DX10Support = $true; + LegacySupport = $false; + ColorSpace = "srgb"; + }, + + # BC3 (DXT5) formats + @{ + Name = "bc3-linear"; + Format = "BC3_UNORM"; + DX10Support = $true; + LegacySupport = $true; + LegacyFormat = "DXT5"; + ColorSpace = "linear"; + }, + @{ + Name = "bc3-srgb"; + Format = "BC3_UNORM_SRGB"; + DX10Support = $true; + LegacySupport = $false; + ColorSpace = "srgb"; + }, + + # BC4 formats + @{ + Name = "bc4-linear"; + Format = "BC4_UNORM"; + DX10Support = $true; + LegacySupport = $true; + LegacyFormat = "BC4_UNORM"; + ColorSpace = "linear"; + }, + @{ + Name = "bc4s-linear"; + Format = "BC4_SNORM"; + DX10Support = $true; + LegacySupport = $false; + ColorSpace = "linear"; + }, + + # BC5 formats + @{ + Name = "bc5-linear"; + Format = "BC5_UNORM"; + DX10Support = $true; + LegacySupport = $true; + LegacyFormat = "BC5_UNORM"; + ColorSpace = "linear"; + }, + @{ + Name = "bc5s-linear"; + Format = "BC5_SNORM"; + DX10Support = $true; + LegacySupport = $false; + ColorSpace = "linear"; + }, + + # BC6H formats + @{ + Name = "bc6u-linear"; + Format = "BC6H_UF16"; + DX10Support = $true; + LegacySupport = $false; + ColorSpace = "linear"; + }, + @{ + Name = "bc6s-linear"; + Format = "BC6H_SF16"; + DX10Support = $true; + LegacySupport = $false; + ColorSpace = "linear"; + }, + + # BC7 formats + @{ + Name = "bc7-linear"; + Format = "BC7_UNORM"; + DX10Support = $true; + LegacySupport = $false; + ColorSpace = "linear"; + }, + @{ + Name = "bc7-srgb"; + Format = "BC7_UNORM_SRGB"; + DX10Support = $true; + LegacySupport = $false; + ColorSpace = "srgb"; + }, + + # R8 formats + @{ + Name = "r8-linear"; + Format = "R8_UNORM"; + DX10Support = $true; + LegacySupport = $true; + ColorSpace = "linear"; + }, + @{ + Name = "r8s-linear"; + Format = "R8_SNORM"; + DX10Support = $true; + LegacySupport = $false; + ColorSpace = "linear"; + }, + + # R8G8 formats + @{ + Name = "r8g8-linear"; + Format = "R8G8_UNORM"; + DX10Support = $true; + LegacySupport = $true; + ColorSpace = "linear"; + }, + @{ + Name = "r8g8s-linear"; + Format = "R8G8_SNORM"; + DX10Support = $true; + LegacySupport = $false; + ColorSpace = "linear"; + }, + + # R10G10B10A2 format + @{ + Name = "r10g10b10a2-linear"; + Format = "R10G10B10A2_UNORM"; + DX10Support = $true; + LegacySupport = $false; + ColorSpace = "linear"; + }, + + # RGBA8 formats + @{ + Name = "rgba32-linear"; + Format = "R8G8B8A8_UNORM"; + DX10Support = $true; + LegacySupport = $true; + ColorSpace = "linear"; + }, + @{ + Name = "rgba32-srgb"; + Format = "R8G8B8A8_UNORM_SRGB"; + DX10Support = $true; + LegacySupport = $false; + ColorSpace = "srgb"; + }, + + # BGRA formats + @{ + Name = "bgra32-linear"; + Format = "B8G8R8A8_UNORM"; + DX10Support = $true; + LegacySupport = $true; + ColorSpace = "linear"; + }, + @{ + Name = "bgra32-srgb"; + Format = "B8G8R8A8_UNORM_SRGB"; + DX10Support = $true; + LegacySupport = $false; + ColorSpace = "srgb"; + }, + + # Float formats + @{ + Name = "rgba-float-linear"; + Format = "R32G32B32A32_FLOAT"; + DX10Support = $true; + LegacySupport = $true; + LegacyFormat = "FP32"; + ColorSpace = "linear"; + }, + @{ + Name = "rgba-half-linear"; + Format = "R16G16B16A16_FLOAT"; + DX10Support = $true; + LegacySupport = $true; + LegacyFormat = "FP16"; + ColorSpace = "linear"; + }, + @{ + Name = "rgb-float-linear"; + Format = "R32G32B32_FLOAT"; + DX10Support = $true; + LegacySupport = $false; + ColorSpace = "linear"; + } +) + +# Calculate total operations (DX10 + legacy formats when supported) +$totalCount = ($formatMappings | Where-Object { $_.DX10Support }).Count + ($formatMappings | Where-Object { $_.LegacySupport }).Count +$currentCount = 0 + +Write-Host "Generating encoded DDS files..." -ForegroundColor Cyan + +# Process each format +foreach ($format in $formatMappings) { + # Generate colorspace arg based on the target format colorspace + $formatColorSpaceArg = "" + + # Determine colorspace conversion flags + # NOTE: texconv handles colorspace in two ways: + # 1. Formats with _SRGB in their name (e.g. R8G8B8A8_UNORM_SRGB) automatically apply linear→sRGB conversion + # 2. Command flags (-srgbi, -srgbo) can also specify conversions + # Be careful not to double-convert by using both methods together + if ($format.ColorSpace -eq "srgb" -and $ColorSpace -eq "linear") { + # Converting from linear input to sRGB output + # For linear→sRGB, just use the sRGB DX10 format without explicit conversion flag + # The _SRGB format suffix will handle the conversion automatically + $formatColorSpaceArg = "" + } + elseif ($format.ColorSpace -eq "srgb" -and $ColorSpace -eq "srgb") { + # Keep as sRGB - input is sRGB, output is sRGB + # Tell texconv the input is sRGB so it doesn't treat it as linear + $formatColorSpaceArg = "-srgbi" + } + elseif ($format.ColorSpace -eq "linear" -and $ColorSpace -eq "srgb") { + # Converting from sRGB input to linear output + # Tell texconv the input is sRGB + $formatColorSpaceArg = "-srgbi" + } + # For linear to linear, don't specify any flags + elseif ($format.ColorSpace -eq "linear" -and $ColorSpace -eq "linear") { + # texconv treats inputs without -srgbi as linear by default + $formatColorSpaceArg = "" + } + + # Set extra args + $extraArgs = if ($format.ExtraArgs) { $format.ExtraArgs } else { "" } + + # Use premultiplied alpha when supported, but not for BC4 and BC5 formats + $premultipliedAlpha = "" + if (($format.Name -match "bc[1-3]" -or $format.Name -match "bc[67]" -or $format.Name -match "rgba" -or $format.Name -match "bgra") -and + (-not ($format.Name -match "bc4" -or $format.Name -match "bc5"))) { + $premultipliedAlpha = "-pmalpha" + } + + # Force single channel input for R8 formats (use only red channel) + $channelSelect = "" + if ($format.Name -match "^r8(-|s-)" -and -not ($format.Name -match "^r8g8")) { + $channelSelect = "--swizzle rrrr" + } + + # Generate DX10 header version + if ($format.DX10Support) { + $outputFile = Join-Path -Path $OutputDir -ChildPath "$inputBaseName-$($format.Name)-dx10.dds" + $currentCount++ + + Write-Progress -Activity "Generating test images" -Status "[$currentCount of $totalCount] Creating $outputFile" -PercentComplete (($currentCount / $totalCount) * 100) + + $cmd = "& `"$TexConvPath`" `"$InputImage`" -o `"$OutputDir`" -y -f $($format.Format) -dx10 $formatColorSpaceArg $premultipliedAlpha $channelSelect $extraArgs -sx -$($format.Name)-dx10" + Write-Verbose "Executing: $cmd" + + # Execute command and only show output if there's an error + $output = Invoke-Expression "$cmd 2>&1" + if ($LASTEXITCODE -ne 0) { + Write-Host "Error executing command: $cmd" -ForegroundColor Red + Write-Host $output -ForegroundColor Red + } + } + + # Generate legacy header version if supported + if ($format.LegacySupport) { + $legacyFormat = if ($format.LegacyFormat) { $format.LegacyFormat } else { $format.Format } + $outputFile = Join-Path -Path $OutputDir -ChildPath "$inputBaseName-$($format.Name).dds" + $currentCount++ + + Write-Progress -Activity "Generating test images" -Status "[$currentCount of $totalCount] Creating $outputFile" -PercentComplete (($currentCount / $totalCount) * 100) + + $cmd = "& `"$TexConvPath`" `"$InputImage`" -o `"$OutputDir`" -y -f $legacyFormat -dx9 $formatColorSpaceArg $premultipliedAlpha $channelSelect $extraArgs -sx -$($format.Name)" + Write-Verbose "Executing: $cmd" + + # Execute command and only show output if there's an error + $output = Invoke-Expression "$cmd 2>&1" + if ($LASTEXITCODE -ne 0) { + Write-Host "Error executing command: $cmd" -ForegroundColor Red + Write-Host $output -ForegroundColor Red + } + } +} + +Write-Progress -Activity "Generating test images" -Completed +Write-Host "All test images have been generated in $OutputDir" -ForegroundColor Green + +# After all formats are processed, decode each generated DDS file back to PNG for reference + +# Check if reference directory exists, if not create it +$referenceDir = Join-Path -Path $OutputDir -ChildPath "reference" +if (-not (Test-Path -Path $referenceDir)) { + New-Item -ItemType Directory -Path $referenceDir | Out-Null +} + +# Get all generated DDS files +$ddsFiles = Get-ChildItem -Path $OutputDir -Filter "*.dds" +$totalDdsFiles = $ddsFiles.Count +$currentDdsFile = 0 + +Write-Host "Generating reference decoded PNGs..." -ForegroundColor Cyan + +# Process each DDS file to create a reference PNG +foreach ($ddsFile in $ddsFiles) { + $currentDdsFile++ + $ddsPath = $ddsFile.FullName + $ddsName = $ddsFile.BaseName + $refPngPath = Join-Path -Path $referenceDir -ChildPath "$ddsName.png" + + # Show progress + Write-Progress -Activity "Decoding DDS files to reference PNGs" -Status "Processing $ddsName" -PercentComplete (($currentDdsFile / $totalDdsFiles) * 100) + + # Check if this is a format that used premultiplied alpha during encoding + $needsUnpremultiply = $ddsName -match "-bgra32-|rgba32-|bc1-alpha-|bc2-|bc3-|bc7-" + $isLinear = $ddsName -match "linear" + + # Decode the DDS to PNG using texconv + # -f R8G8B8A8_UNORM ensures consistent output format + # -ft png specifies the output format as PNG + # -alpha converts premultiplied alpha to straight alpha + # $alphaFlag = if ($needsUnpremultiply) { "-alpha" } else { "" } + $outputPixelFormat = if ($isLinear) { "R8G8B8A8_UNORM" } else { "R8G8B8A8_UNORM_SRGB" } + + $decodeCmd = "& `"$TexConvPath`" `"$ddsPath`" -o `"$referenceDir`" -f $outputPixelFormat -ft png -y -nologo" + Write-Verbose "Decoding DDS to PNG: $decodeCmd" + + $cmdResult = Invoke-Expression $decodeCmd 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Host "Error decoding $ddsPath to PNG:" -ForegroundColor Red + Write-Host $cmdResult -ForegroundColor Red + } +} + +Write-Progress -Activity "Decoding DDS files to reference PNGs" -Completed +Write-Host "Reference decoded PNGs have been generated in $referenceDir" -ForegroundColor Green \ No newline at end of file diff --git a/BCnEncTests/testImages/GenerateTestEncodes.sh b/BCnEncTests/testImages/GenerateTestEncodes.sh new file mode 100755 index 0000000..51b1779 --- /dev/null +++ b/BCnEncTests/testImages/GenerateTestEncodes.sh @@ -0,0 +1,570 @@ +#!/usr/bin/env bash +# GenerateTestEncodes.sh +# Generates test DDS, KTX, and KTX2 texture files using cuttlefish, then decodes +# reference PNGs. KTX2 files are converted from KTX using ktx2ktx2. Reference +# PNGs for KTX/KTX2 are extracted with ktx extract (KTX2 only); DDS reference +# PNGs are decoded with tacentview. +# Replaces GenerateTestDds.ps1 and GenerateTestKtx.ps1. +# +# Notes: +# - Only DX10-header DDS files are generated; cuttlefish does not support DX9/legacy headers. +# - ETC/EAC/ASTC/PVRTC formats are KTX-only (not valid in DDS containers). + +set -euo pipefail + +usage() { + cat <<'EOF' +GenerateTestEncodes.sh - Generate test DDS, KTX, and KTX2 files for BCnEncoder.NET + +USAGE: + ./GenerateTestEncodes.sh -i [OPTIONS] + ./GenerateTestEncodes.sh --no-encode [OPTIONS] + +OPTIONS: + -i, --input Path to input image (required unless --no-encode) + -d, --dds-dir DDS directory (default: ./testdds) + -k, --ktx-dir KTX directory (default: ./testktx) + -K, --ktx2-dir KTX2 directory (default: ./testktx2) + --no-encode Skip encoding; only decode reference PNGs from + existing files in the DDS, KTX, and KTX2 directories + --tacentview-ktx Use tacentview for KTX reference decoding instead + of the default ktx extract method + +TOOLS: + cuttlefish Required for encoding (not needed with --no-encode) + ktx2ktx2 Required for KTX -> KTX2 conversion + ktx Required for KTX2 reference PNG extraction + tacentview Required for DDS reference PNG decoding; also used for KTX + decoding if --tacentview-ktx is set +EOF +} + +INPUT="" +DDS_DIR="./testdds" +KTX_DIR="./testktx" +KTX2_DIR="./testktx2" +NO_ENCODE=0 +TACENTVIEW_KTX=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + -i|--input) INPUT="$2"; shift 2 ;; + -d|--dds-dir) DDS_DIR="$2"; shift 2 ;; + -k|--ktx-dir) KTX_DIR="$2"; shift 2 ;; + -K|--ktx2-dir) KTX2_DIR="$2"; shift 2 ;; + --no-encode) NO_ENCODE=1; shift ;; + --tacentview-ktx) TACENTVIEW_KTX=1; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown option: $1" >&2; usage >&2; exit 1 ;; + esac +done + +if [[ "$NO_ENCODE" == "0" ]]; then + [[ -z "$INPUT" ]] && { echo "Error: -i/--input is required" >&2; usage >&2; exit 1; } + [[ ! -f "$INPUT" ]] && { echo "Error: Input file not found: $INPUT" >&2; exit 1; } + command -v cuttlefish &>/dev/null || { echo "Error: cuttlefish not found in PATH" >&2; exit 1; } +fi + +HAS_TACENTVIEW=0 +command -v tacentview &>/dev/null && HAS_TACENTVIEW=1 \ + || echo "Warning: tacentview not found — DDS reference PNG decoding will be skipped" + +HAS_KTX_TOOLS=0 +command -v ktx2ktx2 &>/dev/null && command -v ktx &>/dev/null && HAS_KTX_TOOLS=1 \ + || echo "Warning: ktx2ktx2/ktx not found — KTX2 conversion and reference PNG extraction will be skipped" + +HAS_COMPRESSONATOR=0 +command -v compressonatorcli &>/dev/null && HAS_COMPRESSONATOR=1 \ + || echo "Warning: compressonatorcli not found — ATC format encoding will be skipped" + +if [[ "$NO_ENCODE" == "0" ]]; then + BASE_NAME="$(basename "${INPUT%.*}")" + mkdir -p "$DDS_DIR" "$KTX_DIR" "$KTX2_DIR" +fi + +run_cf() { + local output="$1"; shift + echo " cuttlefish$(printf ' %q' "$@") -o $output" + if [[ -f "$output" ]]; then + echo " Skipped (already exists): $output" + return + fi + if cuttlefish "$@" -o "$output"; then + echo " Created $output" + else + echo " Warning: cuttlefish failed for $output" >&2 + fi +} + +# encode [extra_flags...] +# cf_type may be empty when the format name implies its type (e.g. B10G11R11_UFloat) +encode() { + local name="$1" output="$2" cf_fmt="$3" cf_type="$4" + shift 4 + local flags=(-i "$INPUT" -f "$cf_fmt" -Q highest) + [[ -n "$cf_type" ]] && flags+=(-t "$cf_type") + flags+=("$@") + run_cf "$output" "${flags[@]}" +} + +# ─── Format tables ───────────────────────────────────────────────────────────── +# Fields: name | cuttlefish_format | cuttlefish_type +# cuttlefish_type is left empty when the format name implies its own data type +# (e.g. B10G11R11_UFloat) or when the cuttlefish default (unorm) is desired. + +# Formats written to both DDS and KTX +COMMON_FORMATS=( + # BC1 + "bc1-rgb|BC1_RGB|unorm" + "bc1-rgba|BC1_RGBA|unorm" + # BC2 (DXT3) + "bc2|BC2|unorm" + # BC3 (DXT5) + "bc3|BC3|unorm" + # BC4 + "bc4|BC4|unorm" + "bc4s|BC4|snorm" + # BC5 + "bc5|BC5|unorm" + "bc5s|BC5|snorm" + # BC6H + "bc6h|BC6H|ufloat" + "bc6h-signed|BC6H|float" + # BC7 + "bc7|BC7|unorm" + # Uncompressed 4-bit + "r4g4|R4G4|unorm" + "r4g4b4a4|R4G4B4A4|unorm" + "b4g4r4a4|B4G4R4A4|unorm" + "a4r4g4b4|A4R4G4B4|unorm" + # Uncompressed 5/6-bit + "r5g6b5|R5G6B5|unorm" + "b5g6r5|B5G6R5|unorm" + "r5g5b5a1|R5G5B5A1|unorm" + "b5g5r5a1|B5G5R5A1|unorm" + "a1r5g5b5|A1R5G5B5|unorm" + # Uncompressed 8-bit + "r8|R8|unorm" + "r8s|R8|snorm" + "r8g8|R8G8|unorm" + "r8g8s|R8G8|snorm" + "r8g8b8|R8G8B8|unorm" + "b8g8r8|B8G8R8|unorm" + "r8g8b8a8|R8G8B8A8|unorm" + "b8g8r8a8|B8G8R8A8|unorm" + "a8b8g8r8|A8B8G8R8|unorm" + # Uncompressed 10-bit + # A2R10G10B10 = DirectX R10G10B10A2 (same layout, different naming convention) + # A2B10G10R10 = BGRA variant + "a2r10g10b10|A2R10G10B10|unorm" + "a2b10g10r10|A2B10G10R10|unorm" + # Uncompressed 16-bit + "r16|R16|unorm" + "r16s|R16|snorm" + "r16-float|R16|float" + "r16g16|R16G16|unorm" + "r16g16s|R16G16|snorm" + "r16g16-float|R16G16|float" + "r16g16b16|R16G16B16|unorm" + "r16g16b16-float|R16G16B16|float" + "r16g16b16a16|R16G16B16A16|unorm" + "r16g16b16a16-float|R16G16B16A16|float" + # Uncompressed 32-bit (float only — 32-bit unorm is not practical for textures) + "r32-float|R32|float" + "r32g32-float|R32G32|float" + "r32g32b32-float|R32G32B32|float" + "r32g32b32a32-float|R32G32B32A32|float" + # Special float formats (type is inherent in the format name, no -t flag needed) + "b10g11r11-ufloat|B10G11R11_UFloat|" + "e5b9g9r9-ufloat|E5B9G9R9_UFloat|" +) + +# Formats for KTX only — not valid in DDS containers +KTX_EXTRA_FORMATS=( + # ETC1 + "etc1|ETC1|unorm" + # ETC2 + "etc2-rgb|ETC2_R8G8B8|unorm" + "etc2-rgba|ETC2_R8G8B8A8|unorm" + "etc2-rgb-a1|ETC2_R8G8B8A1|unorm" + # EAC (single- and dual-channel, signed and unsigned) + "eac-r11|EAC_R11|unorm" + "eac-r11s|EAC_R11|snorm" + "eac-rg11|EAC_R11G11|unorm" + "eac-rg11s|EAC_R11G11|snorm" + # ASTC + "astc-4x4|ASTC_4x4|" + "astc-5x4|ASTC_5x4|" + "astc-5x5|ASTC_5x5|" + "astc-6x5|ASTC_6x5|" + "astc-6x6|ASTC_6x6|" + "astc-8x5|ASTC_8x5|" + "astc-8x6|ASTC_8x6|" + "astc-8x8|ASTC_8x8|" + "astc-10x5|ASTC_10x5|" + "astc-10x6|ASTC_10x6|" + "astc-10x8|ASTC_10x8|" + "astc-10x10|ASTC_10x10|" + "astc-12x10|ASTC_12x10|" + "astc-12x12|ASTC_12x12|" + # PVRTC + "pvrtc1-rgb-2bpp|PVRTC1_RGB_2BPP|" + "pvrtc1-rgba-2bpp|PVRTC1_RGBA_2BPP|" + "pvrtc1-rgb-4bpp|PVRTC1_RGB_4BPP|" + "pvrtc1-rgba-4bpp|PVRTC1_RGBA_4BPP|" + "pvrtc2-rgba-2bpp|PVRTC2_RGBA_2BPP|" + "pvrtc2-rgba-4bpp|PVRTC2_RGBA_4BPP|" +) + +# ATC formats — KTX only, encoded with compressonatorcli (not supported by cuttlefish) +# Fields: name | compressonator_format +KTX_ATC_FORMATS=( + "atc|ATC_RGB" + "atc-explicit-alpha|ATC_RGBA_Explicit" + "atc-interpolated-alpha|ATC_RGBA_Interpolated" +) + +run_compressonator() { + local output="$1"; shift + echo " compressonatorcli$(printf ' %q' "$@") -o $output" + if [[ -f "$output" ]]; then + echo " Skipped (already exists): $output" + return + fi + if compressonatorcli "$@" "$output"; then + echo " Created $output" + else + echo " Warning: compressonatorcli failed for $output" >&2 + fi +} + +if [[ "$NO_ENCODE" == "0" ]]; then +# ─── Generate DDS ──────────────────────────────────────────────────────────── +echo "Generating DDS files..." +total=${#COMMON_FORMATS[@]} +count=0 + +for entry in "${COMMON_FORMATS[@]}"; do + count=$((count + 1)) + IFS='|' read -r name cf_fmt cf_type <<< "$entry" + output="$DDS_DIR/${BASE_NAME}-${name}-dx10.dds" + printf '[%d/%d] %s\n' "$count" "$total" "$name" + encode "$name" "$output" "$cf_fmt" "$cf_type" +done + +echo "DDS generation complete. Files written to: $DDS_DIR" + +# ─── Generate KTX ──────────────────────────────────────────────────────────── +echo "" +echo "Generating KTX files..." +KTX_FORMATS=("${COMMON_FORMATS[@]}" "${KTX_EXTRA_FORMATS[@]}") +total=${#KTX_FORMATS[@]} +count=0 + +for entry in "${KTX_FORMATS[@]}"; do + count=$((count + 1)) + IFS='|' read -r name cf_fmt cf_type <<< "$entry" + output="$KTX_DIR/${BASE_NAME}-${name}.ktx" + printf '[%d/%d] %s\n' "$count" "$total" "$name" + # -m generates a full mipmap chain + encode "$name" "$output" "$cf_fmt" "$cf_type" -m +done + +echo "KTX generation complete. Files written to: $KTX_DIR" + +# ─── Generate ATC KTX (via compressonatorcli) ──────────────────────────────── +if [[ "$HAS_COMPRESSONATOR" == "1" ]]; then + echo "" + echo "Generating ATC KTX files with compressonatorcli..." + total=${#KTX_ATC_FORMATS[@]} + count=0 + for entry in "${KTX_ATC_FORMATS[@]}"; do + count=$((count + 1)) + IFS='|' read -r name ca_fmt <<< "$entry" + output="$KTX_DIR/${BASE_NAME}-${name}.ktx" + printf '[%d/%d] %s\n' "$count" "$total" "$name" + run_compressonator "$output" -fd "$ca_fmt" "$INPUT" + done + echo "ATC KTX generation complete." +else + echo "" + echo "Skipping ATC format encoding (compressonatorcli not available)." +fi + +fi # NO_ENCODE + +# ─── Convert KTX -> KTX2 ───────────────────────────────────────────────────── +# ktx extract (used for reference PNGs) only supports KTX2, so convert each +# KTX file. The pixel data is identical; KTX2 is just a different container. + +if [[ "$HAS_KTX_TOOLS" == "1" ]]; then + mkdir -p "$KTX2_DIR" + ktx_files=("$KTX_DIR"/*.ktx) + if [[ -f "${ktx_files[0]}" ]]; then + echo "" + echo "Converting KTX -> KTX2..." + total="${#ktx_files[@]}" count=0 + for ktx in "${ktx_files[@]}"; do + count=$((count + 1)) + name="$(basename "${ktx%.*}")" + ktx2="$KTX2_DIR/${name}.ktx2" + printf '[%d/%d] %s\n' "$count" "$total" "$name" + if [[ -f "$ktx2" ]]; then + echo " Skipped (already exists): $ktx2" + elif ktx2ktx2 -o "$ktx2" "$ktx"; then + echo " Created $ktx2" + else + echo " Warning: ktx2ktx2 failed for $ktx" >&2 + fi + done + echo "KTX2 conversion complete. Files written to: $KTX2_DIR" + else + echo "No KTX files found in $KTX_DIR — skipping KTX2 conversion" + fi +else + echo "" + echo "Skipping KTX2 conversion (ktx2ktx2 not available)." +fi + +# ─── Reference PNG decode ───────────────────────────────────────────────────── +# DDS: decoded with tacentview +# KTX/KTX2: decoded with ktx extract (KTX2 only); PNGs are then copied from +# the KTX2 reference folder into the KTX reference folder since the +# pixel data is identical. Falls back to tacentview if --tacentview-ktx. +# +# HDR formats (BC6H, float, ufloat) get tone=1.0 for tacentview so the preview +# is representable as an 8-bit PNG. + +decode_dds_references() { + local dir="$1" + local ref_dir="$dir/reference" + mkdir -p "$ref_dir" + + local files=("$dir"/*.dds) + [[ -f "${files[0]}" ]] || { echo "No .dds files found in $dir"; return; } + + local total="${#files[@]}" count=0 + echo "Decoding DDS reference PNGs with tacentview..." + + for src in "${files[@]}"; do + count=$((count + 1)) + local name; name="$(basename "${src%.*}")" + local out_png="${src%.*}.png" + local ref_png="$ref_dir/${name}.png" + + printf '[%d/%d] %s\n' "$count" "$total" "$name" + + if [[ -f "$ref_png" ]]; then + echo " Skipped (already exists): $ref_png" + continue + fi + + local params="corr=none" + + if tacentview -c -w --inDDS "$params" -o png "$src"; then + if [[ -f "$out_png" ]]; then + mv "$out_png" "$ref_png" + echo " Created $ref_png" + else + echo " Warning: expected $out_png not found" >&2 + fi + else + echo " Warning: tacentview failed for $src" >&2 + fi + done + + echo "DDS reference PNGs written to: $ref_dir" +} + +decode_ktx2_references() { + local ktx2_dir="$1" ktx_dir="$2" + local ktx2_ref_dir="$ktx2_dir/reference" + local ktx_ref_dir="$ktx_dir/reference" + mkdir -p "$ktx2_ref_dir" "$ktx_ref_dir" + + local files=("$ktx2_dir"/*.ktx2) + [[ -f "${files[0]}" ]] || { echo "No .ktx2 files found in $ktx2_dir"; return; } + + local total="${#files[@]}" count=0 + echo "Extracting KTX2 reference PNGs with ktx extract (falling back to tacentview for block-compressed)..." + + for src in "${files[@]}"; do + count=$((count + 1)) + local name; name="$(basename "${src%.*}")" + local ktx_src="$ktx_dir/${name}.ktx" + local ktx2_ref_png="$ktx2_ref_dir/${name}.png" + local ktx_ref_png="$ktx_ref_dir/${name}.png" + + printf '[%d/%d] %s\n' "$count" "$total" "$name" + + if [[ ! -f "$ktx2_ref_png" ]]; then + # ktx extract only decodes uncompressed KTX2 to PNG; block-compressed + # KTX2 files (BCn, ETC, ASTC) are not "transcodable" without BasisLZ + # supercompression, so extract won't produce a PNG for those. We detect + # this by checking whether the output file was actually created. + ktx extract "$src" "$ktx2_ref_png" 2>/dev/null || true + if [[ -f "$ktx2_ref_png" ]]; then + echo " Created $ktx2_ref_png" + elif [[ "$HAS_TACENTVIEW" == "1" && -f "$ktx_src" ]]; then + echo " ktx extract produced no PNG (block-compressed); falling back to tacentview" + local out_png="${ktx_src%.*}.png" + local params="corr=none" + if tacentview -c -w --inKTX "$params" -o png "$ktx_src" && [[ -f "$out_png" ]]; then + mv "$out_png" "$ktx2_ref_png" + echo " Created $ktx2_ref_png" + elif [[ "$HAS_COMPRESSONATOR" == "1" && -f "$ktx_src" ]]; then + echo " tacentview fallback failed; trying compressonatorcli" + if compressonatorcli "$ktx_src" "$ktx2_ref_png" 2>/dev/null && [[ -f "$ktx2_ref_png" ]]; then + echo " Created $ktx2_ref_png" + else + echo " Warning: all decode methods failed for $name" >&2 + continue + fi + else + echo " Warning: tacentview fallback failed for $name" >&2 + continue + fi + elif [[ "$HAS_COMPRESSONATOR" == "1" && -f "$ktx_src" ]]; then + echo " ktx extract produced no PNG; falling back to compressonatorcli" + if compressonatorcli "$ktx_src" "$ktx2_ref_png" 2>/dev/null && [[ -f "$ktx2_ref_png" ]]; then + echo " Created $ktx2_ref_png" + else + echo " Warning: compressonatorcli decode failed for $name" >&2 + continue + fi + else + echo " Warning: no PNG produced for $name (block-compressed; tacentview and compressonatorcli unavailable)" >&2 + continue + fi + else + echo " Skipped (already exists): $ktx2_ref_png" + fi + + # Copy to KTX reference folder — pixel data is identical + if [[ -f "$ktx_ref_png" ]]; then + echo " Skipped (already exists): $ktx_ref_png" + else + cp "$ktx2_ref_png" "$ktx_ref_png" + echo " Copied to $ktx_ref_png" + fi + done + + echo "KTX2 reference PNGs written to: $ktx2_ref_dir" + echo "KTX reference PNGs written to: $ktx_ref_dir" +} + +decode_ktx_references_tacentview() { + local dir="$1" + local ref_dir="$dir/reference" + mkdir -p "$ref_dir" + + local files=("$dir"/*.ktx) + [[ -f "${files[0]}" ]] || { echo "No .ktx files found in $dir"; return; } + + local total="${#files[@]}" count=0 + echo "Decoding KTX reference PNGs with tacentview..." + + for src in "${files[@]}"; do + count=$((count + 1)) + local name; name="$(basename "${src%.*}")" + local out_png="${src%.*}.png" + local ref_png="$ref_dir/${name}.png" + + printf '[%d/%d] %s\n' "$count" "$total" "$name" + + if [[ -f "$ref_png" ]]; then + echo " Skipped (already exists): $ref_png" + continue + fi + + local params="corr=none" + + if tacentview -c -w --inKTX "$params" -o png "$src"; then + if [[ -f "$out_png" ]]; then + mv "$out_png" "$ref_png" + echo " Created $ref_png" + else + echo " Warning: expected $out_png not found" >&2 + fi + else + echo " Warning: tacentview failed for $src" >&2 + fi + done + + echo "KTX reference PNGs written to: $ref_dir" +} + +echo "" +if [[ "$HAS_TACENTVIEW" == "1" ]]; then + [[ -d "$DDS_DIR" ]] && decode_dds_references "$DDS_DIR" \ + || echo "Skipping DDS decode: $DDS_DIR does not exist" +else + echo "Skipping DDS reference PNG decode (tacentview not available)." +fi + +echo "" +if [[ "$TACENTVIEW_KTX" == "1" ]]; then + if [[ "$HAS_TACENTVIEW" == "1" ]]; then + [[ -d "$KTX_DIR" ]] && decode_ktx_references_tacentview "$KTX_DIR" \ + || echo "Skipping KTX decode: $KTX_DIR does not exist" + else + echo "Skipping KTX reference PNG decode (tacentview not available)." + fi +elif [[ "$HAS_KTX_TOOLS" == "1" ]]; then + if [[ -d "$KTX2_DIR" ]]; then + decode_ktx2_references "$KTX2_DIR" "$KTX_DIR" + else + echo "Skipping KTX/KTX2 decode: $KTX2_DIR does not exist" + fi +else + echo "Skipping KTX reference PNG decode (ktx2ktx2/ktx not available; use --tacentview-ktx to use tacentview instead)." +fi + +# Compressonatorcli mop-up: decode ATC KTX files that have no reference PNG yet. +# compressonatorcli cannot write PNG directly, so we decode to an intermediate +# uncompressed DDS (ARGB_8888) and then use tacentview to produce the final PNG. +# Only ATC files are attempted — other formats that fell through are unsupported +# by compressonatorcli's KTX plugin and would just produce errors. +if [[ "$HAS_COMPRESSONATOR" == "1" && -d "$KTX_DIR" ]]; then + ktx_ref_dir="$KTX_DIR/reference" + ktx2_ref_dir="$KTX2_DIR/reference" + mkdir -p "$ktx_ref_dir" + + # Build list of ATC KTX files that are missing a reference PNG. + # Match by filename suffix derived from KTX_ATC_FORMATS. + atc_pending=() + for entry in "${KTX_ATC_FORMATS[@]}"; do + IFS='|' read -r atc_name _ <<< "$entry" + for src in "$KTX_DIR"/*-"${atc_name}".ktx; do + [[ -f "$src" ]] || continue + name="$(basename "${src%.*}")" + [[ -f "$ktx_ref_dir/${name}.png" ]] || atc_pending+=("$src") + done + done + + if [[ "${#atc_pending[@]}" -gt 0 ]]; then + echo "" + echo "Decoding ${#atc_pending[@]} ATC KTX reference PNGs with compressonatorcli..." + total="${#atc_pending[@]}" count=0 + for src in "${atc_pending[@]}"; do + count=$((count + 1)) + name="$(basename "${src%.*}")" + ktx_ref_png="$ktx_ref_dir/${name}.png" + printf '[%d/%d] %s\n' "$count" "$total" "$name" + + if compressonatorcli -fd ARGB_8888 "$src" "$ktx_ref_png" && [[ -f "$ktx_ref_png" ]]; then + echo " Created $ktx_ref_png" + # Mirror into KTX2 reference if the KTX2 file exists + if [[ -d "$ktx2_ref_dir" && -f "$KTX2_DIR/${name}.ktx2" && ! -f "$ktx2_ref_dir/${name}.png" ]]; then + cp "$ktx_ref_png" "$ktx2_ref_dir/${name}.png" + echo " Copied to $ktx2_ref_dir/${name}.png" + fi + else + echo " Warning: compressonatorcli decode failed for $name" >&2 + fi + done + fi +fi + +echo "" +echo "All test images generated successfully!" diff --git a/BCnEncTests/testImages/GenerateTestKtx.ps1 b/BCnEncTests/testImages/GenerateTestKtx.ps1 new file mode 100644 index 0000000..e7b2372 --- /dev/null +++ b/BCnEncTests/testImages/GenerateTestKtx.ps1 @@ -0,0 +1,493 @@ +[CmdletBinding()] +param ( + [Parameter(Mandatory=$true)] + [string]$InputImage, + + [Parameter(Mandatory=$false)] + [ValidateSet('linear', 'srgb')] + [string]$ColorSpace = "linear", # default to linear + + [Parameter(Mandatory=$false)] + [string]$OutputDir = "./testktx", + + [Parameter(Mandatory=$false)] + [string]$PVRTexToolPath = "PVRTexToolCLI", + + [Parameter(Mandatory=$false)] + [switch]$Help +) + +# Show help if requested +if ($Help -or [string]::IsNullOrEmpty($InputImage)) { + Write-Host "GenerateTestKtx.ps1 - Generate test KTX1 files for BCnEncoder.NET" + Write-Host "Usage: .\GenerateTestKtxWithPVR.ps1 -InputImage -ColorSpace [-OutputDir ] [-PVRTexToolPath ] [-KtxToolPath ] [-Formats ...]" + Write-Host "" + Write-Host "Options:" + Write-Host " -InputImage Path to the input image file" + Write-Host " -ColorSpace Color space of the input image: 'linear' or 'srgb'" + Write-Host " -OutputDir Output directory for generated files (default: ./testktx)" + Write-Host " -PVRTexToolPath Path to PVRTexToolCLI.exe (default: uses PVRTexToolCLI from PATH)" + Write-Host " -Help Show this help message" + exit 0 +} + +# Input validation when not showing help +if ([string]::IsNullOrEmpty($InputImage)) { + Write-Error "InputImage parameter is required. Use -Help for usage information." + exit 1 +} + +if ([string]::IsNullOrEmpty($ColorSpace)) { + Write-Error "ColorSpace parameter is required. Use -Help for usage information." + exit 1 +} + +# Create output directory if it doesn't exist +if (-not (Test-Path $OutputDir)) { + New-Item -ItemType Directory -Path $OutputDir | Out-Null + Write-Verbose "Created output directory: $OutputDir" +} + +# Create reference directory for decoded PNGs +$referenceDir = Join-Path -Path $OutputDir -ChildPath "reference" +if (-not (Test-Path $referenceDir)) { + New-Item -ItemType Directory -Path $referenceDir | Out-Null + Write-Verbose "Created reference directory: $referenceDir" +} + +# Find tools +$PVRTexToolPath = "PVRTexToolCLI" +$KtxToolPath = "ktx" + +# Verify PVRTexToolCLI is available +try { + $PVRTexToolVersion = Invoke-Expression "& `"$PVRTexToolPath`" -version 2>&1" + Write-Verbose "Using PVRTexToolCLI: $PVRTexToolVersion" +} catch { + Write-Host "Error: PVRTexToolCLI is not available in the path." -ForegroundColor Red + Write-Host "Please install it from: https://developer.imaginationtech.com/pvrtextool/" -ForegroundColor Red + exit 1 +} + +# Verify the input image exists +if (!(Test-Path -Path $InputImage)) { + Write-Host "Error: Input image $InputImage not found." -ForegroundColor Red + exit 1 +} + +# Get input image base name for output file naming +$inputBaseName = [System.IO.Path]::GetFileNameWithoutExtension($InputImage) +Write-Verbose "Using input image: $InputImage (Base name: $inputBaseName)" +Write-Verbose "Color space: $ColorSpace" + +# Define format mappings - each format gets a name, and a PVRTexToolCLI format string +$formatMappings = @( + # BC1 (DXT1) - no alpha + @{ + Name = "bc1-linear" + Format = "BC1,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + ExtraOptions = "-q pvrtcbest" + }, + @{ + Name = "bc1-srgb" + Format = "BC1,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $false + ExtraOptions = "-q pvrtcbest" + }, + + # BC1 (DXT1) - with alpha (1-bit) + @{ + Name = "bc1-alpha-linear" + Format = "BC1,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q pvrtcbest" + }, + @{ + Name = "bc1-alpha-srgb" + Format = "BC1,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $true + ExtraOptions = "-q pvrtcbest" + }, + + # BC2 (DXT3) + @{ + Name = "bc2-linear" + Format = "BC2,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q pvrtcbest" + }, + @{ + Name = "bc2-srgb" + Format = "BC2,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $true + ExtraOptions = "-q pvrtcbest" + }, + + # BC3 (DXT5) + @{ + Name = "bc3-linear" + Format = "BC3,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q pvrtcbest" + }, + @{ + Name = "bc3-srgb" + Format = "BC3,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $true + ExtraOptions = "-q pvrtcbest" + }, + + # BC4 (unsigned) + @{ + Name = "bc4-linear" + Format = "BC4,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + ExtraOptions = "-q pvrtcbest" + }, + + # BC4 (signed) + @{ + Name = "bc4s-linear" + Format = "BC4,SBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + ExtraOptions = "-q pvrtcbest" + }, + + # BC5 (unsigned) + @{ + Name = "bc5-linear" + Format = "BC5,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + ExtraOptions = "-q pvrtcbest" + }, + + # BC5 (signed) + @{ + Name = "bc5s-linear" + Format = "BC5,SBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + ExtraOptions = "-q pvrtcbest" + }, + + # 16-bit color formats + @{ + Name = "etc1-linear" + Format = "ETC1,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + ExtraOptions = "-q ETCSLOW" + }, + @{ + Name = "etc1-srgb" + Format = "ETC1,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $false + ExtraOptions = "-q ETCSLOW" + }, + @{ + Name = "etc2-rgb-linear" + Format = "ETC2_RGB,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + ExtraOptions = "-q ETCSLOW" + }, + @{ + Name = "etc2-rgb-srgb" + Format = "ETC2_RGB,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $false + ExtraOptions = "-q ETCSLOW" + }, + @{ + Name = "etc2-rgba-linear" + Format = "ETC2_RGBA,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ETCSLOW" + }, + @{ + Name = "etc2-rgba-srgb" + Format = "ETC2_RGBA,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $true + ExtraOptions = "-q ETCSLOW" + }, + @{ + Name = "etc2-rgb-a1-linear" + Format = "ETC2_RGB_A1,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ETCSLOW" + }, + @{ + Name = "etc2-rgb-a1-srgb" + Format = "ETC2_RGB_A1,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $true + ExtraOptions = "-q ETCSLOW" + }, + @{ + Name = "eac-r11-linear" + Format = "EAC_R11,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + ExtraOptions = "-q ETCSLOW" + }, + @{ + Name = "eac-rg11-linear" + Format = "EAC_RG11,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + ExtraOptions = "-q ETCSLOW" + }, + + # ASTC formats + @{ + Name = "astc-4x4-linear" + Format = "ASTC_4X4,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-4x4-srgb" + Format = "ASTC_4X4,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-5x4-linear" + Format = "ASTC_5X4,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-5x5-linear" + Format = "ASTC_5X5,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-6x5-linear" + Format = "ASTC_6X5,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-6x6-linear" + Format = "ASTC_6X6,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-8x5-linear" + Format = "ASTC_8X5,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-8x6-linear" + Format = "ASTC_8X6,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-8x8-linear" + Format = "ASTC_8X8,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-10x5-linear" + Format = "ASTC_10X5,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-10x6-linear" + Format = "ASTC_10X6,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-10x8-linear" + Format = "ASTC_10X8,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-10x10-linear" + Format = "ASTC_10X10,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-12x10-linear" + Format = "ASTC_12X10,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-12x12-linear" + Format = "ASTC_12X12,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + + # Uncompressed formats + @{ + Name = "r8-linear" + Format = "R8,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + }, + @{ + Name = "r8g8-linear" + Format = "R8G8,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + }, + @{ + Name = "rgba32-linear" + Format = "R8G8B8A8,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + }, + @{ + Name = "rgba32-srgb" + Format = "R8G8B8A8,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $true + }, + @{ + Name = "bgra32-linear" + Format = "B8G8R8A8,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + } +) + +# Calculate total operations +$totalCount = $formatMappings.Count +$currentCount = 0 + +Write-Host "Generating encoded KTX files..." -ForegroundColor Cyan + +# Process each format + +foreach ($format in $formatMappings) { + $currentCount++ + $outputName = "$inputBaseName-$($format.Name)" + $outputFile = Join-Path -Path $OutputDir -ChildPath "$outputName.ktx" + + Write-Progress -Activity "Generating test images" -Status "[$currentCount of $totalCount] Creating $outputFile" -PercentComplete (($currentCount / $totalCount) * 100) + + # Determine color space flags for PVRTexToolCLI + $colorSpaceFlags = "" + + # PVRTexToolCLI assumes input is sRGB by default + # We need to explicitly tell it the input colorspace + if ($ColorSpace -eq "linear") { + # Specify that input is already linear + $colorSpaceFlags = "-ics lRGB" + } + else { # sRGB input + $colorSpaceFlags = "-ics sRGB" + } + + # Handle premultiplied alpha for formats that support it + # BGRA32, RGBA32, BC1 with alpha, BC2, BC3, and BC7 need premultiplied alpha + $alphaPremulFlag = "" + if ($format.SupportsAlpha -and ($format.Name -match "bc[1-3]" -or $format.Name -match "rgba" -or $format.Name -match "bgra")) { + $alphaPremulFlag = "-p" + } + + # Extra options specific to this format + $extraOptions = if ($format.ExtraOptions) { $format.ExtraOptions } else { "" } + + # Build the command + $cmd = "& `"$PVRTexToolPath`" -i `"$InputImage`" -o `"$outputFile`" -f $($format.Format) -m -dither -pot" + + # Add colorspace input flag + $cmd += " $colorSpaceFlags $alphaPremulFlag $extraOptions" + + Write-Verbose "Executing: $cmd" + + # Execute command and capture any errors + $output = Invoke-Expression "$cmd 2>&1" + if ($LASTEXITCODE -ne 0) { + Write-Host "Error executing command: $cmd" -ForegroundColor Red + Write-Host $output -ForegroundColor Red + } else { + Write-Verbose "Created $outputFile" + } +} + +Write-Progress -Activity "Generating test images" -Completed +Write-Host "All test images have been generated in $OutputDir" -ForegroundColor Green + +# Generate reference PNGs for visualization +Write-Host "Generating reference decoded PNGs..." -ForegroundColor Cyan +Write-Progress -Activity "Generating reference PNGs" -Status "Starting..." -PercentComplete 0 + +$totalKtx = @(Get-ChildItem -Path $OutputDir -Filter "*.ktx").Count +$currentKtx = 0 + +# Generate reference PNGs for all formats using PVRTexToolCLI's decompression +foreach ($ktxFile in Get-ChildItem -Path $OutputDir -Filter "*.ktx") { + $currentKtx++ + $ktxName = [System.IO.Path]::GetFileNameWithoutExtension($ktxFile) + $ktxPath = $ktxFile.FullName + $refPngPath = Join-Path -Path $referenceDir -ChildPath "$ktxName.png" + + Write-Progress -Activity "Generating reference PNGs" -Status "Processing $ktxName" -PercentComplete (($currentKtx / $totalKtx) * 100) + Write-Verbose "Processing $ktxName for reference PNG generation" + + # Use PVRTexToolCLI to decompress directly to PNG + $decompressCmd = "& `"$PVRTexToolPath`" -i `"$ktxPath`" -ics srgb -d `"$refPngPath`" -noout" + + Write-Verbose "Command: $decompressCmd" + try { + $output = Invoke-Expression "$decompressCmd 2>&1" + + if (Test-Path $refPngPath) { + Write-Verbose "Created reference PNG for $ktxName" + } else { + Write-Host "Unable to create reference PNG for $ktxName" -ForegroundColor Yellow + Write-Host "Error: $output" -ForegroundColor Yellow + } + } catch { + Write-Host "An error occurred while generating reference PNG for $ktxName" -ForegroundColor Red + Write-Host "$_" -ForegroundColor Red + } +} + +Write-Progress -Activity "Generating reference PNGs" -Completed +Write-Host "Reference decoded PNGs have been generated in $referenceDir" -ForegroundColor Green diff --git a/BCnEncTests/testImages/GenerateTestKtx2.ps1 b/BCnEncTests/testImages/GenerateTestKtx2.ps1 new file mode 100644 index 0000000..edd0e7f --- /dev/null +++ b/BCnEncTests/testImages/GenerateTestKtx2.ps1 @@ -0,0 +1,602 @@ +[CmdletBinding()] +param ( + [Parameter(Mandatory=$true)] + [string]$InputImage, + + [Parameter(Mandatory=$false)] + [ValidateSet('linear', 'srgb')] + [string]$ColorSpace = "linear", # default to linear + + [Parameter(Mandatory=$false)] + [string]$OutputDir = "./testktx2", + + [Parameter(Mandatory=$false)] + [string]$PVRTexToolPath = "PVRTexToolCLI", + + [Parameter(Mandatory=$false)] + [string]$KtxToolPath = "ktx", + + [Parameter(Mandatory=$false)] + [switch]$Help +) + +# Show help if requested +if ($Help -or [string]::IsNullOrEmpty($InputImage)) { + Write-Host "GenerateTestKtx2.ps1 - Generate test KTX2 files for BCnEncoder.NET" + Write-Host "Usage: .\GenerateTestKtxWithPVR.ps1 -InputImage -ColorSpace [-OutputDir ] [-PVRTexToolPath ] [-KtxToolPath ] [-Formats ...]" + Write-Host "" + Write-Host "Options:" + Write-Host " -InputImage Path to the input image file" + Write-Host " -ColorSpace Color space of the input image: 'linear' or 'srgb'" + Write-Host " -OutputDir Output directory for generated files (default: ./testktx)" + Write-Host " -PVRTexToolPath Path to PVRTexToolCLI.exe (default: uses PVRTexToolCLI from PATH)" + Write-Host " -KtxToolPath Path to ktx.exe (default: uses ktx from PATH)" + Write-Host " -Help Show this help message" + exit 0 +} + +# Input validation when not showing help +if ([string]::IsNullOrEmpty($InputImage)) { + Write-Error "InputImage parameter is required. Use -Help for usage information." + exit 1 +} + +if ([string]::IsNullOrEmpty($ColorSpace)) { + Write-Error "ColorSpace parameter is required. Use -Help for usage information." + exit 1 +} + +# Create output directory if it doesn't exist +if (-not (Test-Path $OutputDir)) { + New-Item -ItemType Directory -Path $OutputDir | Out-Null + Write-Verbose "Created output directory: $OutputDir" +} + +# Create reference directory for decoded PNGs +$referenceDir = Join-Path -Path $OutputDir -ChildPath "reference" +if (-not (Test-Path $referenceDir)) { + New-Item -ItemType Directory -Path $referenceDir | Out-Null + Write-Verbose "Created reference directory: $referenceDir" +} + +# Find tools +$PVRTexToolPath = "PVRTexToolCLI" +$KtxToolPath = "ktx" + +# Verify PVRTexToolCLI is available +try { + $PVRTexToolVersion = Invoke-Expression "& `"$PVRTexToolPath`" -version 2>&1" + Write-Verbose "Using PVRTexToolCLI: $PVRTexToolVersion" +} catch { + Write-Host "Error: PVRTexToolCLI is not available in the path." -ForegroundColor Red + Write-Host "Please install it from: https://developer.imaginationtech.com/pvrtextool/" -ForegroundColor Red + exit 1 +} + +# Verify KTX tool is available +try { + $KtxVersion = Invoke-Expression "& `"$KtxToolPath`" version 2>&1" + Write-Verbose "Using KTX: $KtxVersion" +} catch { + Write-Host "Error: KTX tool is not available in the path." -ForegroundColor Red + Write-Host "Please install it from: https://github.com/KhronosGroup/KTX-Software/releases" -ForegroundColor Red + exit 1 +} + +# Verify the input image exists +if (!(Test-Path -Path $InputImage)) { + Write-Host "Error: Input image $InputImage not found." -ForegroundColor Red + exit 1 +} + +# Get input image base name for output file naming +$inputBaseName = [System.IO.Path]::GetFileNameWithoutExtension($InputImage) +Write-Verbose "Using input image: $InputImage (Base name: $inputBaseName)" +Write-Verbose "Color space: $ColorSpace" + +# Define format mappings - each format gets a name, and a PVRTexToolCLI format string +$formatMappings = @( + # BC1 (DXT1) - no alpha + @{ + Name = "bc1-linear" + Format = "BC1,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + ExtraOptions = "-q pvrtcbest" + }, + @{ + Name = "bc1-srgb" + Format = "BC1,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $false + ExtraOptions = "-q pvrtcbest" + }, + + # BC1 (DXT1) - with alpha (1-bit) + @{ + Name = "bc1-alpha-linear" + Format = "BC1,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q pvrtcbest" + }, + @{ + Name = "bc1-alpha-srgb" + Format = "BC1,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $true + ExtraOptions = "-q pvrtcbest" + }, + + # BC2 (DXT3) + @{ + Name = "bc2-linear" + Format = "BC2,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q pvrtcbest" + }, + @{ + Name = "bc2-srgb" + Format = "BC2,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $true + ExtraOptions = "-q pvrtcbest" + }, + + # BC3 (DXT5) + @{ + Name = "bc3-linear" + Format = "BC3,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q pvrtcbest" + }, + @{ + Name = "bc3-srgb" + Format = "BC3,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $true + ExtraOptions = "-q pvrtcbest" + }, + + # BC4 (unsigned) + @{ + Name = "bc4-linear" + Format = "BC4,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + ExtraOptions = "-q pvrtcbest" + }, + + # BC4 (signed) + @{ + Name = "bc4s-linear" + Format = "BC4,SBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + ExtraOptions = "-q pvrtcbest" + }, + + # BC5 (unsigned) + @{ + Name = "bc5-linear" + Format = "BC5,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + ExtraOptions = "-q pvrtcbest" + }, + + # BC5 (signed) + @{ + Name = "bc5s-linear" + Format = "BC5,SBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + ExtraOptions = "-q pvrtcbest" + }, + + # ETC formats + @{ + Name = "etc2-rgb-linear" + Format = "ETC2_RGB,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + ExtraOptions = "-q ETCSLOW" + }, + @{ + Name = "etc2-rgb-srgb" + Format = "ETC2_RGB,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $false + ExtraOptions = "-q ETCSLOW" + }, + @{ + Name = "etc2-rgba-linear" + Format = "ETC2_RGBA,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ETCSLOW" + }, + @{ + Name = "etc2-rgba-srgb" + Format = "ETC2_RGBA,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $true + ExtraOptions = "-q ETCSLOW" + }, + @{ + Name = "etc2-rgb-a1-linear" + Format = "ETC2_RGB_A1,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ETCSLOW" + }, + @{ + Name = "etc2-rgb-a1-srgb" + Format = "ETC2_RGB_A1,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $true + ExtraOptions = "-q ETCSLOW" + }, + @{ + Name = "eac-r11-linear" + Format = "EAC_R11,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + ExtraOptions = "-q ETCSLOW" + }, + @{ + Name = "eac-rg11-linear" + Format = "EAC_RG11,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + ExtraOptions = "-q ETCSLOW" + }, + + # ASTC formats + @{ + Name = "astc-4x4-linear" + Format = "ASTC_4X4,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-4x4-srgb" + Format = "ASTC_4X4,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-5x4-linear" + Format = "ASTC_5X4,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-5x5-linear" + Format = "ASTC_5X5,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-6x5-linear" + Format = "ASTC_6X5,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-6x6-linear" + Format = "ASTC_6X6,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-8x5-linear" + Format = "ASTC_8X5,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-8x6-linear" + Format = "ASTC_8X6,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-8x8-linear" + Format = "ASTC_8X8,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-10x5-linear" + Format = "ASTC_10X5,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-10x6-linear" + Format = "ASTC_10X6,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-10x8-linear" + Format = "ASTC_10X8,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-10x10-linear" + Format = "ASTC_10X10,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-12x10-linear" + Format = "ASTC_12X10,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + @{ + Name = "astc-12x12-linear" + Format = "ASTC_12X12,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q ASTCEXHAUSTIVE" + }, + + # Basis Universal formats + @{ + Name = "basisu-etc1s-linear" + Format = "BASISU_ETC1S,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q BASISUBEST" + }, + @{ + Name = "basisu-etc1s-srgb" + Format = "BASISU_ETC1S,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $true + ExtraOptions = "-q BASISUBEST" + }, + @{ + Name = "basisu-uastc-linear" + Format = "BASISU_UASTC,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q BASISUBEST" + }, + @{ + Name = "basisu-uastc-srgb" + Format = "BASISU_UASTC,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $true + ExtraOptions = "-q BASISUBEST" + }, + + # BC7 format (needs to be transcoded from UASTC) + @{ + Name = "bc7-linear" + Format = "BASISU_UASTC,UBN,lRGB" # Initial format for transcoding + ColorSpace = "linear" + SupportsAlpha = $true + ExtraOptions = "-q BASISUBEST" + RequiresTranscode = $true + TranscodeTarget = "bc7" + }, + @{ + Name = "bc7-srgb" + Format = "BASISU_UASTC,UBN,sRGB" # Initial format for transcoding + ColorSpace = "srgb" + SupportsAlpha = $true + ExtraOptions = "-q BASISUBEST" + RequiresTranscode = $true + TranscodeTarget = "bc7" + }, + + # Uncompressed formats + @{ + Name = "r5g6b5-linear" + Format = "R5G6B5,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + }, + @{ + Name = "r5g5b5a1-linear" + Format = "R5G5B5A1,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + }, + @{ + Name = "r8-linear" + Format = "R8,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + }, + @{ + Name = "r8g8-linear" + Format = "R8G8,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $false + }, + @{ + Name = "rgba32-linear" + Format = "R8G8B8A8,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + }, + @{ + Name = "rgba32-srgb" + Format = "R8G8B8A8,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $true + }, + @{ + Name = "bgra32-linear" + Format = "B8G8R8A8,UBN,lRGB" + ColorSpace = "linear" + SupportsAlpha = $true + }, + @{ + Name = "bgra32-srgb" + Format = "B8G8R8A8,UBN,sRGB" + ColorSpace = "srgb" + SupportsAlpha = $true + } +) + +# Calculate total operations +$totalCount = $formatMappings.Count +$currentCount = 0 + +Write-Host "Generating encoded KTX2 files..." -ForegroundColor Cyan + +# Process each format + +foreach ($format in $formatMappings) { + $currentCount++ + $outputName = "$inputBaseName-$($format.Name)" + $outputFile = Join-Path -Path $OutputDir -ChildPath "$outputName.ktx2" + + Write-Progress -Activity "Generating test images" -Status "[$currentCount of $totalCount] Creating $outputFile" -PercentComplete (($currentCount / $totalCount) * 100) + + # Determine color space flags for PVRTexToolCLI + $colorSpaceFlags = "" + + # PVRTexToolCLI seems to assume input is sRGB by default + # We need to explicitly tell it the input colorspace + if ($ColorSpace -eq "linear") { + # Specify that input is already linear + $colorSpaceFlags = "-ics lRGB" + } + else { # sRGB input + $colorSpaceFlags = "-ics sRGB" + } + + # Handle premultiplied alpha for formats that support it + # From memory: BGRA32, RGBA32, BC1 with alpha, BC2, BC3, and BC7 need premultiplied alpha + $alphaPremulFlag = "" + if ($format.SupportsAlpha -and ($format.Name -match "bc[1-3]" -or $format.Name -match "rgba" -or $format.Name -match "bgra")) { + $alphaPremulFlag = "-p" + } + + # Extra options specific to this format + $extraOptions = if ($format.ExtraOptions) { $format.ExtraOptions } else { "" } + + # Build the command + $cmd = "& `"$PVRTexToolPath`" -i `"$InputImage`" -o `"$outputFile`" -f $($format.Format) -m -dither -pot" + + # Add colorspace input flag + $cmd += " $colorSpaceFlags $alphaPremulFlag $extraOptions" + + Write-Verbose "Executing: $cmd" + + # Execute command and capture any errors + $output = Invoke-Expression "$cmd 2>&1" + if ($LASTEXITCODE -ne 0) { + Write-Host "Error executing command: $cmd" -ForegroundColor Red + Write-Host $output -ForegroundColor Red + } else { + Write-Verbose "Created $outputFile" + } + + # Transcode to target format if required + if ($format.RequiresTranscode) { + $transcodeOutputFile = $outputFile + $tempUastcFile = $outputFile + + # Generate a unique name for the pre-transcoded file + $preTranscodedFile = "{0}_pretranscoded{1}" -f [System.IO.Path]::GetFileNameWithoutExtension($outputFile), [System.IO.Path]::GetExtension($outputFile) + $preTranscodedPath = Join-Path -Path $OutputDir -ChildPath $preTranscodedFile + + # Make a copy of the pre-transcoded file (UASTC) for later reference PNG generation + Copy-Item -Path $tempUastcFile -Destination $preTranscodedPath -Force + + $transcodeCmd = "& `"$KtxToolPath`" transcode --target $($format.TranscodeTarget) `"$tempUastcFile`" `"$transcodeOutputFile`"" + Write-Verbose "Transcoding to $($format.TranscodeTarget): $transcodeCmd" + $transcodeOutput = Invoke-Expression "$transcodeCmd 2>&1" + if ($LASTEXITCODE -ne 0) { + Write-Host "Error transcoding: $transcodeCmd" -ForegroundColor Red + Write-Host $transcodeOutput -ForegroundColor Red + } else { + Write-Verbose "Transcoded $outputFile to $($format.TranscodeTarget)" + } + } +} + +Write-Progress -Activity "Generating test images" -Completed +Write-Host "All test images have been generated in $OutputDir" -ForegroundColor Green + +# Generate reference PNGs for visualization +Write-Host "Generating reference decoded PNGs..." -ForegroundColor Cyan +Write-Progress -Activity "Generating reference PNGs" -Status "Starting..." -PercentComplete 0 + +$totalKtx = @(Get-ChildItem -Path $OutputDir -Filter "*.ktx2" | Where-Object { $_.Name -notlike "*_pretranscoded.ktx2" }).Count +$currentKtx = 0 + +# Generate reference PNGs for all formats using PVRTexToolCLI's decompression +foreach ($ktxFile in Get-ChildItem -Path $OutputDir -Filter "*.ktx2" | Where-Object { $_.Name -notlike "*_pretranscoded.ktx2" }) { + $currentKtx++ + $ktxName = [System.IO.Path]::GetFileNameWithoutExtension($ktxFile) + $ktxPath = $ktxFile.FullName + $refPngPath = Join-Path -Path $referenceDir -ChildPath "$ktxName.png" + + Write-Progress -Activity "Generating reference PNGs" -Status "Processing $ktxName" -PercentComplete (($currentKtx / $totalKtx) * 100) + Write-Verbose "Processing $ktxName for reference PNG generation" + + # Check if this is a format that we transcoded and needs special handling + $format = $formatMappings | Where-Object { $_.Name -eq $ktxName.Replace("$inputBaseName-", "") } | Select-Object -First 1 + $usePreTranscodedFile = $false + $preTranscodedPath = $null + + if ($format -and $format.RequiresTranscode -eq $true -and (Test-Path (Join-Path -Path $OutputDir -ChildPath "$ktxName`_pretranscoded.ktx2"))) { + $usePreTranscodedFile = $true + $preTranscodedPath = Join-Path -Path $OutputDir -ChildPath "$ktxName`_pretranscoded.ktx2" + Write-Verbose "Using pre-transcoded file for reference PNG generation: $preTranscodedPath" + } + + # Use PVRTexToolCLI to decompress directly to PNG + $sourceFile = if ($usePreTranscodedFile) { $preTranscodedPath } else { $ktxPath } + $decompressCmd = "& `"$PVRTexToolPath`" -i `"$sourceFile`" -d `"$refPngPath`" -noout" + + Write-Verbose "Command: $decompressCmd" + try { + $output = Invoke-Expression "$decompressCmd 2>&1" + + if (Test-Path $refPngPath) { + Write-Verbose "Created reference PNG for $ktxName" + + # Delete the pre-transcoded file after reference PNG generation if it exists + if ($usePreTranscodedFile -and (Test-Path $preTranscodedPath)) { + Remove-Item -Path $preTranscodedPath -Force + Write-Verbose "Removed temporary pre-transcoded file: $preTranscodedPath" + } + } else { + Write-Host "Unable to create reference PNG for $ktxName" -ForegroundColor Yellow + Write-Host "Error: $output" -ForegroundColor Yellow + } + } catch { + Write-Host "An error occurred while generating reference PNG for $ktxName" -ForegroundColor Red + Write-Host "$_" -ForegroundColor Red + } +} + +Write-Progress -Activity "Generating reference PNGs" -Completed +Write-Host "Reference decoded PNGs have been generated in $referenceDir" -ForegroundColor Green diff --git a/BCnEncTests/testImages/NOTICE b/BCnEncTests/testImages/NOTICE index 5539ab7..39ea341 100644 --- a/BCnEncTests/testImages/NOTICE +++ b/BCnEncTests/testImages/NOTICE @@ -1,16 +1,28 @@ -Transparent dice image By POV-Ray source code - Own work: Rendered in POV-Ray by user:ed_g2s., CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=221157 -Other test images are under the public domain. +alpha_1 & alpha_4 CC0, from https://texture.ninja/ +alpha_2 By POV-Ray source code - Own work: Rendered in POV-Ray by user:ed_g2s., CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=221157 +alpha_3 CC0, Made with Paint.NET +alpha_5 CC0, by Refuzzle https://opengameart.org/content/winter-birds +diffuse_1 by Karina Vorozheeva on Unsplash +diffuse_2 by Borna Bevanda on Unsplash +diffuse_3 CC0, a mix of purposefully difficult patterns to encode correctly +diffuse_4 & height_1 & normal_1 CC0, from https://publicdomaintextures.com/ +diffuse_5 & diffuse_6 CC0, from https://texture.ninja/ +rg_1 & blocks CC0, from https://github.com/sevmeyer/ktxtest +hdr_1 CC0, from https://polyhaven.com/ +cubemap_1, CC0, Made with Paint.NET +cubemap_2, CC-BY 3.0, Work of Emil Persson, aka Humus. http://www.humus.name +ktx test files from https://github.com/sevmeyer/ktxtest CC0 -Textures are from https://publicdomaintextures.com/ -Cat pictures are from https://unsplash.com -https://unsplash.com/license - -Cat photo blurry by Karina Vorozheeva on Unsplash -Cat photo gradient by Borna Bevanda on Unsplash +rgb_hard.png - Collection of images from different sources +- KTXTest pattern https://github.com/sevmeyer/ktxtest CC0 +- wcDogg test images https://github.com/wcDogg/test-files CC0 +- 4096 Scifi Hex Tiles PBR Texture Normal by txturs https://opengameart.org/content/4096-scifi-hex-tiles-pbr-texture CC0 +- Medieval Wooden Roof Normal by Kutejnikov https://opengameart.org/content/medieval-wooden-roof CC0 +- Synthetic Texture Pack Synth-Fabric_01 by Screaming Brain Studios https://opengameart.org/content/synthetic-texture-pack CC0 -Pixel art birds by Refuzzle from opengameart.org +Unsplash license: +https://unsplash.com/license -HDR Kiara from https://hdrihaven.com -test_hdr_probe CC0 from http://graphicslearning.com/downloads/hdr-light-probe-02-cc0/ \ No newline at end of file +Unsplash grants you an irrevocable, nonexclusive, worldwide copyright license to download, copy, modify, distribute, perform, and use photos from Unsplash for free, including for commercial purposes, without permission from or attributing the photographer or Unsplash. This license does not include the right to compile photos from Unsplash to replicate a similar or competing service. diff --git a/BCnEncTests/testImages/test_hdr_kiara_dds_float16_data.bin b/BCnEncTests/testImages/bc6h_alltypes.bin similarity index 100% rename from BCnEncTests/testImages/test_hdr_kiara_dds_float16_data.bin rename to BCnEncTests/testImages/bc6h_alltypes.bin diff --git a/BCnEncTests/testImages/test_hdr_kiara_bc6h.dds b/BCnEncTests/testImages/bc6h_alltypes.dds similarity index 100% rename from BCnEncTests/testImages/test_hdr_kiara_bc6h.dds rename to BCnEncTests/testImages/bc6h_alltypes.dds diff --git a/BCnEncTests/testImages/cubemap/cube_2_negx.jpg b/BCnEncTests/testImages/cubemap/cube_2_negx.jpg new file mode 100644 index 0000000..48b37dc Binary files /dev/null and b/BCnEncTests/testImages/cubemap/cube_2_negx.jpg differ diff --git a/BCnEncTests/testImages/cubemap/cube_2_negy.jpg b/BCnEncTests/testImages/cubemap/cube_2_negy.jpg new file mode 100644 index 0000000..0883450 Binary files /dev/null and b/BCnEncTests/testImages/cubemap/cube_2_negy.jpg differ diff --git a/BCnEncTests/testImages/cubemap/cube_2_negz.jpg b/BCnEncTests/testImages/cubemap/cube_2_negz.jpg new file mode 100644 index 0000000..d5b53c9 Binary files /dev/null and b/BCnEncTests/testImages/cubemap/cube_2_negz.jpg differ diff --git a/BCnEncTests/testImages/cubemap/cube_2_posx.jpg b/BCnEncTests/testImages/cubemap/cube_2_posx.jpg new file mode 100644 index 0000000..f457b83 Binary files /dev/null and b/BCnEncTests/testImages/cubemap/cube_2_posx.jpg differ diff --git a/BCnEncTests/testImages/cubemap/cube_2_posy.jpg b/BCnEncTests/testImages/cubemap/cube_2_posy.jpg new file mode 100644 index 0000000..ebee2af Binary files /dev/null and b/BCnEncTests/testImages/cubemap/cube_2_posy.jpg differ diff --git a/BCnEncTests/testImages/cubemap/cube_2_posz.jpg b/BCnEncTests/testImages/cubemap/cube_2_posz.jpg new file mode 100644 index 0000000..3de2360 Binary files /dev/null and b/BCnEncTests/testImages/cubemap/cube_2_posz.jpg differ diff --git a/BCnEncTests/testImages/decoding_dds_bc5.dds b/BCnEncTests/testImages/decoding_dds_bc5.dds deleted file mode 100644 index 813742d..0000000 Binary files a/BCnEncTests/testImages/decoding_dds_bc5.dds and /dev/null differ diff --git a/BCnEncTests/testImages/decoding_dds_bc5_reference.png b/BCnEncTests/testImages/decoding_dds_bc5_reference.png deleted file mode 100644 index cb981d9..0000000 Binary files a/BCnEncTests/testImages/decoding_dds_bc5_reference.png and /dev/null differ diff --git a/BCnEncTests/testImages/encoded/alpha_1_bc1a.dds b/BCnEncTests/testImages/encoded/alpha_1_bc1a.dds new file mode 100644 index 0000000..e3c2916 Binary files /dev/null and b/BCnEncTests/testImages/encoded/alpha_1_bc1a.dds differ diff --git a/BCnEncTests/testImages/encoded/alpha_1_bc2.dds b/BCnEncTests/testImages/encoded/alpha_1_bc2.dds new file mode 100644 index 0000000..7dc41de Binary files /dev/null and b/BCnEncTests/testImages/encoded/alpha_1_bc2.dds differ diff --git a/BCnEncTests/testImages/encoded/alpha_1_bc3.dds b/BCnEncTests/testImages/encoded/alpha_1_bc3.dds new file mode 100644 index 0000000..0a5b888 Binary files /dev/null and b/BCnEncTests/testImages/encoded/alpha_1_bc3.dds differ diff --git a/BCnEncTests/testImages/encoded/alpha_1_bc7.dds b/BCnEncTests/testImages/encoded/alpha_1_bc7.dds new file mode 100644 index 0000000..2664eca Binary files /dev/null and b/BCnEncTests/testImages/encoded/alpha_1_bc7.dds differ diff --git a/BCnEncTests/testImages/encoded/alpha_1_bgra.dds b/BCnEncTests/testImages/encoded/alpha_1_bgra.dds new file mode 100644 index 0000000..0c8b211 Binary files /dev/null and b/BCnEncTests/testImages/encoded/alpha_1_bgra.dds differ diff --git a/BCnEncTests/testImages/encoded/alpha_2_bc1a.dds b/BCnEncTests/testImages/encoded/alpha_2_bc1a.dds new file mode 100644 index 0000000..ba56885 Binary files /dev/null and b/BCnEncTests/testImages/encoded/alpha_2_bc1a.dds differ diff --git a/BCnEncTests/testImages/encoded/alpha_2_bc2.dds b/BCnEncTests/testImages/encoded/alpha_2_bc2.dds new file mode 100644 index 0000000..171aad1 Binary files /dev/null and b/BCnEncTests/testImages/encoded/alpha_2_bc2.dds differ diff --git a/BCnEncTests/testImages/encoded/alpha_2_bc3.dds b/BCnEncTests/testImages/encoded/alpha_2_bc3.dds new file mode 100644 index 0000000..b7949d2 Binary files /dev/null and b/BCnEncTests/testImages/encoded/alpha_2_bc3.dds differ diff --git a/BCnEncTests/testImages/encoded/alpha_2_bc7.dds b/BCnEncTests/testImages/encoded/alpha_2_bc7.dds new file mode 100644 index 0000000..89c722d Binary files /dev/null and b/BCnEncTests/testImages/encoded/alpha_2_bc7.dds differ diff --git a/BCnEncTests/testImages/encoded/alpha_2_rgba.dds b/BCnEncTests/testImages/encoded/alpha_2_rgba.dds new file mode 100644 index 0000000..4c1bdf6 Binary files /dev/null and b/BCnEncTests/testImages/encoded/alpha_2_rgba.dds differ diff --git a/BCnEncTests/testImages/encoded/array_2_elements_cubemap_6_faces.ktx b/BCnEncTests/testImages/encoded/array_2_elements_cubemap_6_faces.ktx new file mode 100644 index 0000000..e218347 Binary files /dev/null and b/BCnEncTests/testImages/encoded/array_2_elements_cubemap_6_faces.ktx differ diff --git a/BCnEncTests/testImages/encoded/astc_12x12_unorm.ktx b/BCnEncTests/testImages/encoded/astc_12x12_unorm.ktx new file mode 100644 index 0000000..2e726a9 Binary files /dev/null and b/BCnEncTests/testImages/encoded/astc_12x12_unorm.ktx differ diff --git a/BCnEncTests/testImages/encoded/astc_4x4_srgb.ktx b/BCnEncTests/testImages/encoded/astc_4x4_srgb.ktx new file mode 100644 index 0000000..3bfdf5d Binary files /dev/null and b/BCnEncTests/testImages/encoded/astc_4x4_srgb.ktx differ diff --git a/BCnEncTests/testImages/encoded/astc_8x5_unorm.ktx b/BCnEncTests/testImages/encoded/astc_8x5_unorm.ktx new file mode 100644 index 0000000..6e0b3df Binary files /dev/null and b/BCnEncTests/testImages/encoded/astc_8x5_unorm.ktx differ diff --git a/BCnEncTests/testImages/encoded/bc1_unorm.ktx b/BCnEncTests/testImages/encoded/bc1_unorm.ktx new file mode 100644 index 0000000..bd4294a Binary files /dev/null and b/BCnEncTests/testImages/encoded/bc1_unorm.ktx differ diff --git a/BCnEncTests/testImages/encoded/bc1a_unorm.ktx b/BCnEncTests/testImages/encoded/bc1a_unorm.ktx new file mode 100644 index 0000000..c884773 Binary files /dev/null and b/BCnEncTests/testImages/encoded/bc1a_unorm.ktx differ diff --git a/BCnEncTests/testImages/encoded/bc2_unorm.ktx b/BCnEncTests/testImages/encoded/bc2_unorm.ktx new file mode 100644 index 0000000..f1b97b5 Binary files /dev/null and b/BCnEncTests/testImages/encoded/bc2_unorm.ktx differ diff --git a/BCnEncTests/testImages/encoded/bc3_unorm.ktx b/BCnEncTests/testImages/encoded/bc3_unorm.ktx new file mode 100644 index 0000000..2e209ff Binary files /dev/null and b/BCnEncTests/testImages/encoded/bc3_unorm.ktx differ diff --git a/BCnEncTests/testImages/test_decompress_bc4_unorm.ktx b/BCnEncTests/testImages/encoded/bc4_unorm.ktx similarity index 100% rename from BCnEncTests/testImages/test_decompress_bc4_unorm.ktx rename to BCnEncTests/testImages/encoded/bc4_unorm.ktx diff --git a/BCnEncTests/testImages/test_decompress_bc5_unorm.ktx b/BCnEncTests/testImages/encoded/bc5_unorm.ktx similarity index 100% rename from BCnEncTests/testImages/test_decompress_bc5_unorm.ktx rename to BCnEncTests/testImages/encoded/bc5_unorm.ktx diff --git a/BCnEncTests/testImages/test_bc6h_ufloat.ktx b/BCnEncTests/testImages/encoded/bc6h_ufloat.ktx similarity index 100% rename from BCnEncTests/testImages/test_bc6h_ufloat.ktx rename to BCnEncTests/testImages/encoded/bc6h_ufloat.ktx diff --git a/BCnEncTests/testImages/encoded/bc7_srgb.DDS b/BCnEncTests/testImages/encoded/bc7_srgb.DDS new file mode 100644 index 0000000..3109738 Binary files /dev/null and b/BCnEncTests/testImages/encoded/bc7_srgb.DDS differ diff --git a/BCnEncTests/testImages/test_decompress_bc7_unorm.ktx b/BCnEncTests/testImages/encoded/bc7_unorm.ktx similarity index 100% rename from BCnEncTests/testImages/test_decompress_bc7_unorm.ktx rename to BCnEncTests/testImages/encoded/bc7_unorm.ktx diff --git a/BCnEncTests/testImages/test_decompress_bc7_types.ktx b/BCnEncTests/testImages/encoded/bc7_unorm_alltypes.ktx similarity index 100% rename from BCnEncTests/testImages/test_decompress_bc7_types.ktx rename to BCnEncTests/testImages/encoded/bc7_unorm_alltypes.ktx diff --git a/BCnEncTests/testImages/encoded/eac_r_unorm.ktx b/BCnEncTests/testImages/encoded/eac_r_unorm.ktx new file mode 100644 index 0000000..4172d30 Binary files /dev/null and b/BCnEncTests/testImages/encoded/eac_r_unorm.ktx differ diff --git a/BCnEncTests/testImages/encoded/eac_rg_unorm.ktx b/BCnEncTests/testImages/encoded/eac_rg_unorm.ktx new file mode 100644 index 0000000..2b83676 Binary files /dev/null and b/BCnEncTests/testImages/encoded/eac_rg_unorm.ktx differ diff --git a/BCnEncTests/testImages/encoded/etc1_unorm.ktx b/BCnEncTests/testImages/encoded/etc1_unorm.ktx new file mode 100644 index 0000000..94c3416 Binary files /dev/null and b/BCnEncTests/testImages/encoded/etc1_unorm.ktx differ diff --git a/BCnEncTests/testImages/encoded/etc2_r8g8b8a1_unorm.ktx b/BCnEncTests/testImages/encoded/etc2_r8g8b8a1_unorm.ktx new file mode 100644 index 0000000..f523755 Binary files /dev/null and b/BCnEncTests/testImages/encoded/etc2_r8g8b8a1_unorm.ktx differ diff --git a/BCnEncTests/testImages/encoded/etc2_r8g8b8a8_unorm.ktx b/BCnEncTests/testImages/encoded/etc2_r8g8b8a8_unorm.ktx new file mode 100644 index 0000000..1bebda2 Binary files /dev/null and b/BCnEncTests/testImages/encoded/etc2_r8g8b8a8_unorm.ktx differ diff --git a/BCnEncTests/testImages/test_hdr_kiara.hdr b/BCnEncTests/testImages/encoded/hdr_1_rgbe.hdr similarity index 99% rename from BCnEncTests/testImages/test_hdr_kiara.hdr rename to BCnEncTests/testImages/encoded/hdr_1_rgbe.hdr index 82ced05..086bfcd 100644 Binary files a/BCnEncTests/testImages/test_hdr_kiara.hdr and b/BCnEncTests/testImages/encoded/hdr_1_rgbe.hdr differ diff --git a/BCnEncTests/testImages/encoded/hdr_1_xyze.hdr b/BCnEncTests/testImages/encoded/hdr_1_xyze.hdr new file mode 100644 index 0000000..b9df3b3 --- /dev/null +++ b/BCnEncTests/testImages/encoded/hdr_1_xyze.hdr @@ -0,0 +1,8 @@ +#?RADIANCE +# Made with Adobe Photoshop +GAMMA=1 +EXPOSURE=5.586592e-03 +FORMAT=32-bit_rle_xyze + +-Y 512 +X 1024 +DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DB~DB~DB~DB~DB~DB~DB~DB~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~DC~EC~DC~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~DC~DC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~FD~FD~FD~FD~FD~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DB~DB~DB~DB~DC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~CB~CB~CB~CB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CA~CA~CA~CA~CA~CA~CA~CA~CB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CA~CA~CA~DB~DB~DB~DB~DB~DB~DB~DB~DB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CB~CB~CB~CB~CA~CA~CA~CA~CA~CA~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~CB~DB~DB~DB~DB~DB~DB~CB~CB~CB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~CB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~CB~DB~DB~DB~DB~CB~CB~CB~CB~CB~CA~CA~CA~CA~CB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~CA~CA~CB~DB~CA~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CA~CA~CA~CA~CA~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~CA~CA~CA~CA~CA~CA~DB~DB~DB~DB~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~EC~DB~EC~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DC~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~DC~EC~EC~EC~DC~DC~DC~DC~DC~DB~DB~DB~DB~DC~DC~DC~EC~EC~EC~EC~DC~DC~DC~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~DC~DC~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~DC~DB~DC~DC~DC~EC~DC~DC~DC~DC~DC~DB~DB~DB~DC~DC~DC~EC~EC~EC~FD~FD~FD~EC~FD~FD~FD~FD~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~FD~FD~FD~FD~FD~FD~EC~EC~EC~FD~FD~EC~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~EC~EC~EC~EC~EC~EC~FD~FD~FD~FD~FD~FD~FD~EC~EC~EC~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~DC~DC~EC~EC~EC~EC~FD~FD~FD~FD~FD~ED~ED~EC~EC~EC~EC~FD~FD~FD~FD~FD~EC~EC~DC~DC~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~DC~DB~DB~DC~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~DB~DB~DB~DB~DB~DB~DC~DC~EC~DC~DC~DC~DC~DB~DB~DB~CB~CB~DB~DB~DB~DC~EC~EC~EC~EC~EC~DC~DC~DC~DB~DB~DC~DC~EC~EC~FD~FD~ED~ED~EC~EC~EC~EC~DC~DC~DC~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DB~DB~DB~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DB~DB~DB~DB~DB~CB~CB~CB~CB~DB~CB~CB~CB~CB~CB~CB~CB~CB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~CB~CB~CB~CB~CB~CB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~CB~CB~CB~CB~DB~DB~DB~DB~DB~DB~DC~DB~DB~DB~DB~DB~DB~DB~CB~CB~CA~CA~CA~CA~CB~CB~CB~CB~DB~DB~DB~DB~DB~DB~DB~CA~CA~CA~CA~CA~CA~CA~CA~CB~CA~CA~BA~CA~CB~CB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CB~CA~CA~CA~CA~CB~DB~DB~DB~DB~DB~DB~CB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CB~DB~DB~DB~CB~CA~CA~BA~BA~BA~BA~CA~CB~CB~CB~CA~CA~CA~CA~CA~BA~BA~BA~BA~BA~BA~CA~CB~CB~CB~CB~CB~CB~CB~CA~CA~CA~BA~BA~CA~CA~CA~CA~BA~CA~CA~CB~CB~CB~CB~CB~CA~BA~BA~BA~BA~BA~CA~CA~CA~CB~DB~CB~CB~CB~DB~DB~DB~DB~DB~DB~CB~CA~CB~DB~DB~DB~CA~CA~CB~DB~DB~DB~CB~CA~BA~BA~CA~CB~DB~DB~DB~DB~DB~DB~CB~CB~CA~CA~CB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~DB~DB~DB~DB~DB~DB~DB~DB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CB~CA~CA~CA~CA~CA~DB~DB~DB~DB~DB~DB~DB~CA~CA~CA~DB~DB~DB~DB~DB~DB~CA~CA~CA~CA~CA~CA~CA~CB~CB~DB~DB~DB~DB~DB~CB~CB~CB~CB~CB~CB~CB~CB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CA~CA~CA~CB~CB~CB~CB~CB~DB~DB~DB~DB~DB~CB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CB~CB~CB~CA~CA~CA~CA~CA~CB~DB~DB~DB~DB~CB~CB~CB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CA~CB~CB~CB~CB~CB~CB~DB~DB~DC~DB~DB~CB~CB~CB~CB~CB~DB~DB~DB~CB~CB~CB~CB~CB~CB~CB~DB~DB~DB~DB~DB~CB~CB~DB~DB~DB~CB~CB~CB~CB~DC~DC~DC~DB~DB~DB~DC~DC~DB~DC~DB~DB~DB~DB~DB~CB~CB~CB~DB~DC~EC~EC~EC~DC~DB~CB~CB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~DB~DB~CB~CB~CB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~CB~CB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~DB~DB~DB~EC~EC~EC~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~FD~FD~EC~EC~EC~EC~EC~DC~EC~EC~EC~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~FD~FD~FD~FD~FD~FD~FD~EC~EC~EC~EC~EC~EC~EC~EC~DC~DC~EC~EC~EC~EC~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~ED~FD~FD~FD~ED~EC~EC~EC~EC~EC~EC~FD~FD~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~ED~ED~EC~EC~EC~EC~EC~EC~EC~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~EC~EC~EC~FD~FD~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~DB~DB~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~EC~EC~EC~EC~DB~EC~EC~EC~EC~EC~EC~EC~EC~DB~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~DC~DC~DC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~CB~CB~DB~DC~DC~EC~EC~DC~DC~DC~DB~CB~CB~CB~DC~DC~DC~EC~EC~EC~EC~EC~DC~DC~DC~DC~DC~DB~DB~DB~DB~CB~CB~CB~CB~DB~DB~DB~DC~DC~DC~DC~DB~DB~DB~DB~CB~CB~CB~CB~CB~CB~DB~DB~DC~DC~DB~DB~CB~CB~CB~CB~CB~CB~DB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DB~DB~DB~DB~CB~CB~CB~DC~DC~DC~DB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DC~DC~DC~CB~CB~DB~CB~CB~DB~DC~DC~DC~DC~DC~DC~DC~DC~DC~DB~DB~DB~DB~DC~DB~DB~DB~DC~DC~DC~DC~DC~DB~CB~CB~CA~CA~CB~CB~CB~CB~CB~CB~CB~CB~DC~DB~DB~DB~DC~DB~CB~CB~DB~DB~DB~DB~DC~DB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DB~DB~DB~DB~CB~DB~DB~DB~DB~DB~DB~DB~CB~CB~CB~CB~CB~CB~CB~CB~CB~BA~BA~CA~CA~CB~CB~CB~CB~CB~CB~CB~CB~CB~DB~DB~DB~DB~CB~CB~CB~CA~CA~CA~CA~CA~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DB~DB~CB~CA~CA~CB~CB~CA~CA~CA~BA~CA~CA~CA~CA~BA~BA~BA~BA~CA~CA~CA~CA~CB~CB~CA~CA~BA~BA~BA~CB~CB~CB~CA~CB~BA~BA~BA~CA~CB~CB~CB~CB~BA~BA~BA~BA~CA~CB~CB~CB~CB~CB~CB~CB~CB~CA~BA~BA~CA~CB~CB~CB~CB~CB~CB~CA~BA~CA~CA~CA~CA~CA~BA~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CA~BA~BA~CB~CB~CA~BA~BA~BA~BA~BA~BA~BA~BA~CA~CA~CA~CA~CA~BA~CA~CA~BA~BA~BA~BA~CA~CA~CA~CA~CA~CA~CA~BA~BA~BA~BA~BA~BA~CA~CA~CB~CA~BA~BA~CA~CA~CB~CB~CA~CA~CA~CA~CA~CA~BA~CA~CA~CB~CB~CA~CA~CA~CB~CB~DB~CB~CA~BA~BA~BA~BA~BA~CA~CA~CB~CB~CB~CB~CA~CA~CA~BA~BA~BA~BA~BA~CB~CB~CB~CB~CB~CB~CA~BA~BA~BA~BA~BA~BA~BA~CA~CA~CA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~CA~CA~CA~CA~CA~CA~CA~BA~BA~BA~BA~CA~CA~CA~CA~CA~CA~CB~CB~BA~BA~BA~BA~BA~BA~BA~CA~CA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~CA~CB~CB~CA~CB~CB~CB~CB~CB~BA~BA~BA~CA~CA~BA~BA~BA~BA~CB~CB~CB~DB~DB~CB~CA~BA~BA~CA~CB~DB~DB~DB~CB~CB~DB~DB~DB~CB~CB~CB~CB~CB~CB~CB~DB~DB~CB~CB~DB~DB~CB~CB~CB~CA~CA~CB~CB~DB~DB~CB~CA~CA~CB~CB~DB~CB~CB~CA~CA~CB~CA~CB~CB~CB~DB~DB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DB~DC~DC~DC~CB~CB~CB~CB~CB~CB~DB~DB~CB~CB~DB~DC~DC~DC~DB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DB~DB~DB~CB~CB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CA~CB~CB~DB~DB~DB~DC~DC~DC~DC~DB~DB~DB~DC~EC~EC~DB~DB~DB~DB~DC~EC~DC~EC~DC~DC~DC~DC~DC~DC~DB~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~CB~CB~DC~EC~DC~DC~DC~DC~DC~DC~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~EC~EC~DB~EC~EC~EC~EC~EC~EC~EC~DC~DC~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~ED~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~ED~ED~EC~EC~EC~EC~EC~EC~EC~EC~FD~FD~FD~FD~FD~FD~FD~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~EC~EC~ED~ED~FD~ED~EC~EC~EC~ED~FD~EC~EC~DB~EC~ED~FE~FE~FD~EC~EC~EC~EC~EC~FD~EC~EC~EC~EC~EC~ED~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~ED~FD~FD~ED~EC~EC~EC~EC~EC~EC~FD~FD~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~EC~ED~ED~EC~DC~DB~DC~EC~ED~EC~ED~ED~ED~ED~ED~ED~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~DC~DC~DC~DC~DB~DC~EC~EC~DC~DC~DC~DC~EC~EC~EC~EC~DC~DC~DC~DC~EC~EC~EC~EC~EC~DC~DB~DC~DC~DC~DB~DB~DB~DB~DC~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~DC~DC~EC~EC~EC~DC~EC~EC~EC~DB~DB~DB~DC~DC~DB~DB~DB~DB~CB~DB~DB~DB~DC~EC~EC~FD~EC~EC~DB~DB~DB~DB~DC~DC~DC~DC~DC~DC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DC~DB~DB~DC~DB~DB~DB~DB~DB~DC~DB~DB~DB~DB~CB~CB~CB~CB~DB~DB~DB~DB~CB~CB~DB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DB~DC~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DB~DC~CB~DB~DB~DB~DC~DB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DB~DB~DB~DB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CA~CA~BA~CA~CA~CA~CB~DB~DB~DB~DB~DB~CB~CB~CA~CA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~CA~CB~CB~CB~CB~CB~CA~CA~CA~CA~CA~BA~BA~BA~BA~BA~BA~CA~CB~CB~DB~DB~CA~CA~BA~CA~CA~CA~CA~BA~BA~BA~CA~BA~CA~CA~BA~BA~BA~BA~BA~CA~CA~CA~CA~BA~BA~CA~CA~CA~CB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CB~CB~CB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~B@~B@~B@~B@~B@~B@~B@~CA~CA~B@~B@~B@~B@~BA~CA~CB~CA~BA~BA~BA~B@~BA~BA~BA~BA~CB~CB~CB~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~CA~CB~CB~CB~CB~CB~CB~CB~CB~CB~CA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~CA~CA~BA~BA~BA~BA~BA~BA~BA~BA~CA~BA~BA~BA~CA~CA~BA~BA~BA~CA~CA~CA~CA~BA~BA~BA~BA~BA~BA~BA~BA~BA~CA~CA~CA~BA~BA~CA~CA~CA~CA~CA~BA~BA~BA~BA~CA~BA~BA~BA~BA~BA~BA~BA~BA~BA~CA~CA~CA~BA~B@~BA~CA~CA~BA~BA~BA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~BA~B@~BA~CA~BA~BA~BA~CA~CA~CA~BA~BA~CA~CA~CA~CA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~CA~BA~BA~BA~BA~CA~BA~B@~B@~BA~BA~BA~BA~BA~BA~B@~B@~BA~CA~CA~CA~BA~BA~B@~BA~BA~BA~BA~CA~CA~CA~BA~BA~BA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CB~CB~CB~CB~CA~CA~CA~BA~BA~BA~BA~CB~CB~CB~CB~BA~BA~BA~BA~BA~BA~BA~BA~CB~CB~CA~BA~BA~CA~BA~BA~BA~BA~BA~CA~CB~CB~CB~CB~CB~CA~BA~CA~CA~CA~CB~CB~CB~CA~CB~CB~CB~CA~BA~BA~BA~BA~CA~CB~CB~CB~CB~CB~CB~CB~CA~CA~CA~CA~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CA~CA~CA~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DC~DC~DC~DC~DC~DB~CB~DB~CB~CA~CA~CA~BA~BA~CA~CA~CA~CB~CB~CB~DB~CB~CB~CB~CB~CB~CB~DB~DB~DB~DB~CB~CB~CB~CB~CB~CB~DB~DC~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DB~DB~DB~CB~CB~DB~DB~DB~DB~DC~DC~DC~DB~CB~CB~CB~DB~DC~DC~DC~EC~DC~DC~DB~DC~DC~DC~DC~DC~DC~DB~DB~DB~DB~DB~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DB~DB~DC~DC~DC~EC~EC~ED~FD~FD~ED~EC~EC~EC~EC~DC~EC~EC~DC~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~DC~EC~EC~EC~EC~EC~FD~FD~FD~FD~FD~EC~EC~DB~DB~DB~EC~EC~EC~EC~EC~ED~FD~FD~ED~EC~EC~EC~EC~ED~ED~ED~EC~EC~EC~ED~EC~ED~ED~ED~EC~EC~EC~ED~ED~ED~ED~EC~DC~EC~EC~EC~EC~ED~ED~ED~ED~ED~ED~EC~EC~EC~EC~ED~EC~EC~EC~ED~ED~EC~EC~EC~FD~FD~FD~FD~FE~FD~EC~EC~ED~ED~EC~EC~EC~ED~ED~ED~ED~ED~ED~ED~ED~ED~ED~ED~EC~ED~ED~EC~ED~FD~FD~ED~ED~EC~EC~ED~ED~EC~EC~EC~ED~ED~ED~ED~ED~ED~ED~ED~ED~ED~FD~ED~ED~ED~ED~ED~ED~FD~FD~ED~EC~EC~ED~ED~ED~FD~FD~FD~FE~ED~EC~ED~ED~FD~FE~FE~FD~EC~FD~FD~FD~EC~ED~FD~FD~FD~FD~ED~ED~FD~ED~EC~EC~ED~ED~FD~EC~EC~EC~ED~ED~EC~EC~ED~FD~FD~EC~EC~EC~EC~EC~EC~EC~EC~ED~FD~FD~FD~ED~EC~EC~EC~EC~ED~EC~EC~ED~FD~ED~FD~FD~EC~EC~EC~EC~EC~EC~ED~FD~ED~ED~ED~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~ED~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~DC~EC~EC~DB~DB~EC~EC~EC~EC~EC~DB~EC~EC~EC~DC~DB~DC~DC~EC~EC~EC~EC~DB~CB~DB~EC~EC~EC~DB~DB~DC~DC~EC~DB~DB~DB~DB~DB~DB~EC~DB~DB~DB~DB~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~CB~DB~DB~DB~DB~CB~CB~CB~DB~DB~DB~DB~DB~CB~CB~CB~CB~DB~DB~DB~DC~DB~CB~CB~CB~CB~CB~CB~CB~CB~DB~DC~DC~DC~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DC~DC~DC~DB~DC~DC~DC~DC~DB~CB~DB~DB~DB~DB~CB~CA~BA~BA~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CA~CA~CA~CB~CB~CB~CA~CA~CB~DB~DB~DB~DB~CA~CA~CA~CA~CA~CB~CB~CB~CB~CB~CA~CA~CA~CA~CA~CA~CA~CB~CB~CB~CB~CB~BA~BA~CA~CA~CA~BA~BA~BA~CA~CA~CA~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~BA~BA~BA~BA~BA~CA~CB~CB~BA~BA~CA~CB~CB~CA~BA~BA~CA~CA~CA~CA~CA~CA~CA~CA~B@~B@~BA~BA~CA~CA~CA~CA~CA~CA~CA~BA~BA~B@~B@~B@~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~BA~CA~CA~CA~BA~B@~BA~BA~B@~B@~B@~B@~B@~BA~BA~BA~CA~BA~BA~CA~CA~B@~B@~B@~B@~CA~CA~CA~CA~CA~CA~CA~CA~B@~B@~B@~BA~CA~CA~CA~BA~B@~B@~B@~B@~B@~B@~B@~BA~CA~CA~BA~BA~B@~B@~B@~BA~BA~BA~BA~B@~B@~B@~B@~B@~B@~B@~B@~BA~BA~BA~BA~B@~B@~B@~B@~BA~BA~B@~B@~BA~BA~BA~B@~BA~BA~BA~B@~B@~B@~BA~BA~BA~B@~B@~B@~BA~B@~B@~B@~B@~B@~B@~BA~BA~BA~BA~BA~B@~B@~B@~B@~BA~BA~BA~BA~B@~BA~BA~BA~CA~BA~BA~BA~BA~BA~B@~A@~B@~BA~BA~B@~BA~BA~BA~BA~BA~B@~B@~B@~B@~B@~BA~BA~BA~BA~BA~B@~B@~B@~B@~BA~CA~B@~B@~BA~BA~BA~BA~BA~B@~B@~B@~BA~CA~BA~BA~BA~BA~BA~BA~B@~BA~BA~CA~BA~BA~BA~BA~CA~BA~BA~BA~BA~BA~BA~BA~B@~B@~BA~BA~CA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~A@~B@~BA~BA~BA~BA~BA~BA~B@~B@~B@~B@~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~B@~BA~BA~BA~BA~BA~BA~CA~BA~BA~BA~BA~BA~CA~CA~CA~CA~CA~CA~BA~CA~CA~BA~CA~CA~CA~CB~CB~CA~CA~CA~CA~CA~CA~BA~BA~CA~BA~B@~BA~BA~BA~BA~BA~CA~BA~BA~B@~BA~BA~BA~BA~CA~BA~BA~CB~CB~BA~BA~BA~BA~BA~BA~BA~BA~BA~CB~CA~BA~BA~BA~BA~BA~BA~BA~B@~BA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CB~CB~CA~CA~CA~CB~CB~CB~CB~DC~DB~CB~CB~CB~CA~CB~CB~CB~CB~CB~CB~CB~CB~CB~CA~CB~CB~CB~CB~CB~DB~DC~DC~DB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DB~CB~CB~CB~CA~CB~CB~CB~CB~DB~DC~DB~CB~CB~DB~DB~DB~DB~CB~DB~DC~DC~DC~DC~DB~DB~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DB~DC~EC~EC~DC~DB~DB~DC~DC~DC~DC~DC~EC~ED~FD~EC~EC~EC~EC~EC~EC~DC~DC~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~EC~EC~EC~FD~FD~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~EC~EC~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~EC~EC~ED~FD~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~ED~EC~EC~EC~EC~EC~EC~ED~ED~ED~ED~ED~ED~ED~EC~EC~ED~FD~FE~ED~ED~ED~ED~ED~FD~FE~FE~FE~ED~ED~ED~ED~FD~ED~ED~ED~ED~ED~ED~ED~ED~FD~FE~FE~ED~ED~ED~FD~FD~ED~ED~ED~ED~FD~FE~FE~FD~FD~FD~GE~GE~FD~FD~FD~ED~ED~FD~FE~FE~FE~FD~FD~FE~FE~FE~FE~FE~FE~FE~FD~ED~FE~ED~ED~ED~FE~FE~FE~FD~ED~ED~FD~FE~FE~FE~ED~ED~ED~FE~FD~FD~FE~FE~FE~FE~ED~FD~ED~ED~FE~FE~FE~FE~FE~FD~FE~FE~FE~FD~ED~ED~FE~FE~ED~ED~ED~ED~ED~FE~FE~FD~ED~ED~ED~ED~ED~ED~ED~ED~FD~FE~FD~FE~FE~FE~FE~FE~FE~FE~FE~FE~ED~FD~FD~FD~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FD~ED~EC~ED~ED~FD~FD~FD~FD~FE~FD~FD~FD~FD~FD~ED~EC~EC~EC~ED~FD~EC~EC~EC~EC~FD~FD~ED~ED~ED~EC~EC~ED~ED~FD~ED~ED~ED~EC~ED~FD~FD~ED~ED~ED~ED~ED~ED~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~FD~FD~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~EC~EC~EC~EC~DB~EC~EC~EC~EC~EC~EC~DC~DB~DB~DB~DB~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~DB~DC~DC~DC~DB~DC~DC~DC~DB~DB~DB~DC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~CB~CB~CB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CA~CA~CB~DB~DB~DC~DC~DC~DB~DB~DC~DB~DB~CB~CB~DB~CB~CB~CB~CB~CA~CA~CA~CA~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~BA~BA~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CA~CB~CB~CB~CA~BA~BA~CA~CB~CB~CB~CB~CA~CA~CA~BA~B@~CA~CB~CA~CA~CA~DB~CB~CA~CA~CB~CB~CB~CB~CA~BA~BA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~BA~BA~BA~BA~CA~CA~BA~BA~BA~CA~BA~CA~CA~CA~CA~CA~CA~CA~BA~BA~BA~BA~BA~BA~BA~BA~BA~CA~CA~CA~BA~BA~BA~CA~BA~BA~BA~BA~BA~CA~CB~CB~CA~BA~BA~BA~BA~BA~B@~B@~BA~BA~BA~BA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~BA~BA~BA~BA~B@~B@~CA~CA~CA~BA~BA~CA~CA~CA~CA~CA~BA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~B@~B@~B@~B@~B@~BA~BA~B@~B@~B@~B@~BA~CA~CA~CA~BA~B@~BA~BA~BA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~BA~B@~B@~B@~A@~B@~B@~B@~B@~CA~BA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~BA~B@~BA~B@~B@~A@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~BA~B@~B@~B@~B@~A@~A@~B@~B@~B@~B@~BA~B@~A@~B@~BA~B@~B@~A@~B@~B@~BA~B@~B@~A@~B@~B@~B@~BA~CA~BA~BA~CA~B@~A@~B@~B@~B@~B@~A@~A@~A@~A@~A@~A@~A@~A@~A@~B@~BA~BA~BA~B@~A@~A@~A@~A@~B@~B@~A@~A@~B@~B@~B@~B@~B@~B@~B@~A@~B@~B@~B@~B@~B@~B@~B@~B@~BA~BA~B@~BA~B@~B@~BA~BA~B@~B@~BA~BA~B@~B@~BA~BA~BA~BA~BA~B@~A@~B@~B@~B@~B@~B@~B@~BA~BA~BA~B@~B@~B@~B@~BA~BA~BA~B@~B@~BA~BA~BA~B@~BA~BA~CA~CA~BA~B@~B@~B@~B@~B@~B@~B@~B@~A@~A@~A@~B@~BA~BA~BA~BA~B@~B@~B@~BA~BA~BA~B@~BA~BA~BA~BA~BA~BA~BA~CA~BA~BA~BA~BA~BA~BA~B@~B@~B@~B@~BA~CA~BA~BA~BA~BA~B@~B@~BA~CA~CA~BA~BA~BA~BA~BA~BA~BA~BA~B@~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~CB~BA~BA~BA~BA~BA~BA~BA~BA~BA~CB~BA~BA~BA~CA~BA~CB~CB~CB~CB~CA~CB~CB~CB~CA~BA~CB~CA~CA~CA~CB~CB~CB~CB~CB~CB~CB~CB~CA~BA~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CA~CB~DC~DC~DC~CB~CB~CB~CB~CB~CB~DB~DB~CB~CB~CB~CB~DC~CB~CB~CB~DB~DB~CB~DB~CB~CA~CB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DC~DC~DB~DC~DC~DC~DC~DC~DC~DC~DB~DB~DB~DC~DB~DB~DC~DC~EC~EC~EC~EC~DC~DB~EC~EC~DC~DC~DC~DC~DC~DC~DC~EC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~DC~EC~EC~EC~EC~DB~DB~DB~EC~DB~EC~EC~EC~EC~EC~EC~EC~DB~EC~EC~EC~ED~ED~EC~DC~DB~EC~ED~EC~EC~EC~ED~EC~EC~EC~ED~ED~ED~ED~ED~ED~EC~EC~EC~FD~FD~FD~ED~ED~FD~FD~FD~FD~FD~EC~FD~FD~EC~EC~FD~FD~FD~FD~FD~FE~FE~FD~FD~ED~FD~FD~FD~FD~FD~FD~FD~GE~GE~FD~FD~FD~FD~FD~FD~FD~FD~FD~FE~FD~FD~FD~FD~FD~FD~FD~ED~ED~FD~FE~FE~FD~FD~ED~FE~FD~FE~FE~FE~ED~ED~FE~GE~GE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~GE~FE~FE~FE~FE~FE~GE~GF~GE~FD~ED~FD~FD~FD~FE~FE~ED~ED~FE~GE~FE~FE~FE~ED~FE~FE~FD~FE~FE~FE~FE~FE~FE~GE~FE~FE~FE~FE~FD~FD~FE~GE~GF~GE~FE~GE~GE~FD~FE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FE~FE~GE~GE~FE~FE~FE~GF~GF~GF~GF~FE~FE~FE~GE~FE~FE~FE~FE~GE~GF~GF~FE~FD~FE~FE~FE~FE~FE~FE~FE~FE~FD~FD~FE~GE~FE~FD~FE~FE~FE~FE~FE~FE~FD~FD~FD~FE~FE~FE~FD~FE~FE~FE~FE~FE~FD~FD~FE~FD~ED~ED~ED~FE~FE~FE~FE~FD~FD~FD~FD~FE~FE~ED~ED~ED~FD~FE~FE~ED~FD~FE~FE~FE~FD~FD~FD~ED~EC~ED~FD~FD~EC~EC~EC~ED~ED~ED~EC~DB~EC~FD~EC~EC~EC~EC~EC~ED~ED~EC~ED~ED~ED~EC~DC~DC~DC~EC~EC~DC~DC~DC~DC~EC~EC~ED~EC~EC~EC~DB~DB~DB~DB~EC~ED~ED~EC~EC~EC~DC~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~EC~EC~EC~DB~EC~DB~DB~DB~EC~DC~DC~EC~EC~DC~EC~DC~DC~DC~DC~DC~DB~DB~DB~DB~DB~DB~DC~DC~EC~DC~DB~DB~EC~EC~DC~DB~DB~DC~DB~DB~CB~CB~CB~CB~CB~DB~DB~CB~CB~CA~CA~CB~CB~CA~CA~CA~CB~CA~CA~CB~DB~DB~CB~CB~DB~DB~DB~CB~CA~CA~CB~CB~CA~CA~CA~CB~CB~CB~CA~CA~CA~CB~CB~CB~CB~BA~BA~BA~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CA~CA~CA~CB~CB~CA~CB~CB~CA~CA~CA~CB~CB~CB~CB~CA~CA~CA~CA~CA~CA~CA~CA~DB~DA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~B@~B@~CA~CA~B@~B@~B@~B@~CA~CA~CA~CA~B@~B@~BA~BA~CA~CA~CA~BA~B@~B@~B@~B@~B@~B@~B@~CA~CA~CA~BA~B@~B@~B@~B@~B@~B@~CA~CA~BA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~CA~BA~B@~B@~BA~BA~BA~BA~BA~CA~CA~CA~CA~CA~CA~CA~B@~B@~CA~CA~CA~CA~CA~CA~B@~CA~CA~CA~B@~CA~CA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A@~B@~B@~B@~B@~BA~B@~B@~B@~B@~B@~B@~A?~B@~BA~B@~B@~B@~B@~A@~B@~B@~A@~A@~BA~B@~B@~B@~B@~B@~B@~A@~B@~B@~B@~B@~B@~A@~A@~A@~B@~B@~BA~BA~BA~BA~B@~A@~A@~BA~B@~B@~B@~B@~BA~BA~BA~B@~B@~B@~B@~A@~A@~B@~B@~B@~B@~B@~B@~BA~BA~BA~B@~B@~B@~B@~B@~B@~B@~BA~BA~A@~A@~B@~BA~BA~BA~BA~B@~B@~B@~B@~A@~A?~A?~B@~B@~B@~B@~B@~B@~A@~B@~A@~A?~A?~A?~A?~A@~A@~A@~A@~A@~A@~A@~A@~B@~A@~A@~A@~A@~A@~A@~A@~A@~A@~A@~A@~A@~A@~A@~B@~A@~A@~A@~A@~A@~A@~B@~B@~B@~A?~B@~B@~A?~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~A?~B@~B@~A?~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~A?~B@~B@~B@~A?~B@~A@~A?~A@~B@~B@~A?~A?~B@~B@~A?~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~A@~B@~B@~B@~B@~A@~A@~A@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~CA~B@~B@~B@~CA~B@~B@~B@~B@~BA~BA~BA~BA~B@~B@~B@~BA~BA~A@~A@~B@~BA~B@~B@~B@~A@~B@~B@~BA~BA~CA~BA~BA~CA~BA~BA~BA~BA~BA~BA~BA~B@~BA~CA~CA~BA~BA~BA~CA~BA~B@~BA~CA~B@~B@~B@~B@~BA~CA~CA~CA~CA~CA~BA~B@~BA~CA~CA~CA~CA~CA~BA~BA~CA~CA~CA~CA~CA~CA~CA~CA~CB~CA~CA~BA~CA~CA~BA~CA~CB~CA~CA~CA~CA~C@~B@~B@~CA~CA~CA~CB~CA~CA~CA~CA~CB~CB~CA~BA~CA~CA~CA~CA~CA~CA~CB~CA~CB~CB~CA~CA~BA~CA~CB~CB~CA~CB~CB~CB~CB~CB~CB~DB~DB~CB~CB~CB~CB~CA~DB~DB~CB~CB~CB~CB~DB~CB~CB~CB~CB~CB~CB~CB~DC~DC~DC~CB~CB~CB~DB~DC~DB~DB~DC~DC~DC~DC~DC~DC~EC~EC~DB~DC~DC~DC~DB~DB~DC~EC~EC~EC~EC~EC~DB~DB~EC~EC~DB~DB~DB~DB~DB~DB~EC~EC~DC~EC~EC~EC~EC~DB~DB~EC~EC~EC~EC~EC~EC~EC~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~ED~EC~ED~FD~ED~EC~ED~ED~EC~EC~ED~ED~EC~FD~ED~ED~ED~EC~EC~ED~FD~FD~EC~EC~ED~FD~FD~FD~FD~FD~FD~FD~FE~FE~FE~FE~FE~FE~FD~FE~FE~FE~FE~ED~ED~FD~FE~FE~FD~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~GE~FE~FE~GE~GE~FE~FE~GE~GE~FE~FE~GE~FE~FE~FE~FE~FE~FE~FE~FE~GE~GE~GE~GE~GE~GE~GE~FE~FE~FE~GE~GE~FE~GE~GE~GE~GE~GE~GE~GE~FE~GE~GE~FD~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~HF~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~HF~HF~HF~HF~HF~HF~GE~GE~HF~HF~GE~GE~GE~GE~GF~GF~GE~GE~HF~GE~HF~GE~GF~HF~GE~FE~GF~GF~GE~GE~GE~FE~FE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FE~FE~GE~GE~HF~GE~GE~GE~GE~GE~GE~GE~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~GF~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FD~FE~FE~ED~ED~FD~FD~FD~FD~FD~FD~FD~FD~FD~ED~ED~FD~FE~FD~ED~EC~ED~FD~FE~FD~FD~FD~ED~EC~EC~ED~FD~FD~FD~FD~FD~FD~ED~FD~FD~FD~EC~FD~FD~FD~EC~FD~FD~ED~FD~ED~EC~EC~EC~EC~FD~FD~ED~EC~ED~ED~EC~ED~FD~ED~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~ED~ED~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~EC~DB~EC~EC~EC~EC~DC~DC~DC~DB~CB~DB~DB~DB~DB~DC~DB~DB~DB~CB~CA~DB~DB~DB~DB~CA~CA~DB~DB~DB~CB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CA~DB~DB~DB~DB~CB~CA~CA~CA~CA~CA~CB~CB~CB~CB~DC~DB~CB~CB~CB~CA~CB~CB~CB~CB~CA~CA~CB~CB~DB~CB~CA~CA~CA~CA~CA~CA~CB~CB~CB~CB~CB~CB~CB~CA~CA~CA~CB~CB~CA~CA~CA~CA~CA~BA~CB~CB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CB~CB~CA~CA~CA~CA~CA~CB~CA~CA~CB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CB~CA~BA~CA~CA~BA~CA~CA~CA~CA~CA~CA~C@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~B@~B@~BA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~CA~CA~CA~BA~BA~BA~CA~BA~B@~BA~BA~BA~B@~B@~BA~BA~BA~B@~B@~B@~BA~A@~B@~BA~A@~A@~B@~BA~B@~B@~B@~BA~B@~B@~A@~B@~A@~B@~BA~B@~B@~A@~B@~B@~B@~B@~B@~B@~BA~B@~B@~B@~B@~B@~A@~B@~B@~B@~A@~A@~B@~B@~B@~A@~A@~A@~A@~B@~A@~A@~A@~A@~A@~A@~B@~B@~A@~B@~BA~BA~A@~B@~B@~BA~BA~B@~A@~A@~B@~B@~A@~B@~B@~B@~A@~A@~A@~A@~B@~BA~BA~A@~A@~A@~A@~A?~A@~B@~A@~A@~A@~A@~A@~A@~A@~B@~A@~A@~A@~A@~BA~A@~A@~A@~B@~A@~A@~A@~B@~B@~A@~A@~A@~A@~B@~B@~A@~A@~A@~A@~A@~A@~A@~A@~A?~A?~A@~BA~B@~A@~A@~A@~A@~A@~A@~A?~B@~A@~A?~A@~A@~A@~A@~A@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A@~A?~A?~B@~A@~A@~B@~B@~A?~A?~A?~A?~B@~B@~B@~A?~B?~B@~B@~B@~B@~B@~B@~A?~A@~B@~B@~B@~BA~BA~BA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A@~A@~A@~B@~A@~A@~A@~B@~A@~A?~A?~A@~A@~A@~A@~A@~A@~A@~A@~A@~A@~A@~A@~A@~A@~A@~A@~A@~A?~A@~B@~B@~A?~A?~B@~BA~B@~BA~BA~BA~BA~BA~B@~B@~B@~B@~BA~B@~B@~B@~B@~B@~B@~B@~CA~BA~A?~B@~B@~B@~BA~BA~B@~B@~B@~B@~B@~B@~BA~B@~B@~B@~BA~A@~A@~BA~BA~A@~B@~B@~A@~A@~A@~B@~A@~A@~A@~B@~BA~B@~A@~A@~B@~A@~A@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~BA~BA~BA~BA~BA~BA~BA~BA~CA~BA~BA~BA~BA~BA~B@~BA~BA~BA~BA~BA~BA~BA~CB~BA~B@~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~CA~BA~BA~BA~CA~CB~CA~CA~CA~CA~CA~BA~CA~CB~BA~BA~CA~CA~CB~CA~CA~BA~BA~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CA~CA~CB~BA~CB~DB~DB~DB~CB~CA~CB~DB~DB~CB~CB~DB~DB~CB~CB~DB~DB~DB~DB~CB~CB~CB~CB~CB~CB~CB~DC~CB~DC~DC~DC~DC~DC~DC~DC~DC~DC~DB~DC~EC~DC~DB~EC~EC~DC~DB~DC~DC~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~EC~EC~EC~FD~FD~EC~EC~FD~FD~EC~FD~FD~FD~ED~FD~FD~ED~ED~FD~FD~ED~FD~FE~FE~FE~FE~FE~FD~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~GE~GF~GE~GE~GE~FE~FE~FD~FD~GE~FE~FE~FE~FE~GE~GE~GF~GE~GE~GE~FE~FE~GF~GE~GE~GE~GE~GE~GE~GE~GE~GF~GE~GE~GF~HF~HF~GF~GF~GE~GE~GF~HF~HF~GF~GF~GF~GF~HF~HF~HF~HF~HF~HF~HF~HF~GE~GE~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~GE~HF~HF~HF~GE~HF~HF~HF~HF~HF~HF~GE~HF~HF~HF~HF~GE~HF~HF~HF~HF~HF~HF~GE~GE~HF~HF~GE~GE~GE~GE~GF~GE~GE~GE~GE~HF~HF~HF~GE~GE~HF~HF~GE~GE~GE~GE~GE~GE~GE~GE~HF~GE~GE~HF~HF~HF~GE~GE~GE~GE~HF~HF~HF~HF~HF~GE~HF~GE~GE~HF~HF~HF~GE~GE~GE~HF~HF~HF~HF~HF~GE~GE~HF~HF~GE~HF~GE~FE~GE~HF~GE~HF~HF~GF~HF~HF~HF~GE~GE~GE~HF~HF~GE~GE~GE~GE~FE~GE~GF~GF~GF~GF~GE~FE~GF~GE~GE~GE~FE~GE~GE~FE~GE~FE~GE~GF~FE~FE~GE~GE~FE~FD~FD~FE~FE~GE~GE~FD~ED~FD~FE~FD~FE~GE~FE~FD~FD~ED~ED~ED~FE~FE~FD~ED~ED~ED~ED~ED~EC~ED~ED~ED~ED~ED~ED~FD~FE~FD~ED~EC~ED~ED~ED~ED~ED~ED~FD~ED~ED~ED~ED~EC~DC~EC~EC~ED~ED~ED~ED~EC~DC~ED~EC~EC~EC~EC~EC~EC~ED~FD~EC~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~EC~EC~EC~EC~EC~DC~DC~DC~EC~EC~EC~EC~EC~EC~DC~CB~CB~DB~DB~EC~EC~EC~DB~DB~DB~DB~EC~DB~DB~DB~CB~CA~DB~DB~DB~DB~DB~DB~CB~CB~CA~CA~CA~CB~DB~DB~DB~DC~DB~DB~CA~CA~CB~DB~CB~CB~CB~CB~CB~DB~CA~CA~CA~CA~CA~CA~CB~CA~CB~CB~CB~CB~CB~CB~CA~CA~CA~CA~CA~CB~CB~CB~CB~BA~BA~CB~CB~CB~CA~BA~CB~DB~CB~CA~CA~CA~B@~CA~CA~CA~CA~CA~BA~BA~B@~CA~CA~BA~BA~BA~BA~BA~BA~BA~BA~BA~CA~CB~BA~BA~CA~CB~BA~BA~BA~BA~BA~B@~BA~BA~BA~CA~BA~BA~CA~CA~B@~B@~BA~CA~CA~BA~B@~B@~B@~B@~B@~CA~CA~CA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~BA~B@~B@~B@~B@~B@~B@~BA~CA~B@~B@~B@~B@~B@~B@~CA~C@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A@~A?~A?~A@~BA~BA~BA~B@~B@~B@~B@~A?~A?~A?~A?~A@~B@~B@~B@~B@~A?~A@~A@~A@~A@~A?~A?~A?~B@~A@~A?~A?~B@~B@~A@~A?~A?~A?~A?~A?~A?~B@~B@~B@~B@~B@~B@~B@~B@~A?~A?~A?~B@~A?~A?~B@~B@~B@~A?~A?~B@~A?~A?~A@~A?~A?~B@~A?~A?~A?~A?~B@~B@~A?~A?~A?~A?~A?~A?~A@~A@~A@~A@~A@~A?~A?~A?~A?~B@~B@~A?~A@~B@~A?~A?~A?~B@~A@~B@~B@~A@~A@~B@~B@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~B@~A@~A@~A?~A?~A@~A?~A@~A@~A@~A@~A@~A@~A@~B@~A@~A?~A@~A@~A@~A@~A@~A@~B@~B@~A@~A?~A?~A?~A@~B@~B@~A?~A?~A?~A@~B@~B@~A?~A?~A?~B@~B@~B@~B@~B@~A@~B@~B@~A?~A?~B@~A?~A?~A@~A?~A?~A@~B@~A@~A?~A@~A@~A?~A?~A?~A?~A@~A@~A?~A?~A@~A@~A@~B@~B@~A@~A@~A?~A?~A?~A@~A@~A@~A?~A?~A?~@?~A?~A@~B@~B@~B@~A@~A@~B@~B@~A?~A@~B@~B@~B@~A@~A@~A@~A@~A@~B@~B@~A@~A@~B@~B@~B@~A@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~B@~B@~A@~A@~B@~B@~B@~A@~A@~A@~B@~B@~B@~B@~BA~B@~A@~A@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~BA~BA~B@~B@~B@~BA~CA~BA~BA~BA~BA~BA~B@~B@~BA~BA~BA~B@~B@~BA~BA~B@~B@~B@~BA~BA~BA~BA~BA~BA~CA~BA~BA~BA~BA~CA~BA~CA~BA~BA~BA~CA~CA~CA~CA~BA~CA~CB~CA~CA~BA~BA~CB~CB~CB~CB~CA~CA~CA~BA~BA~BA~CA~CB~CB~CB~CB~CA~CB~CA~CA~BA~CA~CA~CA~CB~CB~CB~CB~BA~BA~CA~CA~CA~CA~CA~CA~DB~CA~CA~DB~DB~CB~CA~CA~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~DB~DB~DB~DB~DC~DC~CB~CB~CB~DB~DC~DB~DC~DC~DC~DC~DB~DB~DB~DB~DC~DC~DB~DC~DC~DC~EC~EC~EC~EC~EC~DC~DB~DC~EC~EC~EC~EC~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~ED~ED~EC~EC~FD~EC~FD~FD~FD~EC~EC~ED~FD~ED~FE~FD~EC~EC~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FE~FD~FD~FE~FE~FE~FD~FE~FE~FE~FD~FE~FE~FE~FE~ED~FD~FE~FD~ED~FE~FE~FE~GF~GF~FE~FE~FE~GF~GE~GF~GE~FE~FE~GE~GF~GE~GE~GE~GE~GE~FE~GE~GF~FE~FE~GE~GE~FE~GE~GF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~HF~HF~GE~GE~GE~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~IG~HF~HF~HF~HF~HF~GE~GE~HF~HF~HF~HF~GE~HF~HF~HF~HF~HF~GE~GE~HF~HF~GE~GE~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~GE~HF~HF~HF~GE~GE~GE~HF~HF~HF~HF~GE~HF~GE~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~GE~HF~HF~GE~GE~GE~HF~GE~HF~GE~HF~HF~HF~HF~HF~HF~HF~HF~HF~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~IG~HF~HF~IG~HF~IG~HF~HF~HF~HF~HF~HF~GE~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GF~GE~GE~GE~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FE~FE~FE~GE~GE~FE~ED~FE~FE~FE~FE~FE~FE~FE~FE~FE~ED~ED~FE~FE~FE~FE~FD~FE~FE~ED~ED~ED~EC~FD~FD~ED~ED~FD~FD~FD~FD~FD~FD~FE~ED~ED~ED~ED~ED~ED~ED~ED~ED~EC~ED~ED~EC~ED~ED~ED~ED~ED~ED~ED~EC~EC~DC~DB~EC~FD~EC~EC~EC~EC~EC~DB~DB~EC~EC~EC~EC~EC~EC~FD~EC~DB~EC~EC~EC~EC~EC~EC~EC~DB~DB~EC~EC~EC~DB~DB~EC~DB~DB~DB~EC~EC~EC~DB~DB~CB~DB~DB~DC~DC~CB~CB~DC~DB~DB~CA~CB~CB~CB~CB~CB~CB~DB~DC~DB~DB~DB~DB~DB~DB~CB~CA~CB~DB~DB~CB~CB~CB~CB~CA~CA~CA~CB~CA~CA~CB~CA~BA~BA~CA~CB~DB~CB~BA~BA~CB~CB~CA~CA~CA~CA~CA~CA~BA~BA~BA~BA~CA~CA~CA~B@~B@~CA~CA~CA~CA~CA~CA~CA~BA~BA~CA~BA~CA~CB~BA~CA~CA~BA~BA~BA~B@~BA~BA~CA~B@~B@~CA~B@~B@~BA~B@~B@~BA~BA~BA~B@~A@~A?~BA~CA~BA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~CA~B@~B@~B@~B@~B@~B@~CA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~CA~B@~A?~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~BA~A@~A?~A@~B@~A?~@?~A?~A@~A?~A?~A?~A?~A?~A?~A@~A@~A@~A@~A@~A?~A?~A@~A?~A?~A@~A?~A?~A@~A@~A@~B@~A?~A?~A?~A@~B@~A?~A?~B@~B@~A?~A?~B@~A@~B@~A@~A?~A?~A@~A?~A?~A?~A?~A?~A?~A?~A@~A?~A?~A?~A?~B@~B@~A?~A?~B@~A?~A?~A?~A?~A?~A@~A@~A?~A?~A?~A?~A?~A?~A@~A@~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A>~A?~A?~A?~A?~A?~A?~B@~A?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~B@~A?~B@~A?~A?~B@~A@~A?~A?~A?~A?~A?~A?~A@~A@~A?~A?~A@~A@~B@~A@~A?~A?~A?~A@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~A?~@?~A?~B@~A?~A?~A@~A@~A@~A?~A?~B@~A@~A@~A?~A?~A?~A@~A@~A?~A?~A@~A@~A?~A?~A?~A?~A@~A?~A?~A?~A?~A?~A?~A@~A@~@?~A?~A@~A@~A@~A@~A@~A?~A@~A@~A?~A?~A?~A@~A@~A@~@?~A@~A?~A?~A?~A?~A@~A@~A?~A@~A@~A@~A@~B@~B@~A@~A@~A?~A?~A?~A?~A?~A@~A?~A@~A@~A?~A@~A?~A@~B@~B@~A@~A@~B@~B@~A@~B@~A@~A?~A?~A?~A?~A?~A?~A@~B@~A@~A?~A@~B@~A@~A@~B@~A@~A?~A?~A@~A@~B@~B@~A@~B@~BA~B@~BA~A@~A@~B@~A@~BA~B@~A@~A@~A@~B@~A@~A@~BA~B@~B@~B@~B@~B@~B@~A@~B@~BA~B@~BA~B@~A@~BA~BA~BA~BA~A@~A@~A@~BA~BA~BA~B@~BA~B@~B@~B@~B@~B@~B@~B@~B@~A@~B@~B@~B@~B@~B@~BA~B@~BA~BA~A@~BA~B@~BA~B@~B@~B@~BA~BA~B@~BA~BA~B@~A@~B@~BA~BA~BA~BA~BA~B@~B@~BA~B@~BA~BA~BA~BA~CA~CA~CA~CA~CA~CB~BA~B@~CA~B@~BA~CA~CA~BA~CA~CA~BA~CA~CB~CA~CA~CA~BA~CA~CB~CB~CB~CA~CB~CA~CA~CA~CB~CA~CA~CB~CB~CB~CA~CA~CA~CA~CA~CB~CB~CA~DB~DB~DB~CA~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CA~DA~DB~DB~DB~DB~CB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~DC~DB~EC~EC~DB~DB~DB~DB~EC~EC~EC~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~FD~EC~EC~EC~EC~EC~FD~FD~EC~EC~EC~FD~FD~FD~FD~FD~FD~ED~EC~ED~ED~FD~FD~FD~ED~FD~FD~FD~FD~FD~ED~FD~FD~FD~FD~FE~FE~FE~FE~FE~FD~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~FE~GE~GE~GE~GE~GE~GE~GE~GE~FE~FE~FE~FE~FE~GE~GE~GE~GE~GE~GE~GF~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~HF~HF~GE~GE~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GF~HF~HF~HF~HF~GE~HF~HF~GE~HF~HF~GF~GE~HF~HF~HF~IG~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~GE~HF~HG~HG~HF~HF~IG~HG~IG~HG~HG~IG~HG~HF~HF~HF~HF~HF~HF~HG~HG~HF~HG~HG~HG~HF~HF~HF~HG~HG~IG~HF~IG~IH~HG~IG~IG~IH~IG~HF~HF~HF~HF~HF~HF~HG~HF~HF~HF~HF~HF~HG~IG~HF~HF~HF~HF~HF~HG~HG~HG~GF~HF~HG~HF~HG~HG~HF~HF~HF~HF~HF~HF~GE~HF~HF~HF~HF~HG~HF~GF~GF~HG~HG~HF~HF~GF~HF~GF~GF~HG~HG~HG~HG~HF~HF~HF~GE~GE~GE~GF~HF~HF~HF~GE~HF~GF~HF~HF~GE~HF~HF~HF~GE~GE~GE~GE~HF~GE~GE~HF~HF~GE~GE~HF~HF~GE~GE~HF~HF~GE~HF~HF~HF~GE~GE~GE~GE~GE~GE~HF~GE~GE~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FD~FD~FD~GE~GE~FD~FD~FD~GE~GE~FD~FE~GE~FE~FD~ED~FD~ED~ED~GE~FE~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~ED~EC~EC~EC~ED~EC~FD~FD~FD~EC~EC~ED~FD~EC~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DC~DB~CB~DB~DC~DB~DB~CB~DB~DB~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CA~CA~CA~CA~CA~DB~DB~DB~DB~DB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~BA~CB~DB~CA~CA~CB~CB~CB~CA~BA~BA~CA~BA~BA~CA~DB~CA~CA~B@~B@~CB~CA~CA~B@~CA~CA~CA~CA~CA~BA~BA~BA~CA~BA~BA~CA~CA~CA~CA~CA~BA~BA~CA~BA~CA~CA~CA~CA~CA~B@~B@~B@~B@~CA~B@~B@~B@~C@~C@~B@~B@~CA~B@~B@~CA~CA~CA~CA~B@~B@~B@~B@~B@~BA~B@~A@~B@~BA~BA~B@~B@~A?~B@~BA~BA~A@~A@~B@~BA~BA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A@~B@~B@~B@~B@~B@~B@~B@~A?~B@~B@~A?~B@~B@~CA~CA~B@~B@~B@~B@~B@~B@~A?~B@~B@~A?~A?~A?~B@~A?~A?~A?~A?~A?~A?~B@~B@~A@~A?~B@~A?~A?~A?~A@~A@~A?~A?~@>~A?~A?~A?~A@~A@~A?~A?~A?~A?~A?~B@~B@~A?~A?~A?~A?~A?~A?~A?~A?~B@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~B@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A@~A?~A?~@?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~B@~A?~A?~A?~A?~A?~A?~B@~B@~A?~A?~A?~A?~A?~A?~A@~A?~A?~A?~A@~A?~A?~A?~A?~A?~A?~B@~B@~A?~A?~A?~A?~A@~B@~A@~B@~B@~A?~A?~A?~A@~A?~A?~A?~A?~A?~A?~A@~A@~A@~A@~B@~A@~A?~A@~A@~A@~A@~A?~A?~A@~A?~A@~A@~A?~A@~A?~A?~A?~A?~B@~B@~B@~B@~A@~B@~A@~A@~A@~B@~B@~A@~A@~B@~B@~B@~A?~A?~A?~A@~A@~B@~B@~A@~A?~B@~A?~A?~B@~B@~B@~B@~B@~B@~B@~A?~A@~B@~B@~BA~B@~A@~A@~B@~A@~B@~BA~B@~B@~B@~B@~A@~B@~B@~B@~B@~B@~B@~B@~A@~B@~B@~BA~B@~B@~BA~B@~B@~B@~BA~B@~B@~B@~B@~B@~B@~BA~BA~BA~B@~B@~BA~B@~BA~BA~BA~B@~BA~BA~CA~CA~CA~BA~CA~BA~B@~B@~B@~BA~BA~BA~BA~BA~CB~BA~BA~BA~BA~CA~CB~BA~BA~BA~CB~BA~CA~BA~CA~CA~CA~CA~BA~CA~CB~CB~CB~CA~CA~CA~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DB~CB~CB~CB~DB~CB~DB~CB~DC~DB~DC~DC~DB~CA~DB~CB~DB~DB~DB~DB~DB~EC~DB~DB~DB~DB~EC~EC~DB~DB~EC~EC~DB~EC~EC~EC~EC~EC~EC~EC~EC~DC~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~ED~ED~FD~EC~EC~EC~ED~ED~ED~FD~FD~FD~FD~FD~FD~FE~FE~FD~FD~FE~GE~FE~FE~FE~FE~FE~FD~FE~FE~FE~FE~GF~FE~FE~FE~FE~GE~GE~FE~GE~GE~GE~GE~GE~FE~GE~GE~GE~GE~GE~GE~GE~HF~GE~GE~HF~HF~GE~GE~GE~GE~GE~GE~GE~GE~HF~GE~HF~HF~GE~HF~HF~GE~HF~HF~HF~GE~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HG~HF~HF~HF~HG~HG~HF~HF~HF~HF~HG~HF~HF~HF~HF~HF~HF~HG~HG~HF~HF~HG~HG~IG~HF~HF~HF~IG~HG~HF~HF~IG~IG~IG~IG~IG~IG~HF~IG~IG~IG~IG~IG~IG~IG~IG~HF~HG~HG~JH~IG~HG~IG~IG~IG~IG~HG~IG~JH~IG~IG~HG~IG~IH~HG~HG~HF~IG~IH~IG~IH~IG~IG~IG~HG~HG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~HG~IG~HG~HG~HG~IG~HG~HG~IG~HG~HG~IG~IG~IG~IG~IG~IG~HG~HG~IG~IG~IG~HF~IG~IG~IG~HG~IG~HG~HG~IG~IG~IG~HG~HG~IG~HF~HF~IG~IG~HF~IG~HF~IG~HG~HF~HF~IG~IG~HF~HF~IG~IG~HF~HF~IG~HF~HF~HF~HF~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~GE~HF~HF~HF~HF~HF~HF~HF~HF~HF~GE~GE~HF~GE~HF~GE~GE~GE~GE~HF~HF~GE~GE~GE~HF~HF~HF~GE~HF~GE~GE~GE~FD~FD~GE~GE~GE~GE~GE~FD~FD~GE~GE~GE~GE~GE~GE~FD~GE~GE~GE~GE~GE~FE~GE~FE~GE~FD~FE~GE~GE~FE~FE~ED~ED~FE~FD~ED~FD~FE~FE~FE~FD~ED~ED~EC~FD~FE~FE~ED~ED~ED~ED~ED~EC~ED~FD~FD~EC~FD~ED~EC~EC~EC~FD~ED~EC~EC~FD~FD~EC~EC~FD~EC~FD~FD~FD~EC~DC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~EC~EC~EC~EC~DB~DB~EC~DB~DB~DB~DB~DB~EC~DB~DB~DB~DB~EC~DB~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~CB~CB~CB~CA~CA~CB~CB~CA~CB~CB~CB~CB~CA~CA~BA~BA~CA~CA~DB~CB~CB~CA~CA~CA~CA~B@~BA~CA~B@~BA~CA~BA~CA~BA~BA~BA~BA~BA~CA~CB~BA~BA~BA~B@~BA~BA~BA~BA~BA~BA~BA~BA~BA~B@~B@~B@~BA~BA~B@~B@~B@~B@~B@~B@~B@~CA~CA~CA~B@~B@~B@~B@~CA~CA~BA~B@~B@~B@~BA~BA~B@~B@~B@~BA~B@~B@~B@~A@~A@~A@~A@~BA~BA~B@~B@~B@~B@~B@~BA~B@~B@~CA~BA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~B@~B@~BA~B@~B@~BA~B@~A?~A?~B@~B@~B@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~B@~A?~A?~A?~A?~B@~B@~B@~B@~B@~B@~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~A@~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~@>~A?~A?~A@~A@~@?~@>~A?~A?~A?~@?~@?~A?~A?~A?~@?~A?~A?~B@~A?~A?~A?~A?~@>~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~@?~A?~A?~@?~A?~A?~A?~@>~@>~@>~@>~A?~A?~@>~A?~A?~@>~@>~@>~@>~A?~A?~@?~@?~A?~@?~@>~@?~A?~A?~A?~A?~A?~@?~@>~@>~A?~A?~A?~B@~A?~A?~A?~@>~@>~@>~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~A?~A?~A?~A?~A?~A?~A?~B@~A?~A?~A?~A?~A?~A?~A?~A?~@>~@>~A?~A?~A?~A?~A?~A?~A?~A?~B@~BA~B@~A?~A@~A?~A?~A?~A@~A?~@?~@?~A@~A@~A@~A@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~B@~B@~B@~A?~A@~B@~B@~A?~A?~B@~B@~A?~A?~A?~A?~A?~A?~B@~A?~A?~A?~A?~B@~B@~B@~B@~A@~B@~B@~B@~A@~A@~A?~A@~B@~A?~A?~B@~B@~A?~A?~B@~A?~A?~A?~B@~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~B@~B@~B@~CA~CA~BA~BA~BA~BA~B@~B@~B@~B@~B@~B@~B@~BA~BA~BA~BA~BA~BA~BA~BA~B@~B@~BA~BA~BA~CA~CA~CA~BA~BA~CA~CA~BA~BA~CA~BA~BA~CA~BA~BA~CB~CB~CA~CB~CA~CA~CA~CB~CA~CA~CA~CA~CB~CB~BA~CB~CA~BA~BA~CB~CB~CB~CA~CB~DB~CB~CB~CB~DB~DC~DB~CA~DB~CA~CA~CA~DB~DB~CA~CA~DB~EC~EC~EC~CA~DB~EC~DB~DB~EC~EC~DB~DB~DB~EC~DB~DB~EC~EC~EC~EC~DB~DB~DB~EC~EC~DC~DB~DC~EC~EC~DB~EC~ED~DC~EC~EC~EC~ED~ED~EC~EC~EC~EC~ED~ED~ED~ED~ED~ED~ED~ED~ED~ED~FE~FE~ED~FE~FE~ED~FE~FE~FD~ED~FD~FD~FE~FE~FE~FE~FE~FE~FE~FE~GE~FE~FE~FE~FE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~HF~GE~GE~HF~GE~HF~HF~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GF~HF~HF~GE~HF~HF~HF~HF~HF~HF~HF~GF~HF~HG~IG~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~HG~HF~HF~HG~IG~HG~HG~HG~IG~HG~IG~HF~HG~HG~HG~IG~HG~HG~HG~HG~HG~IH~IH~HG~IG~IH~JH~IH~IG~IG~IG~IG~IG~JH~JH~IG~IG~IG~IG~IG~IG~IG~JH~JH~IG~IG~JH~JH~JH~IG~JH~JH~JH~JH~JH~JH~JH~IG~IG~JH~IG~JH~JH~IG~JH~JH~JH~IG~JH~IG~JH~IG~IG~JH~IG~JH~JH~JH~JH~JH~JH~IG~IG~IG~JH~IG~IG~IG~JH~JH~JH~JH~IG~IG~IG~IG~JH~JH~IG~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~HF~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~HG~IG~IG~HG~HG~HG~HG~IG~IG~IG~HF~HG~IG~HG~IG~HG~HG~HG~HF~HF~HF~IG~HG~HF~IG~IG~HF~HF~HF~HF~HG~HF~HG~HG~HF~HF~HF~HF~HF~HF~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~GE~GE~HF~HF~GE~HF~GE~GE~GE~HF~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FD~GE~GE~GE~GE~GE~FD~FD~GE~GE~GE~GE~GE~GE~FE~FD~FD~FD~FE~FE~GE~FD~FD~ED~ED~FD~FE~FE~FD~FD~FD~FE~FD~ED~ED~FD~FD~FE~ED~ED~FD~ED~FD~ED~FD~FD~FD~ED~FD~FD~FD~EC~FD~ED~ED~FD~ED~EC~EC~FC~FC~FD~EC~EC~EC~EC~EC~EC~DB~EC~FD~EC~EC~EC~DB~EC~EC~EC~DB~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~DB~CB~CB~DC~DB~DB~CB~CB~CB~CB~CB~CB~CA~CA~DB~DB~CA~DB~CB~CB~CB~CB~CB~CB~CB~CB~CA~CA~CB~CA~CA~CA~CA~CB~CA~CA~CA~CA~CA~BA~CA~CA~CA~BA~BA~CA~CA~CA~CA~BA~CA~CA~B@~CA~BA~CA~B@~BA~B@~B@~CA~B@~CA~C@~B@~C@~CA~C@~C@~C@~C@~C@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~CA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~B@~B@~B@~B@~B@~B@~A?~B@~B@~A?~A?~B@~B@~A?~A@~B@~B@~B@~B@~A@~B@~B@~A?~A@~B@~A@~B@~B@~B@~B@~B@~B@~A?~A?~A?~B@~B@~B@~A@~A?~A?~A?~A?~B@~A?~A?~A?~A?~A?~A?~B@~A?~A@~B@~A?~A?~A?~A?~A?~A?~A?~A@~A?~A?~A?~A?~A?~A?~B@~A?~B@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~@?~A?~A?~A?~A?~@>~@?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A>~@>~A?~A?~@>~@>~@>~A?~@>~@>~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~@>~A?~A?~A?~A?~@>~A?~A?~A?~A?~@>~A?~A?~A?~@>~A?~A@~A?~@?~A?~A?~@?~A?~A?~A?~A?~@?~A?~A?~@>~@?~A?~A?~A?~A?~A?~@>~@>~A?~@>~@>~A?~A?~A?~A?~@>~A?~@>~@>~A?~@>~@>~@>~@>~A?~A?~A?~@>~A?~A?~@>~@>~A?~@>~A?~A?~A?~A?~A?~A?~@>~A?~@>~A?~@>~@>~A?~A?~@>~A?~A?~@>~A?~A?~A?~A?~@>~A?~A?~@?~A?~@?~@?~A?~@?~@>~A?~A?~@?~@?~@?~@>~A?~A?~@?~A?~A?~A?~A?~A?~A?~A?~A?~@?~@?~A?~A?~@?~A?~@?~@?~A?~A?~@?~@>~A?~A?~A?~A?~A?~A?~@>~@>~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@?~A?~A?~A?~A@~A?~@>~A?~A?~A?~A?~A?~A@~A?~A?~A@~B@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~A@~A@~A@~A?~A?~A?~A?~A@~A@~A@~B@~A@~A?~A@~A@~B@~A@~B@~A@~A?~A?~A?~B@~B@~A?~A@~A@~B@~A@~A?~A@~A@~A?~A@~A@~A@~A@~A@~A@~A@~A@~A@~A@~A@~A@~B@~B@~B@~BA~B@~B@~B@~B@~BA~B@~B@~B@~B@~B@~B@~BA~BA~B@~BA~B@~CA~BA~CA~B@~B@~B@~B@~B@~B@~B@~BA~CA~BA~B@~B@~BA~BA~BA~BA~BA~BA~BA~B@~BA~BA~BA~BA~BA~BA~CA~BA~B@~BA~BA~CA~BA~CA~BA~CB~CA~BA~BA~CA~BA~CB~BA~BA~BA~CA~CA~BA~CB~CB~CB~CB~BA~CA~CB~CB~DB~CB~DB~CB~CA~CA~CB~CB~CB~CB~CB~CB~CB~CB~CB~CB~DC~CB~CB~CB~CB~DB~CB~DC~CB~DC~EC~DC~DB~DC~EC~EC~DC~EC~DB~EC~EC~DC~DC~DC~EC~EC~EC~EC~DC~EC~EC~EC~FD~EC~EC~FD~EC~EC~EC~EC~EC~EC~FD~FD~EC~EC~FD~FD~FD~FD~FD~EC~FD~ED~FD~FD~FD~FD~ED~EC~FD~FD~FD~FD~FD~FE~FE~ED~ED~FE~GE~FE~FE~GF~FE~GE~GF~GF~GE~FE~GE~GF~GE~GE~GF~GE~GE~GE~GE~HF~GE~GE~HF~HF~HF~GE~GE~GE~GE~GE~GE~HF~GE~GE~GE~GE~GE~HF~HF~GE~HF~HF~GF~HF~GF~HG~GE~GE~GF~GF~HF~HG~HG~GF~GF~HF~HF~HG~HG~HG~HG~HG~HF~HF~HG~HF~HG~HG~HG~HF~HG~IG~IG~IG~IG~IG~IH~IG~IG~IG~IG~IG~IG~IH~IG~IH~IG~IG~IG~IH~IH~IH~IH~IG~IH~IH~HG~IH~JH~IH~IH~JH~IH~IG~IH~JH~JH~JH~JH~JH~JH~IG~JH~JH~JH~JH~IG~IG~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~IG~IG~JH~JH~IG~IG~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~IG~IG~IG~JH~IG~JH~IG~IG~IG~IG~JH~IG~JH~JH~IG~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~IG~IG~IG~JH~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~HF~IG~IG~IG~HG~IG~IG~IG~HG~HG~HG~IG~HF~IG~HG~HG~HF~HF~HF~HF~HF~HG~HG~HF~HF~HF~GF~HF~HG~GF~HF~HF~GF~HF~GF~GE~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~IG~HF~GE~HF~GE~HF~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~HF~HF~GE~GE~FE~GE~GE~GF~FE~FE~FE~FE~FE~GE~GE~FE~FE~GE~FE~FD~FE~GE~GE~GE~GE~FE~FD~GE~FD~ED~GE~FD~FD~FD~GE~FD~GE~FD~FD~FD~ED~EC~ED~FD~FD~FD~ED~EC~EC~EC~FD~FD~ED~EC~ED~FD~ED~EC~EC~EC~EC~EC~EC~FD~EC~FD~EC~FD~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~DC~DC~DC~DC~EC~EC~DB~DB~DB~EC~EC~EC~DB~DB~DB~CB~DB~DB~DB~DB~DB~DB~DB~CB~CA~DB~CA~DB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CB~CA~CA~CA~CA~CA~CA~CA~CA~CA~BA~BA~CB~CB~CA~CB~CB~CB~CA~BA~CA~CA~BA~BA~CA~CA~BA~CA~BA~CA~CA~CA~CA~CA~CA~CA~CA~CA~B@~B@~B@~B@~B@~B@~BA~B@~B@~B@~B@~B@~BA~BA~BA~B@~BA~B@~B@~BA~B@~A?~B@~B@~BA~B@~B@~CA~B@~B@~B@~B@~B@~B@~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~CA~B@~B@~B@~B@~B@~B@~A@~A@~A?~A?~A@~A@~A?~A@~A@~A?~A@~A@~A@~A?~A?~A?~A?~A@~A@~A?~A?~A?~A?~A?~A?~A@~A?~A?~A@~A?~A?~A@~B@~A@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~A?~A?~A?~A?~A?~A@~A?~A?~A?~A@~A@~@?~@>~@>~@>~@?~@>~@?~A?~@?~A?~@>~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~@>~@>~@>~@>~@>~A?~A?~@>~A?~A?~A?~A?~@>~A?~A?~@?~A?~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~A?~A?~A?~A?~@>~A?~A?~@>~@>~@>~A?~A?~@>~@>~@>~A?~A?~A?~@>~@>~@>~A?~@>~@>~@>~@>~A?~@>~@>~A?~@>~A?~A?~A?~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~A?~A?~A?~A?~A?~A?~A?~@>~A?~@?~@>~A?~A?~A?~@>~@>~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~@>~@>~@>~@>~@>~@>~A?~@>~A?~@>~@>~@>~@>~A?~@>~A?~@?~@>~@>~A?~A?~@>~@>~A?~A?~@>~@>~@>~A?~A?~@>~@>~@>~A?~A?~A?~A?~@?~A?~@?~@>~@>~@?~@>~A?~@?~@?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~@>~@>~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~B@~A?~@>~A?~A?~A?~@>~A?~@>~A?~A?~B@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@?~A?~A?~A?~A?~A?~A@~A?~@?~A?~A@~A@~A?~A?~A?~A?~A?~A?~B@~A?~A?~B@~A?~A?~B@~A?~B@~A?~A?~A?~B@~B@~B@~A@~A@~A?~A@~A@~A@~A@~A@~A@~A@~A@~B@~A@~A@~A@~A@~A@~A@~A@~A@~B@~B@~B@~B@~B@~B@~B@~A@~A@~B@~B@~B@~B@~B@~BA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~BA~B@~B@~B@~B@~B@~B@~B@~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~BA~CA~B@~BA~BA~BA~CA~CA~BA~CA~CA~CB~CB~CA~CA~CB~CB~CB~BA~CB~CB~BA~CB~CB~CA~CB~CB~CA~CB~CB~CA~CB~DB~CB~CB~CB~CA~CB~DC~CB~DC~CB~CB~DB~DB~DC~DC~DB~DB~DB~EC~EC~DB~DB~EC~DB~DB~EC~DB~EC~DB~EC~EC~EC~EC~EC~FD~EC~EC~EC~EC~EC~EC~EC~EC~FD~FD~EC~ED~ED~EC~EC~ED~ED~ED~ED~ED~ED~ED~FD~ED~FD~FD~FE~FE~FE~FD~ED~FE~FE~FE~GE~FE~FE~FE~FE~GE~GE~FD~FD~GE~GE~GE~GE~GE~GE~GE~GE~GE~HF~GE~GE~HF~GE~GE~GE~GE~HF~HF~HF~GE~GE~GE~HF~HF~GE~HF~HF~GE~GE~GE~GE~HF~HF~HF~HF~GE~GE~GE~HF~HG~HF~HF~HF~HG~HF~HG~HF~HG~HF~HF~HF~HF~HG~HG~HG~HG~IG~IG~HG~HG~IG~IG~IG~IG~IG~IG~HG~HF~IG~HG~IG~HG~HG~IG~IH~JH~IH~IG~JH~IH~IG~IH~IH~IH~IG~IH~IH~IH~IH~IH~JH~JH~JH~IG~IG~JH~JH~JH~IG~JH~JH~JH~JH~IG~JH~JH~IG~JH~IG~IG~IG~IG~IG~IG~IG~IG~JH~JH~JH~JH~IG~JH~JH~JH~JH~JH~JH~JH~JH~IG~JH~IG~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~JH~JH~IG~JH~JH~IG~IG~JH~JH~JH~IG~JH~JH~IG~IG~JH~IG~JH~JH~IG~JH~IG~IG~JH~JH~JH~JH~JH~JH~IG~JH~JH~JH~JH~IG~IG~JH~IG~IG~IG~IG~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~IG~IG~IG~IG~HF~HF~HF~HF~IG~IG~HF~IG~HF~IG~HF~IG~IG~HF~HF~HF~HF~HG~HF~IG~IG~IG~HF~HF~HF~HF~GE~HF~HF~HG~HF~HF~HF~HF~GE~GE~HF~HF~HF~HF~HF~GE~GE~GE~GE~GE~GE~GE~GF~GF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FD~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FD~GE~FE~FD~FD~FD~FD~FD~FD~FD~FD~FD~GE~GE~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~ED~FD~FD~ED~EC~FD~EC~EC~FD~EC~EC~EC~EC~EC~EC~EC~FD~EC~EC~EC~EC~EC~EC~EC~DB~EC~EC~EC~DB~EC~EC~EC~EC~EC~EC~DB~DB~EC~EC~DB~DB~DB~DB~EC~DB~DB~DB~DB~DB~DB~CA~DB~DB~DB~DB~DB~CB~CB~DB~CB~DC~CB~CB~CB~CB~CB~DB~CB~CB~CB~CA~CB~CA~CB~CA~CA~CA~CA~CA~CA~CA~CB~DB~CB~CA~CA~CA~CB~CB~CA~CA~CA~CA~CA~CA~B@~CA~CA~CA~CA~CA~CA~B@~B@~B@~B@~CA~CA~BA~BA~BA~BA~B@~B@~BA~BA~BA~B@~B@~BA~BA~B@~B@~A@~BA~B@~BA~B@~B@~B@~B@~B@~B@~B@~CA~BA~B@~B@~BA~B@~B@~B@~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~B@~B@~A?~B@~B@~A?~B@~B@~A?~B@~B@~B@~A@~A?~A@~B@~A?~A?~A@~B@~B@~A?~B@~A?~B@~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~B@~B@~B@~A?~A?~B@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~A@~A?~A?~A?~A?~A?~@?~@>~A?~A?~A?~@>~A?~A?~A?~@>~@>~@?~A?~A?~A?~A?~@>~@>~A?~A?~@>~A?~@?~@>~A?~@>~@>~A?~A?~A?~A?~A?~@>~A?~A?~@>~@>~@>~@>~A?~@>~@>~@>~@>~A?~@>~A?~A?~A?~@>~@>~A?~@>~@>~@>~A?~@>~@>~A?~A?~A?~@>~@>~@>~@>~@>~@>~A?~A?~@>~A?~A?~@>~@>~@>~@>~A?~A?~@>~@>~@>~@>~@>~@>~A?~A?~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@?~A?~@>~A?~A?~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~A?~A?~@>~@>~@>~@>~@>~A?~A?~A?~@>~@>~@>~A?~@>~@>~A?~A?~@>~@>~@>~@>~@?~@?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~A?~A?~@>~@>~@>~@>~A?~@>~A?~@>~@>~A?~A?~A?~A?~@>~@>~A?~@>~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~@>~A?~A?~@>~A?~A?~A?~@>~@>~@>~A>~@>~A?~A?~A?~A?~@>~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~@?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~@>~@>~A?~A?~A?~A@~A?~A@~A?~A?~A@~B@~B@~A@~A@~A?~A?~A?~B@~A?~A?~A?~A?~A?~A@~A?~A?~A@~A@~A?~A@~A@~B@~A@~A@~A@~A@~A?~B@~B@~B@~A@~B@~B@~B@~A?~B@~B@~B@~B@~BA~B@~B@~CA~B@~B@~B@~A?~BA~B@~B@~B@~B@~BA~BA~BA~B@~BA~BA~BA~B@~B@~BA~B@~BA~BA~BA~BA~BA~B@~BA~CA~CA~BA~CA~B@~CA~BA~BA~BA~BA~BA~BA~BA~BA~BA~CA~BA~BA~BA~BA~CA~CB~CB~CA~CA~CB~CA~CB~CB~CB~DB~DB~CB~DB~CB~CB~DB~DB~CB~DB~DB~CB~CB~CB~DB~DB~DB~DB~EC~DB~DB~EC~DB~EC~DB~DB~DB~EC~EC~EC~EC~EC~DB~EC~EC~EC~DB~EC~EC~EC~EC~EC~DB~EC~EC~EC~EC~EC~EC~ED~ED~EC~EC~ED~EC~EC~EC~EC~EC~ED~ED~FD~ED~EC~ED~FD~ED~ED~ED~FD~FD~FD~FE~FE~FD~FE~FE~FE~FD~FE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~HF~HF~HF~HF~HF~GE~HF~GE~HF~GE~HF~HF~GE~GE~HF~GE~GE~GE~GE~GF~GE~HF~GF~HF~HF~HG~GF~HF~HF~GF~HF~HF~HF~HG~IG~IG~HG~HG~HG~IH~HG~IG~HG~HG~HG~HF~HF~IG~IH~IH~HG~IG~HG~IG~JH~HG~IG~HG~IG~IG~IG~IG~IG~IG~JH~IG~IG~IG~IG~IG~IG~JH~JH~IG~JH~IG~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~IG~JH~IG~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~KI~JH~JH~JH~KI~JH~JH~KI~JH~JH~JH~KI~KI~JH~JH~JH~JH~JH~JH~JH~KI~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~KI~KI~KI~KI~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~JH~JH~KH~JH~JH~JH~JH~JH~JH~IG~JH~JH~JH~IG~IG~IG~IG~IG~JH~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~IG~JH~IG~IG~IG~IG~IG~HG~HG~HG~IG~HG~IG~IG~HG~HG~HG~IG~HG~HF~HF~HG~HF~HG~HG~HG~HG~HF~HF~HF~HF~GF~HG~GF~GF~HF~IG~GF~GF~HG~HF~GF~GE~GF~GF~GF~GF~HF~GF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FD~GE~GE~HF~GE~GE~GE~GE~GE~GE~FD~GE~GE~GE~GE~GE~FD~GE~GE~GE~GE~FD~FD~EC~EC~FD~FD~EC~FD~FD~FD~FD~FD~FD~EC~EC~EC~FD~FD~ED~EC~FD~FD~EC~EC~EC~FD~FD~FD~ED~EC~EC~EC~EC~EC~EC~EC~EC~EC~ED~EC~DC~DC~EC~EC~EC~DC~DC~DB~DB~EC~EC~EC~EC~DB~DB~DB~EC~EC~EC~EC~DB~EC~DB~DB~DB~DB~DB~CB~DB~DB~DB~CB~DB~CA~DB~DB~DB~DB~DB~DB~DB~CA~DB~DB~CA~CA~CA~CA~CA~CA~DB~DB~CA~CA~CA~CB~CA~CA~CA~BA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~BA~CA~BA~CA~BA~CA~CA~CA~BA~BA~B@~BA~BA~BA~CA~CA~CA~CA~B@~CA~CA~B@~B@~B@~B@~B@~BA~B@~BA~BA~BA~B@~BA~BA~B@~BA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~B@~B@~A?~B@~A?~B@~A?~A?~A?~A?~B@~B@~A?~A?~A?~B@~B@~A?~A?~A?~B@~A?~A?~A?~A?~A?~A?~A?~A?~@?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~A?~A?~@>~@>~@>~A?~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~A?~@>~@>~A?~A?~@>~A?~@>~@>~@>~@>~A?~@>~@>~@>~@>~A?~A?~@>~@>~@>~@>~A?~A?~@>~@>~A?~A?~A?~@>~@>~@>~@>~@>~@>~A?~A?~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~A?~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~A?~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@?~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~A?~@>~@>~@>~@?~A?~@?~@?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~A?~@>~A?~A?~A?~A?~A?~@>~@>~A?~@>~A?~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~A?~@>~@>~@>~A?~A?~@>~A?~A?~A?~@>~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~@>~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~@>~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~A?~B@~A?~B@~A?~A?~A?~A?~A?~A?~B@~A?~A?~A?~A@~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~A?~B@~A?~B@~B@~B@~B@~B@~B@~B@~B@~B?~A?~B@~B@~B@~B@~B@~BA~BA~BA~BA~B@~B@~BA~B@~B@~B@~B@~B@~B@~B@~B@~BA~B@~B@~B@~B@~BA~BA~BA~CA~CA~BA~BA~BA~BA~CA~BA~B@~CA~CA~BA~BA~CB~BA~BA~CB~CA~CB~CA~CA~CA~CA~CA~CB~CB~CA~CA~DB~CA~CA~CA~CA~CB~DB~CB~CB~DB~DB~DB~DB~DB~DB~DB~EC~DB~DB~DB~DC~DB~EC~EC~DB~EC~EC~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~EC~FD~FD~FD~FD~ED~EC~ED~FD~FD~FD~FD~FD~FE~FE~FD~GE~FD~FD~ED~FE~FD~GE~FD~FD~GE~GE~GE~GE~GE~FD~GE~FD~GE~GE~GE~GE~GE~GE~GE~GE~GE~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GF~GF~GF~GE~GE~GE~HF~GF~GF~GF~GF~GE~GF~GF~HG~HG~HG~HG~HG~HG~HG~HG~HG~HG~HG~HG~HG~IG~HG~HG~IG~IG~JH~IG~IG~IG~IG~IG~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~KI~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~JH~JH~JH~KI~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~KI~JH~JH~JH~KI~JH~JH~JH~JH~JH~KI~JH~JH~JH~JH~JH~KI~JH~JH~JH~JH~KI~JH~JH~KI~KI~KI~JH~JH~KI~KI~JH~KI~JH~JH~KI~JH~JH~JH~KI~JH~JH~JH~JH~KI~JH~JH~JH~KI~JH~JH~JH~KI~JH~JH~JH~JH~JH~JH~JH~JH~KI~JH~JH~JH~JH~JH~KH~KH~KH~KH~KH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~JH~JG~IG~JG~JH~JH~JH~JH~JH~IG~JH~IG~IG~JH~JH~IG~JH~JH~IG~IG~JH~JH~IG~IG~IG~JH~IG~IG~IG~IG~IG~JH~IG~IG~IG~IG~HF~IG~IG~IG~IG~IG~HF~HF~IG~IG~IG~IG~IG~HF~GF~HF~HF~HG~HF~GF~HG~HG~HF~GF~GE~GF~GE~GF~GF~GF~HF~GF~GF~HF~GF~FE~GF~GF~GF~GE~GE~GE~GF~GF~GE~GE~GE~GE~GE~GE~GF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FD~GE~GE~FD~FD~GE~FD~GE~FD~FD~FD~FD~GE~FD~FD~FD~FD~FD~FC~FD~FD~FD~FC~EC~EC~EC~EC~EC~EC~FD~ED~EC~EC~EC~FD~FD~DC~DB~EC~ED~EC~EC~EC~EC~DC~EC~EC~DC~DB~DB~DB~EC~EC~DB~DC~EC~EC~DC~DC~EC~DC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~DB~CA~CA~DB~DB~CA~CA~CA~DB~DB~DB~CA~CA~CA~DB~DB~DB~DB~CA~CA~DB~CA~CA~DB~DB~CA~CA~CA~BA~CA~CA~CA~CA~BA~CA~BA~BA~CA~BA~CA~BA~CA~BA~BA~BA~BA~B@~BA~B@~B@~BA~BA~B@~CA~BA~BA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~B@~B@~B@~B@~B@~A@~B@~B@~B@~B@~B@~B@~B@~B@~A?~A?~B@~B@~A@~A?~B@~A?~B@~A?~A@~A?~A?~B@~B@~A@~A@~A@~A?~A?~A@~A@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~A?~A@~A?~@?~@?~A?~A?~A@~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A>~A>~A?~A?~A?~@>~A?~A?~@>~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~@>~@?~@?~@?~@>~A?~@?~@?~A?~A?~A?~A?~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@?~@>~@>~@>~@>~@?~@?~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@?~@>~@?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~A>~A>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~A?~A?~@>~A?~A?~@>~A?~@>~A?~A?~@>~@>~@?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@?~@?~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@?~@>~@>~@>~@>~@>~@>~@>~@?~@>~@>~@>~@>~@>~A?~@>~@>~@>~A?~@>~A?~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@?~@>~@>~@>~A?~@>~A?~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~A?~A?~@>~@>~A?~@>~@>~A?~@>~@>~A?~A?~@?~@?~@>~A?~A?~A?~@?~A?~A?~@>~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@?~A?~A?~A?~A?~A?~A?~@>~@>~A?~A?~B@~A?~A?~A?~A?~A?~A?~B@~A?~B@~B@~B@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~B@~A?~B@~A?~A?~A?~A?~A?~B?~B@~B@~B@~B@~B@~B@~B@~A?~B@~B@~B@~B?~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~C@~CA~C@~CA~B@~CA~CA~C@~B@~CA~CA~B@~B@~CA~CA~CA~CA~CA~CA~CA~CA~DB~CA~C@~CA~CA~CA~CA~CA~CA~DB~DB~DB~CA~CA~DB~CA~DB~CA~CA~DB~CB~DB~CB~DB~DB~DB~DB~CB~DB~DB~DB~DB~EC~DB~DB~DB~EC~EC~EC~EC~EC~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~EC~EC~EC~EC~EC~EC~ED~ED~ED~ED~ED~ED~FD~ED~EC~ED~ED~FD~FD~FD~FD~FD~GE~FD~ED~FD~FE~FE~GE~FD~GE~GE~GE~FD~GE~GE~FD~GE~GE~FD~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~HF~HF~GE~GE~HF~GE~HF~HF~HF~GE~GF~HF~HF~HF~HG~HF~HF~HF~HF~HG~HG~HG~HG~HG~HG~HG~IG~IG~HG~IG~HG~HG~HG~IG~IG~IG~IG~IG~IG~HG~IG~IG~IG~JH~IG~IG~IG~IG~IG~IG~HF~JH~IG~IG~IG~IG~IG~IG~IG~JH~IG~JH~JH~JH~IG~JH~JH~JH~JH~JH~JH~JH~IG~JH~JH~JH~JH~JH~JH~JH~JH~JH~KI~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~KI~JH~JH~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~JH~KI~JH~JH~KI~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~KI~KI~KI~KI~KI~KI~KI~KI~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~JH~KI~KI~KI~KI~JH~KI~JH~JH~JH~JH~KI~JH~KI~KI~KI~KI~JH~KI~KI~KI~KI~KI~KI~KI~JH~JH~JH~JH~JH~KI~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~IG~IG~IG~JH~JH~JH~IG~JH~JH~JH~JH~JH~IG~IG~JH~IG~JH~JH~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~HF~IG~HF~IG~HF~HF~HF~IG~IG~IG~IG~HF~IG~HF~HF~HF~IG~HF~HF~HF~HF~HF~IG~HF~HF~HF~GF~GF~GF~GE~GE~GE~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FD~GE~FD~GE~FD~FD~FD~GE~FD~ED~ED~FD~FD~EC~FD~FD~FD~FD~FD~ED~ED~FD~FD~ED~ED~ED~EC~ED~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~DB~DB~EC~DC~EC~EC~DC~DB~EC~EC~EC~EC~DB~EC~DB~EC~DB~DB~DB~DB~DB~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~CB~DB~DB~CB~DB~CB~CB~CB~CB~CB~CB~CA~BA~CA~BA~CB~CB~CB~CA~CA~CA~BA~CA~CA~CA~CA~BA~CA~CA~CA~CA~BA~CA~B@~CA~BA~B@~B@~BA~BA~B@~BA~B@~B@~B@~BA~CA~B@~B@~B@~A?~B@~B@~B@~B@~B@~B@~B@~B@~A@~B@~B@~A@~A@~B@~A@~A?~A@~A?~B@~B@~A?~A?~B@~B@~B@~B@~A?~A?~B@~A?~A?~A?~A?~B@~A?~A?~A?~A?~B@~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~@>~@>~A?~A?~A?~A?~A?~B@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~@>~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~@>~A?~@?~@?~@?~A?~@>~A>~@>~@>~@>~@>~?=~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~?=~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@?~@?~@?~@>~@?~@>~@?~@>~@>~@>~@>~@?~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@?~@>~@>~@>~A?~@>~A?~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~@>~?=~@>~?=~@>~@>~@>~?=~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~A>~@>~@>~A>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~?=~?=~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~A?~@>~A?~A?~A?~@>~@?~A?~A?~A?~A?~@>~@>~A?~@>~@>~@>~A?~A?~@>~@>~@>~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~A?~A@~A?~A@~A?~B@~B@~A?~A?~A?~A?~A?~B@~B@~A?~A?~B@~B@~B@~A@~A@~A?~A@~A@~BA~A@~A?~A@~B@~A@~A@~B@~A@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~B@~B@~BA~BA~B@~CA~CA~CA~CA~CA~B@~BA~CA~CA~CB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~B@~CA~CA~CA~CA~CA~CA~CB~DB~DB~DB~CA~DB~DB~DB~CA~DB~DB~DB~DB~DB~DB~DB~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~ED~ED~DC~ED~FD~EC~EC~EC~EC~EC~EC~FD~EC~FD~FD~FD~FD~FD~FD~FD~FD~ED~FD~FE~GE~FE~GE~FE~GE~FE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~HF~HF~HF~GE~GE~HF~HF~GE~HF~HF~HF~HF~HF~HG~HG~HF~HF~IG~HF~HF~IG~IG~HF~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~IG~IG~HF~IG~IG~IG~IG~IG~IG~IG~IG~HF~JH~JH~IG~JH~IG~IG~JH~JH~IG~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JG~KH~JH~JH~JG~JH~JH~JH~JH~JH~JH~KI~JH~JH~JH~JH~KI~KI~KI~KI~KI~JH~JH~KI~KI~JH~JH~KI~KI~KI~KI~KI~JH~KI~JH~KI~JH~KI~KI~KI~KI~KI~KI~KI~KI~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~KI~KI~KI~KI~KI~LJ~KI~LJ~LJ~LJ~LJ~LJ~KI~KI~KI~KI~KI~LJ~KI~KI~KI~LJ~LJ~KI~KI~LJ~LJ~KI~KI~KI~KI~KI~KI~LJ~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~KI~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~KI~KI~KI~KI~KI~KI~KI~JH~KI~JH~KI~KI~JH~JH~KI~KI~JH~JH~KI~KI~KI~KI~KI~KI~JH~JH~JH~KI~JH~JH~JH~IG~IG~JH~JH~IG~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~IG~IG~JH~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~HF~IG~IG~IG~IG~HF~HF~IG~IG~IF~IG~HF~IG~IG~HF~IG~IG~HF~IG~IG~HF~GF~HF~HG~HF~HF~HF~HF~GF~HF~HF~GF~GE~HF~GE~GF~HF~GF~GF~HF~GF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FD~GE~FD~FD~FD~GE~GE~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~EC~FD~FD~FD~FD~ED~ED~ED~EC~ED~FD~ED~EC~EC~FD~FD~EC~FC~EC~EC~FC~EC~EC~EC~EC~DB~FD~EC~DB~DB~EC~DB~EC~EC~EC~DB~DB~DB~EC~DB~DB~DB~EC~DB~DA~DB~CA~DB~DB~DB~DB~DB~DB~DB~CA~DA~DA~DB~DA~DB~DB~CA~DA~CA~CA~CA~CA~CA~DB~DB~CA~CA~CA~CA~CA~CA~CA~DB~CA~CA~CA~CA~CA~BA~CA~CA~BA~BA~BA~CA~CA~CA~B@~CA~CA~B@~B@~CA~B@~B@~B@~B@~B@~B@~B@~A@~B@~B@~A@~A@~B@~B@~A?~B@~B@~B@~B?~B?~B@~B@~B@~B@~B@~B@~B@~A?~A?~A?~A@~A?~A@~A@~A@~A?~A?~A?~B@~A?~A?~A?~A?~A?~B?~A?~A?~B@~A?~A?~A?~B@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~A?~A?~A?~A?~@>~@>~@>~A?~A?~A?~A?~A?~A?~A?~@>~@>~@>~@>~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~@>~@>~@>~@>~A?~A?~A?~@>~A?~A?~A?~A?~@>~@>~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~?=~@=~@=~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@=~A>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~A>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~?=~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~@>~@>~@>~?=~?=~?=~?=~@>~?=~@>~@>~@>~@>~?=~@>~@>~@>~?=~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@?~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~A?~@>~@>~@>~@>~@>~@?~@>~@>~@>~@?~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~A?~A?~@>~@>~A?~@>~A?~A?~A?~A?~A?~A?~A?~@>~@>~@?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~B@~A?~A?~A?~A?~A?~A?~B@~B@~B@~B@~B@~A?~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~B@~B@~B@~B?~B@~B@~B@~B@~B@~B@~B@~B@~BA~B@~B@~B@~BA~BA~BA~B@~BA~B@~BA~CA~BA~CA~CA~CA~CA~CA~CB~CB~CB~CB~CB~CA~CB~CB~CB~CB~DB~CB~CA~DB~DB~DB~DB~DB~DB~EC~DB~DB~DB~DB~DB~DB~DB~DB~EC~DB~DB~EC~DB~EC~DB~EC~EC~EC~EC~EC~ED~EC~EC~EC~EC~EC~EC~EC~ED~ED~ED~EC~EC~ED~ED~FD~FC~EC~FD~FD~FD~FD~FD~FD~FD~FE~FE~FD~FD~FD~FD~FD~GE~FD~GE~GE~GE~GE~FD~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FD~FD~GE~GE~GE~GE~HF~HF~HF~HF~HF~HF~HF~HF~GE~GF~HF~GF~HF~HG~HF~IG~HF~IG~HF~HF~IG~IG~IG~IG~IG~IG~IG~HF~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~IG~IG~JH~JH~JH~IG~IG~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~JH~JH~JH~JH~JH~IG~JH~JH~JH~JH~JH~KI~JH~JH~JH~JH~JH~JH~JH~JH~JH~KI~KI~KI~JH~JH~JH~KI~JH~KI~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~LJ~KI~KI~KI~KI~LJ~LJ~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~KI~LJ~LJ~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~LJ~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~LJ~KI~LJ~KI~LJ~LJ~KI~LJ~LJ~LJ~LJ~LJ~KI~KI~KI~KI~KI~KI~LJ~KI~LJ~KI~LJ~LJ~LJ~KI~KI~KI~LJ~KI~KI~KI~KI~KI~KI~KI~LJ~KI~KI~KI~KI~KI~JH~JH~KI~JH~JH~JH~KI~KI~JH~JH~KI~JH~KI~JH~KI~KI~KI~JH~JH~JH~JH~JH~JH~JH~IG~JH~IG~IG~JH~JH~JH~JH~IG~HF~JH~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~HF~HF~HF~IG~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~GF~HF~GF~GF~HF~GE~GF~FE~GE~HF~GF~GE~GE~FE~FE~GE~GE~GE~FD~FD~FD~GE~GE~FD~GE~FD~GE~FD~FD~FD~FD~FD~FD~FD~FD~ED~ED~ED~ED~FD~FD~ED~ED~EC~EC~EC~ED~EC~ED~EC~ED~ED~EC~EC~EC~EC~EC~EC~EC~EC~EC~EB~DB~EC~DB~EB~EC~DB~DB~DB~DC~DB~DB~DB~DB~DC~DB~DC~DB~DB~DB~DB~DB~DB~DB~CA~DB~DB~DA~CA~DB~DB~CB~CA~CB~CA~DB~CA~CA~DB~CA~CA~CA~DB~CA~CA~CA~CA~CA~CA~CA~CA~CA~B@~CA~B@~CA~CA~CA~B@~CA~B@~B@~C@~B@~C@~B@~C@~CA~CA~CA~CA~B@~B@~C@~CA~C@~C@~C@~B@~C@~C@~B@~B@~B@~B@~B@~B@~B@~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A@~A@~A@~A@~A?~B@~B@~B@~A@~A@~B@~B@~B@~B@~A@~B@~A@~A@~A?~A?~A@~A?~A@~A@~A?~A?~A?~A?~A?~A@~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@?~A?~@>~A?~A?~A?~A?~@>~A?~@>~@>~A?~A?~A?~@>~@>~@>~@>~A?~A?~A?~@>~@>~@>~@>~@>~A?~@>~A>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@?~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~@>~@>~@>~@>~@>~@=~@=~@=~@=~@=~@=~@=~@=~@>~@=~?=~@=~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~?=~?=~?=~?=~?=~?=~?=~@>~@>~@>~@>~?=~?=~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~?=~@>~@>~@>~?=~?=~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~?=~?=~?=~?=~?=~?=~@>~@>~@>~?=~@>~@>~@>~@>~@>~?=~@>~@>~?=~?=~@>~@>~?=~?=~?=~?=~@>~?=~@>~@>~?=~?=~@>~@>~@>~?=~?=~@>~@>~@>~@>~@>~@>~@>~?=~?=~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~A?~A?~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~A?~@?~@?~@>~A?~A?~@>~@>~@>~A?~A?~A?~A?~@>~@>~A?~@>~@>~A?~@>~A?~@>~A?~A?~A?~@>~A?~A?~@>~A?~A?~A?~@>~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~B@~B@~A?~A?~A@~A?~A?~B@~A?~A?~B?~A?~B@~B@~A?~B@~B@~B?~B@~B?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~BA~BA~B@~B@~B@~BA~B@~B@~B@~BA~BA~B@~B@~B@~BA~BA~BA~BA~BA~BA~BA~CA~B@~CA~B@~CA~CA~B@~B@~C@~CA~CA~CA~CA~DB~CA~CA~CA~CA~DB~DB~CA~CB~DB~CB~DB~DB~CA~CB~DB~DB~DB~DB~DB~DB~EC~DB~DB~DB~DB~DB~DC~DC~DB~EC~EC~EC~DB~EC~DB~EC~DB~EC~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~FD~FD~EC~FC~FD~FD~FD~FD~FD~FD~FD~FD~FD~GE~FE~FD~GE~FD~FD~GE~FD~GE~FD~FD~GD~FD~FD~FD~GD~GD~GD~HE~HE~HE~GE~GE~HF~GE~GE~HF~HF~GF~GF~GF~HF~HF~HF~GF~GF~GF~GF~HF~HF~HF~HF~HF~HF~IG~HF~HF~HF~IG~IG~IG~HF~HF~IG~IG~IG~IG~IG~IG~IG~IG~JG~IG~IG~IG~JG~JH~IG~IG~JH~JH~JH~IG~JH~JH~JH~JH~IG~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~KI~KI~JH~IG~JH~KI~JH~JH~JH~JH~JH~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~LJ~KI~LJ~LJ~LJ~LJ~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~LJ~LJ~KI~LJ~LJ~KI~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~LJ~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~KI~LJ~LJ~LJ~LJ~LJ~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~JH~KI~JH~KI~KI~JH~JH~JH~JH~JH~JH~KI~KI~JH~KI~KI~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~HF~IG~IG~HF~IG~HF~HF~IG~IG~IG~HF~HF~HF~IG~IG~IG~HF~IG~HF~IG~HF~HF~HF~HG~HF~HF~HF~HF~HF~GE~HF~GE~HF~GE~HF~GF~GF~FE~GF~GF~GE~GE~FE~FD~GE~GE~GE~GE~FD~GE~GE~GE~FD~GE~GE~FD~FD~FD~FD~FD~FD~FD~FD~FD~ED~FD~FD~ED~ED~FD~FD~FD~EC~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DC~EC~DC~DC~DC~DB~EC~DC~DC~DC~DC~DC~DB~DB~DC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CA~CA~DB~CA~DB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~B@~CA~CA~CA~CA~CA~CA~CA~B@~CA~CA~CA~CA~CA~B@~CA~B@~B@~B@~CA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A@~B@~BA~B@~B@~B@~B@~A?~B@~B@~B@~B@~B@~B@~B@~A?~B@~B@~A?~B@~B?~A?~B@~A?~A?~A@~A?~A@~A@~A@~A@~A@~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~@>~A?~A?~A?~A>~@>~@>~A>~@>~@>~A>~A>~@>~@>~A?~@>~@>~A?~A?~A?~@>~A?~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~A?~@>~A?~A?~@>~@>~@>~@>~A?~@>~@>~@>~@>~A?~@>~A?~A?~@>~@>~@>~@>~@>~A?~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~?=~?=~@>~?=~@>~@>~@>~@>~@>~@>~?=~@>~@>~?=~?=~@=~@=~@>~@=~@>~@>~@>~@>~@>~@>~@>~?=~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~@>~@>~@>~@=~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~?=~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~?=~?=~@>~@=~?=~@=~@>~@=~@>~?=~@>~@=~@=~@>~?>~@>~?>~?=~?>~@>~@>~@>~@>~?=~?=~?=~?=~@>~@=~@>~@=~?=~@=~@=~@=~?=~?=~?=~?=~@>~@=~?=~?=~@>~?=~?=~?=~?=~?=~?=~@>~@=~?=~@=~?=~?=~?=~?=~@>~@>~@>~@>~@>~?=~@>~@>~@>~?=~?=~@>~@=~@=~?=~?=~@=~?=~?=~@>~@>~?=~@>~@>~?=~?=~@>~?=~?=~?=~@>~@>~?=~?=~@>~@>~@>~@>~?=~?=~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~?=~@>~?=~@>~@>~@>~?=~?=~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~A>~@>~@>~@>~@>~@>~@>~?=~?=~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~A?~A?~@>~@>~@>~A?~A?~@>~A?~@>~A?~@>~@>~@>~A?~A?~@>~@>~A?~@>~@>~A?~@>~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~A?~A?~A?~A@~A?~A?~B@~A?~A?~A?~A?~B@~A?~A?~A?~B?~B?~A?~A?~A?~B@~B@~B@~B@~B@~B@~A@~BA~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~B@~B@~BA~BA~BA~BA~BA~BA~BA~A@~A@~BA~B@~B@~B@~B@~CA~B@~CA~BA~B@~B@~B@~CA~CA~CA~CA~CA~DB~CA~BA~CA~CA~CA~CA~CA~CA~DB~CA~DB~DB~DB~DB~CB~CA~DB~DB~DB~EC~DB~DB~DB~DB~EC~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~DB~DC~EC~EC~EC~EC~DC~EC~ED~FD~ED~ED~FD~FD~FD~FD~FD~FD~EC~FD~FD~FD~FD~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FD~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GF~GF~GE~GF~GF~GE~GF~GF~HF~HF~HF~HF~HF~GE~HF~IG~HF~HF~IG~HF~HG~IG~HF~IG~IG~IG~IG~IG~IG~IG~HF~HF~IG~IG~IG~IG~IG~IG~JH~IG~IG~JG~JH~JG~JH~IG~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~KI~JH~JH~KI~KI~KI~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~KI~KI~KI~KI~KI~LJ~KI~LJ~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~LJ~LJ~LJ~LJ~MK~MK~MK~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~LJ~LJ~LJ~LJ~KI~LJ~LJ~KI~LJ~KI~LJ~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~JH~JH~JH~JH~JH~JH~JG~JG~JH~IG~JH~JG~JH~JH~JH~IG~IG~IG~IG~IG~IG~JH~IG~IG~JH~IG~IG~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~HF~IG~IG~HF~HF~IF~IF~IG~IF~HF~HF~IG~HF~HF~IG~HF~IG~HF~HF~HF~HF~HF~HF~GE~HF~GF~GF~GF~HF~GF~GF~GE~GF~GF~GE~GE~GE~GE~GE~FD~GE~FD~GE~GE~GE~GE~GE~FD~GE~GE~FD~GE~FD~FD~FD~GE~FD~FD~FD~GE~FD~EC~EC~FD~FD~FD~ED~ED~EC~ED~EC~EC~EC~EC~EC~EC~EC~EC~FC~EC~EB~EB~EC~EC~EC~DB~EC~DB~DB~DB~DB~EC~EC~EC~DB~CA~DB~DB~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~CA~CA~CA~DB~CA~DB~CA~DB~CA~DB~CA~CA~CA~DB~CA~CA~CA~DB~CA~CA~CA~CA~CA~CA~C@~C@~C@~CA~C@~C@~CA~CA~CA~B@~B@~B@~B@~B@~B@~B@~CA~CA~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~B@~A?~B@~B?~B@~B?~B?~B@~B@~B@~B@~B@~A?~A?~B@~B?~B?~B@~B@~B@~B@~B@~A?~A?~B@~A?~B@~A?~A?~B?~B?~B?~A?~B?~B?~B?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~@>~A?~A?~@>~A?~A?~A?~A?~A?~@>~@>~@>~@>~A?~@>~@>~@>~@>~A?~A?~A?~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@?~@?~@>~@?~@?~@>~@?~@?~@?~@>~@?~@>~A?~@?~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~@>~@>~@>~@>~?=~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~@=~@>~?=~@>~?=~A>~@=~?=~?=~@>~@=~?=~@>~?=~@>~?=~@>~A>~@>~@>~?=~@>~@>~?=~?=~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~?=~@>~?=~A>~@=~@>~@=~@=~@>~@>~?=~@=~@=~@=~@=~?=~@=~@=~@=~@>~@=~?=~@=~?=~@>~@=~@=~@=~@=~@=~@>~@>~?=~@>~?=~@=~@=~@>~?=~@>~@>~?=~@>~@=~?=~@=~@>~@>~@>~?=~?=~?=~?=~@=~?=~@>~?=~?=~@>~?=~@>~@>~@>~?=~?=~@>~@>~@>~?>~?=~?=~@>~@>~?=~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~?>~@>~@>~@>~?>~@>~@>~@>~?=~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~?=~?=~?=~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~A?~A?~A?~A?~@>~A?~@>~@>~A?~@>~A?~A?~A?~@>~A?~A?~A?~@>~A?~@>~@>~A?~@>~@>~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~B?~A?~B?~A?~B?~B@~A?~A?~A?~A?~B@~B@~A?~B@~A?~B@~A?~B@~B@~A@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~B@~B@~B@~B@~B@~BA~BA~BA~BA~BA~BA~B@~BA~BA~BA~BA~BA~CA~BA~CA~CA~CA~BA~CA~BA~CA~CA~DB~CA~CA~DB~DB~DB~CA~DB~DB~CA~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~EC~DC~EC~EC~EC~ED~EC~EC~FC~EC~EC~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~GE~GE~GE~GE~GE~FD~FD~GE~FD~GE~GE~FD~FD~GE~GF~GF~GE~GE~GE~GE~GE~HF~GE~GE~GF~GF~GE~GF~GF~HF~IG~HF~HF~HF~IG~IG~HF~HF~HF~IG~HF~HF~HF~IG~IG~IG~HF~HF~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~JH~IG~IG~JH~JH~JH~JH~KI~JH~JH~JH~JH~JH~JH~KI~JH~KI~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~KI~KI~KI~KI~KI~KI~LJ~KI~KI~KI~LJ~KI~KI~LJ~LJ~LJ~LI~LJ~LJ~KI~KI~KI~KI~KI~KI~KI~KI~LI~LJ~LJ~LJ~KI~LJ~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~MK~MK~LJ~LJ~MK~LJ~LJ~LJ~LJ~MK~LJ~MK~MK~LJ~MK~MK~MK~LJ~LJ~LJ~LJ~LJ~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~LJ~MK~MK~MK~LJ~MK~LJ~MK~LJ~LJ~LJ~LJ~MK~LJ~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~KI~LJ~LJ~LJ~LJ~KI~LJ~KI~KI~KI~KI~LJ~KI~KI~JH~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~JH~KI~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~JH~JH~IG~IG~IG~IG~IG~IG~JH~IG~IG~IG~JH~JH~IG~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~HF~HF~IG~HF~HF~HF~HF~IG~HF~HF~HF~HF~HF~HF~HF~GF~FE~GF~GF~GF~GE~GF~GF~GE~GE~GE~GE~GE~GE~GD~GE~GE~GE~GD~FD~FD~FD~GE~GE~GE~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~ED~ED~EC~EC~EC~EC~EC~FD~EC~EC~EC~FC~EC~EC~EC~EC~EC~EC~EC~DB~EC~EC~EC~EC~EC~EC~EC~EC~DB~EC~DB~DB~EC~DB~DB~DB~DB~EB~DB~DB~DB~DA~DB~DB~DB~DB~DB~DB~CA~CA~CA~CA~DB~CA~CA~CA~CA~CA~CA~CA~CA~CA~DB~CA~CA~CA~CA~CA~CA~B@~CA~CA~C@~C@~C@~C@~C@~CA~CA~CA~C@~C@~B@~CA~C@~C@~B@~B@~B@~B@~C@~B@~C@~B@~B@~B@~B@~B@~B@~B@~B@~A?~B@~B@~B@~B@~A?~A?~A?~B@~A?~A?~B@~B@~B@~A?~A?~B@~A?~B@~B@~A@~A?~A?~A?~A?~B@~B?~A?~A?~A?~A?~A?~A?~A?~A>~A?~A>~A?~A>~A?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~@>~A?~@>~@>~A>~A?~@>~@>~@>~A?~@>~@>~A?~@>~A>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~A>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@=~?=~@>~@>~@=~@=~@=~@>~@=~@=~@=~@>~@>~@=~@=~@=~@>~@>~@>~@>~A>~@>~?=~@>~@>~@=~@>~@>~@>~@=~@=~@>~@>~@>~@>~@>~@>~?=~?=~@>~@>~@>~@>~@>~@=~@>~@>~@>~A>~?=~@>~@>~?=~@>~@>~@>~?=~@>~?=~@>~@>~@>~?=~@>~@>~@>~@>~?=~?=~@>~@>~?=~?=~@=~@=~@>~@=~@=~@=~@>~@>~@=~?=~?=~@>~@=~?=~?=~@>~?=~@>~@=~@>~?=~@=~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~?=~@>~@>~?=~@>~@>~@>~?=~?=~@>~@>~?=~?=~@>~?=~?=~@>~@>~?=~@>~?=~@>~?=~@>~@>~@>~@>~@=~@=~@=~@=~?=~?=~?=~@=~@=~?=~?=~?=~@>~@>~?=~@>~?=~?=~?=~@>~@>~?=~@>~@>~?=~?=~@>~@>~@>~?=~@>~@=~?=~@>~?=~@>~@>~@>~@>~?=~?=~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~@>~?=~@=~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@?~@?~A?~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~A?~A?~A?~@>~A?~@>~@>~@>~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~B@~A?~A?~B@~A@~B@~B@~A?~B@~B@~A@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A@~B@~B@~B@~CA~BA~BA~B@~CA~BA~BA~BA~BA~BA~CB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~DB~CA~DB~CA~CA~DB~DB~DB~DB~DB~DB~DB~EC~DB~DB~DB~EC~DB~DB~EC~EC~EC~EC~EC~EC~EB~EC~EB~EB~FD~EC~EC~EC~ED~FD~EC~EC~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~GE~FD~FD~GE~FD~FD~FD~GE~GE~GE~FE~FD~GE~GE~FE~GE~FE~GE~FE~FE~GE~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~IG~IG~IG~IG~HF~IG~IG~IG~IF~IG~IG~IG~IG~IG~IG~IG~JH~JH~IG~IG~JH~IG~JH~JH~JH~IG~JH~JH~JH~IG~JH~JH~JH~JH~JH~JH~KI~JH~KI~JH~JH~KI~JH~KI~JH~KI~KI~KI~LJ~LJ~LJ~KI~KI~KI~LJ~LJ~LJ~LJ~KI~KI~LJ~LJ~LJ~LJ~KI~KI~KI~LJ~LJ~LJ~KI~LJ~KI~LJ~LJ~MJ~LJ~LJ~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~MK~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~MK~LJ~LJ~MK~LJ~LJ~MK~LJ~LJ~MK~MK~MK~LJ~MK~LJ~LJ~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~LJ~LJ~MK~MK~MK~MK~NL~MK~MK~LJ~LJ~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~LJ~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~LJ~LJ~LJ~LJ~LJ~KI~LJ~LJ~LJ~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~JH~KI~KI~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~JH~JH~JH~JG~JH~JG~JG~JG~JG~JG~JG~IG~JG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~HF~IG~IG~IG~IG~HF~HF~HF~HF~HF~HF~HF~IG~HF~HF~HF~HF~GF~GE~GF~HF~GF~GE~GF~GE~GE~GE~GF~GE~GF~GE~GE~GE~GE~GE~GE~HF~FD~FD~GE~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~EC~FD~FD~EC~FD~EC~FD~EC~EC~EC~EC~EC~EC~EC~FD~EC~EC~EC~EB~EC~EC~EB~EB~EB~EC~DB~EC~EC~DB~DB~DB~EC~DB~EC~DB~CA~DB~DB~DB~DB~DB~DB~CA~DB~DB~DB~DB~DB~DB~DB~DB~DB~CA~CA~CA~CB~CA~CA~CA~DB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~B@~CA~CA~B@~CA~CA~CA~BA~B@~B@~B@~B@~B@~B@~CA~B@~BA~B@~B@~B@~B?~B@~C@~CA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B?~B@~B?~B?~A?~A?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A>~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A?~A?~A>~A?~A?~A?~A>~@>~A?~A?~@>~A?~@>~A?~A?~@>~@>~@>~A?~@>~@>~@>~A?~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~@>~@>~@>~@=~@=~@=~@>~@>~A>~@>~@>~@>~@>~@>~@>~A?~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~?=~@>~@>~@>~@>~@=~@=~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@=~A>~@>~@=~@=~@>~@>~@=~@>~?=~?=~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~?=~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~?=~@>~@>~@>~?=~?=~@=~A>~@>~@>~@>~@>~@=~@=~@=~@=~@>~@>~@>~@=~@>~@>~?=~@>~?=~?>~?=~?=~@>~?=~@>~@>~@>~?=~?=~@=~@=~@>~@>~?=~?=~?=~?=~?=~?=~@=~@>~?=~@>~?=~?=~?=~@>~@>~@=~@>~?=~@>~?=~@=~?=~@=~?=~?=~?=~?=~?=~?=~?=~@>~?=~?=~?=~?=~?=~@>~@>~?=~?=~?=~@=~?=~?=~@>~@>~@>~@>~@>~@=~?=~?=~@>~?=~@>~@>~?=~?=~@>~?=~?=~@>~?=~@>~?=~?=~?=~?=~?=~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~?=~?=~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~?=~@>~@>~?=~@>~?=~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~A?~A?~A?~A?~A?~@>~@>~A?~A?~A?~A?~A?~A>~A>~A>~A?~A?~A>~A>~@>~A?~A?~A?~A?~@>~A?~A?~A?~A?~B?~A?~B?~B?~A?~A?~A?~A?~B@~B@~A@~B@~B@~A?~A?~B@~B@~B@~B@~B?~B?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~BA~BA~B@~BA~BA~BA~BA~CA~CA~CA~CA~CA~B@~CA~CA~CA~CA~CA~CA~B@~CA~CA~DB~DB~CA~CA~CA~CA~DA~CA~DB~CA~DB~CA~CA~DB~CA~DA~DB~DB~DB~DB~DB~EB~EC~EC~EC~EC~EC~EC~EC~EC~EB~EB~EC~EC~EC~EC~EC~EC~FD~EC~FD~FD~FD~EC~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~GE~FE~GE~FE~FD~FE~FE~FE~FE~FE~GE~GF~FE~GF~GE~GE~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~IG~HF~IG~HF~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~IG~IG~IG~IG~IG~IG~IG~IG~JH~JH~IG~IG~IG~JH~JH~JH~JH~JH~JH~IG~JH~JH~JH~JH~JH~JH~JH~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~KI~KI~LJ~LJ~LJ~KI~KI~LJ~LJ~KI~LJ~LJ~LJ~LJ~LJ~KI~LJ~LJ~LJ~MJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~MK~MK~LJ~LJ~LJ~LJ~MK~MK~MK~MK~MK~LJ~LJ~MK~MK~MK~MK~LJ~LJ~LJ~MK~LJ~MK~NL~MK~NL~MK~MK~MK~MK~MK~MK~MK~MK~MK~LJ~MK~MK~MK~NL~MK~NL~MK~NL~MK~MK~LJ~NL~MK~MK~MK~NL~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~NL~MK~MK~MK~NL~MK~MK~MK~MK~MK~MK~NL~MK~MK~MK~MK~MK~MK~MK~MK~LJ~MK~MK~LJ~LJ~MK~MK~LJ~MK~MK~LJ~LJ~MK~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~LJ~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~KI~KI~KI~KI~KI~KI~KI~JH~JH~JH~JH~KI~KI~JH~JH~JH~IG~JH~JH~JH~IG~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~HF~HF~IG~IG~IG~HF~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~GE~HF~HF~HF~HF~GE~HF~GF~GF~GF~GF~GE~GE~GE~GF~GE~GE~GE~GD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~EC~FD~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EB~EC~EC~EC~EC~EC~EC~EC~DC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DA~DA~DB~DB~CA~CA~DB~DB~CA~CA~CA~CA~CA~DB~DB~CA~CA~DB~DB~CA~CA~CA~CA~CA~CA~B@~CA~CA~CA~CA~B@~B@~B@~CA~CA~CA~CA~B@~B@~B@~CA~CA~B@~B@~B@~B@~B@~BA~B@~B@~BA~B@~B@~BA~B@~B@~B@~B@~B@~B@~A?~B@~B@~B@~B@~B@~B@~B@~A?~A?~B@~B@~B@~B@~A?~A?~A?~A?~A?~B?~B?~A?~A?~A?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A?~A>~A?~A?~A?~A?~A?~@>~A>~A?~A?~A?~@>~A?~@?~A?~@>~A?~A?~A?~A?~A?~A?~@>~@>~@>~A?~@>~@>~A?~A?~@>~@>~@>~@>~@>~A?~A?~@>~@>~@>~A?~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~?=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~?=~@>~?=~@>~@>~@>~?=~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~?>~?=~@=~@=~@=~@=~@>~?=~@>~@>~@>~@>~?=~?=~@>~?=~@>~@>~@>~@>~?=~?=~?=~@>~@>~@>~@=~@>~@>~?=~@=~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@=~@=~@>~@>~@=~@=~@=~@=~@=~@=~@=~@=~@=~@>~@=~@>~@>~?=~@=~@=~@=~@=~@>~@>~@=~?=~?=~@>~@>~@?~@>~@>~@>~?=~@=~@>~?=~?=~@=~@=~@=~@=~@=~@>~?=~?=~@>~@>~@>~?=~@>~@=~@=~@=~?=~?=~@=~@=~?=~@>~A>~?=~?=~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~?=~?=~?=~?=~?=~?=~@=~@>~@=~@=~@=~@=~@=~@=~A>~?=~@=~?=~@=~?=~?=~@>~@=~?=~@=~@>~@=~@=~@>~@>~@>~@>~@>~@>~@>~@=~@=~@=~@=~@=~?=~@=~@=~A>~A>~@=~@>~@=~@=~@>~@=~@=~@>~A>~?=~?=~@>~@>~@>~@>~@>~@>~@>~@=~@=~?=~@>~@>~?=~@>~@=~@>~?=~?=~@>~@>~?=~@>~?=~@>~@>~@>~?=~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~?=~?=~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~?=~?=~@>~@>~@>~@=~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~A?~@>~A?~@>~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~A?~B@~B?~B?~A?~A?~A?~A?~A?~B@~B@~A?~B?~B?~B@~B@~B@~B@~B@~B@~B?~B?~B@~A@~B@~B@~B@~B@~B@~B@~B@~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~B@~CA~CA~CA~BA~CA~BA~CA~DB~CA~DB~CA~DB~CA~CA~CA~DB~CA~CA~DB~DB~DB~CA~DB~DB~DB~DB~DB~EC~DB~DB~DB~DB~EC~EC~EC~EC~EC~DB~EC~EC~EC~EC~EC~EC~EC~ED~EC~EC~FD~FD~FD~FD~FD~EC~EC~FD~FD~FD~FD~FD~FD~FD~GE~FE~FE~FD~GE~FD~FD~FD~FD~GE~GD~FD~GE~GE~GE~GE~GE~GE~GE~GE~GE~GF~HF~GE~HF~HF~IG~HF~HF~HF~HF~HF~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~IG~IG~IG~JH~JH~IG~JH~JH~IG~JH~JH~KI~KI~JH~KI~JH~KI~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~MK~MK~LJ~LJ~LJ~LJ~MK~MK~MK~MK~LJ~MK~LJ~MK~LJ~LJ~MK~MK~LJ~MK~MK~MK~MK~MK~MK~MK~MK~LJ~MK~MK~MK~NL~MK~MK~MK~MK~MK~MK~MK~MK~MK~NL~MK~MK~MK~MK~NL~NL~NL~NL~MK~MK~NL~NL~MK~MK~NL~MK~NL~NL~MK~NL~NL~MK~NL~MK~MK~NL~MK~NL~NL~NL~MK~MK~NL~MK~NL~NL~NL~MK~MK~NL~MK~MK~NL~MK~MK~NL~NL~NL~NL~MK~NL~NL~MK~NL~NL~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~NL~NL~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~LJ~MJ~LJ~LJ~MK~MK~MK~MK~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~KI~KI~KI~KI~KI~JH~KI~KI~JH~KI~JH~KI~JH~JH~JH~JH~JH~IG~JH~JH~JH~JH~IG~JH~JH~IG~JH~JH~IG~JH~JH~IG~IG~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~GE~GE~GE~GE~GE~GE~GE~GF~GE~GE~GE~GE~GD~GE~GE~GE~FD~GD~GD~GE~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FC~FC~FC~FC~EC~EC~EC~EC~FC~FD~EC~EC~ED~EC~DB~EC~EC~EB~EB~EB~EB~EB~EB~EB~DA~DB~DB~EB~DB~EC~DB~DB~DB~EC~DB~DB~DB~DB~DB~DA~DB~DB~DB~CA~DB~DB~CA~CA~CA~DB~CA~CA~CA~CA~DB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~B@~B@~CA~CA~CA~CA~CA~B@~B@~B@~B@~CA~B@~CA~B@~B@~CA~B@~B@~B@~A@~B@~B@~B@~B@~B@~B?~B?~B@~B@~B@~B?~B@~B?~B?~B?~B?~B?~B?~B?~B@~A?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~@?~@>~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~@>~A?~A?~@>~A?~A?~@>~@>~A>~@>~A>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@?~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~?=~?=~?=~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~@>~@>~@=~?=~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~@>~@>~@=~@=~A>~@>~A>~@>~?=~@>~@>~@>~@=~@>~@=~@>~@>~@=~@>~@>~@>~@>~@>~?=~@>~?>~?>~@>~@>~@>~@>~@>~@>~?=~?=~@>~?=~@>~@>~@>~?=~@=~@=~@>~@>~@>~@>~@=~@=~@=~@=~@=~@=~@=~@=~?=~@=~@=~@=~@=~@>~@=~@>~@=~@=~@>~@>~@=~?=~@=~@=~@=~@=~?=~@>~@>~?=~?=~@>~@>~@>~@=~@=~@=~@=~@=~@=~@>~@=~@=~@=~@>~@=~@=~@>~@=~@>~@=~@=~@>~?=~?=~?=~@=~@>~@>~?=~@=~?=~@>~@>~?=~@=~?=~@>~@>~@>~@>~@>~@>~?=~@>~@>~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@>~@>~?=~@>~@>~@>~@>~?=~@=~@=~?=~A>~@>~@=~@=~@=~@>~@>~A>~@>~@=~@>~@>~?=~?=~?=~@>~@>~@>~?=~@>~@>~?=~?=~@>~@>~@>~@>~@>~?=~@>~@>~?=~@>~@=~@>~@>~@>~?=~?=~@>~@=~?=~?=~?=~@>~@>~?=~@=~?=~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~@>~?=~?=~@>~@>~@>~@>~?=~?=~?=~@=~@=~@>~@>~@>~@=~?=~@>~@>~@>~@>~@>~?=~?=~?=~@>~@>~@>~?=~?=~@>~@>~?=~?=~@>~@>~?=~?=~@>~@>~?=~?=~@>~?=~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~A?~A?~A?~A?~@>~@>~A?~A?~A?~@>~A?~@>~@>~@>~@>~@>~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~B?~A?~B?~B?~B?~B?~A?~B?~B?~B?~B@~B@~B@~B@~B@~B@~A?~B@~B@~B@~A?~A@~A@~B@~B@~A@~B@~BA~B@~B@~BA~CA~B@~CA~CA~CA~BA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~DB~CA~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~DC~EC~EC~EC~EC~EC~EC~EC~EC~FD~ED~ED~FD~ED~FD~FD~FD~GE~FD~FD~FD~GE~FD~GE~GE~GE~GE~GE~GE~GE~FD~GE~GE~GE~GE~HF~GE~GD~GE~GE~GE~HF~HF~GE~HF~HF~HF~HF~HF~HF~IG~HF~HF~HF~HF~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~JH~JH~JH~IG~JH~IG~JH~JH~JH~JH~JH~IG~JH~JH~JH~IG~JH~JH~JH~KI~JH~KI~KI~KI~KI~KI~KI~KI~JH~KI~KI~KI~KI~LI~KI~KI~KI~KI~LJ~LI~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~MK~LJ~LJ~MK~MK~MK~MK~LJ~NL~MK~MK~NL~NL~MK~NL~MK~MK~MK~MK~MK~MK~MK~MK~MK~NL~NL~NL~NL~MK~NL~NL~NL~NL~NL~NL~NL~NL~MK~NL~MK~MK~NL~MK~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~OM~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~MK~NL~NL~NL~NL~OM~NL~NL~NL~NL~NL~NL~NL~MK~NL~NL~MK~NL~NL~NL~NL~MK~MK~NL~MK~MK~MK~MK~MK~MK~MK~MK~MK~NL~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~LJ~MK~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~KI~KI~KI~KI~KI~KI~JH~KI~KI~KI~JH~JH~JH~JH~JH~JH~KI~JH~JH~JH~IG~JH~JH~JH~JH~JH~JH~IG~IG~JH~JH~JG~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~HF~HF~HF~HF~HF~IG~HG~HF~HF~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~GF~HF~GE~GE~HF~HE~GD~GE~GE~GE~GE~GE~FD~GE~GE~GE~FD~FD~FD~GE~FD~FD~FD~FD~EC~EC~FD~FC~EC~EC~FD~FC~EC~ED~EC~EC~ED~ED~EC~EC~EC~EC~EC~EC~EB~EC~EB~EC~EB~EC~EB~DB~DB~DB~DC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CA~DB~CA~DB~DB~CA~DA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~B@~CA~CA~CA~CA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B?~B?~B?~B@~B@~B@~B@~B@~B@~B@~B@~B@~A@~B@~B@~A?~A@~A?~A@~A@~A@~A?~A?~A?~A?~A?~A?~A?~B@~A?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@?~A?~@?~@>~A?~A?~A?~A?~@>~@>~@>~A?~A?~A?~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@?~@>~@>~@>~@>~A>~@>~@>~@>~?=~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~?=~@>~@=~@=~@>~@>~@>~@=~@=~@=~@>~@>~@=~@>~@=~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@=~@=~@>~@=~@=~@=~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@=~@>~@=~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~?=~@=~?=~@>~@>~@>~@>~@>~@>~@=~@>~@=~?=~?=~@>~@>~@=~@=~@=~@=~@=~@=~@>~@=~@=~@>~@>~@=~@=~@>~@=~@>~@=~@>~@=~@=~@=~@=~@>~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~?=~@=~@>~@>~?=~@>~@=~@>~?=~?=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~?=~@>~@>~@>~@=~@=~@=~@=~@=~@>~@=~@>~@=~@=~@=~@=~@>~@=~?=~?=~@>~@>~@>~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~?=~@=~@>~@=~@=~@>~@>~@=~@=~@=~@=~@=~?=~@>~@>~?=~?=~@=~?=~@>~?=~@=~@=~?=~@=~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~@=~@=~@=~@>~@>~@=~@>~@=~?=~@>~?=~?=~@>~@>~@>~@>~?=~@>~?=~?=~@>~@>~?=~@>~@>~?=~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~@=~@>~?=~@>~@>~?=~@>~@>~?=~@=~?=~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~A?~@>~A?~@>~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A?~A?~A?~A?~A?~A@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~B@~B@~B@~A@~A@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B?~B@~B@~B@~B@~B@~B@~B@~B@~B@~CA~CA~B@~CA~CA~DB~CA~CA~CA~CA~CA~CA~CA~CA~CA~DB~DB~DB~DB~DB~DB~CA~CA~CA~DB~DB~DB~DB~DB~DC~DC~DC~DB~DB~DC~DB~DB~DC~EC~DC~DC~EC~DC~EC~DC~EC~DC~EC~EC~EC~EC~EC~EC~FD~ED~FD~FD~FD~FD~EC~FD~FD~FD~FD~GE~GE~FD~FD~FD~FD~GE~GE~GE~GE~GE~GE~GE~GF~GE~GE~GE~GE~GE~GE~GE~HF~GE~HF~HF~GE~HF~HF~HF~IG~HF~IG~IG~IG~IG~HF~IG~IG~IG~IG~IG~IG~IG~JH~IG~IG~IG~JH~IG~JH~JH~JH~JH~JH~JH~IG~JH~JH~JH~JH~JH~KI~JH~JH~KI~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~KI~KI~LJ~LJ~LJ~LJ~LJ~KI~KI~KI~KI~LJ~KI~KI~KI~LJ~KI~LJ~LJ~LJ~KI~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~MK~MK~MK~MK~MK~MK~NL~MK~NL~MK~MK~MK~NL~NL~NL~MK~MK~NL~NL~NL~MK~MK~MK~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~OM~NL~NL~OM~NL~NL~NL~NL~NL~NL~OM~NL~NL~OM~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~OM~NL~OM~OM~OM~NL~NL~NL~NL~NL~OM~NL~OM~NL~NL~NL~NL~NL~OM~OM~OM~OM~NL~OM~NL~NL~OM~NL~NL~NL~NL~OM~NL~NL~NL~OM~NL~NL~MK~NL~MK~NL~MK~NL~NL~NL~NL~NL~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~JH~KI~JH~KI~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~JH~JH~IG~IG~IG~IG~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~HF~IG~IG~IF~IG~IF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~GE~HF~GE~GE~GE~GE~GE~GE~HE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FD~GE~GE~GE~GE~FD~GE~FD~FD~FD~FD~FD~FD~FD~FD~EC~FD~FD~FD~FD~FD~EC~EC~EC~FD~FD~DC~EC~EC~EC~EC~EC~DB~EC~EC~EC~DB~DB~EC~DC~EC~DB~EC~EC~DB~DB~DB~DB~DB~DB~DB~CA~DB~DB~DB~CA~DB~DB~DB~DB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~B@~CA~B@~CA~CA~CA~CA~CA~B@~B@~CA~CA~CA~B@~B@~B@~B@~B@~B@~B?~B@~B@~B@~B@~B@~B@~B@~B@~B?~B@~A?~A?~B@~A?~B@~A?~A?~A?~B@~B@~B@~A?~B@~A?~A?~A?~A?~A?~A?~A?~A?~B@~B@~B@~A?~B@~A?~A?~A?~A?~A?~A?~A?~A?~A@~A?~A?~A?~A?~A?~A?~A>~A?~B?~A?~B?~A?~A?~A>~A?~A>~A>~A>~A>~A>~@>~A?~@>~@>~@>~@>~@>~A?~A?~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~A>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~?=~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~?=~?=~?=~@=~?=~@>~@>~@=~@=~@>~@>~@>~?=~@=~@=~@=~@>~@=~@>~@=~@>~@=~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~?=~@>~@=~@>~@>~@>~@=~@>~@>~@=~@=~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@=~@>~@>~@>~@=~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~?>~@>~@=~?=~@=~@=~@>~@>~@=~@=~?=~?=~@=~@=~@=~@=~@>~@>~@=~@>~?=~@>~@=~?=~@=~@=~@=~@=~@=~A>~@=~@=~@=~@=~@=~@>~@=~@=~@=~@=~@=~@=~@=~@>~@=~@=~@=~@=~@=~@=~?=~@=~@=~@=~@>~@>~?=~?=~@=~@=~@=~@=~@=~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@=~@=~@=~@=~@=~@=~@>~@>~@=~@=~@=~@>~@=~@>~@>~@>~?=~@>~@>~?=~@=~@=~@>~@=~@=~@=~@>~@>~@=~@=~@=~@=~@=~@=~@=~@>~@=~@=~@=~@>~@=~@=~@=~A>~@=~?=~?=~@>~@=~@=~@=~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~?=~@=~@=~@=~A>~@=~@=~@=~@=~@=~@>~@>~?=~@=~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~?=~?=~?=~@>~?=~?=~@>~@>~?=~@>~@>~@>~?=~?=~@>~@>~?=~?=~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~B?~B?~A?~B?~B?~A?~B@~B@~A?~A?~A?~A?~A?~B@~A@~B@~B@~A@~A@~B@~B@~BA~B@~A@~B@~B@~B@~B@~B@~B@~C@~B@~C@~B@~CA~B@~B@~B@~CA~B@~CA~CA~B@~CA~CA~CA~CA~CA~B@~CA~CA~CA~CA~DB~CA~CA~DB~CA~DB~DB~CA~DB~DB~CA~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~DB~EC~EB~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~FD~EC~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~GE~HE~GE~GD~GE~GE~GD~GE~GE~HE~HE~HE~HE~HE~GE~HF~GE~HF~HF~HF~HF~HF~HF~HF~HF~HF~IG~HF~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~JH~JH~IG~JH~JH~IG~JH~JH~JH~JH~IG~JH~IG~JH~KI~JH~KI~KI~KI~JH~KI~KI~JH~KI~KI~KI~KI~KI~JH~KI~KI~KI~KI~KI~KI~LJ~LJ~LJ~KI~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~LJ~LJ~LJ~LJ~MK~LJ~LJ~LJ~MK~MK~LJ~MK~MK~MK~MK~MK~MK~MK~NL~NL~MK~NL~NL~NL~NL~NL~NL~MK~NL~MK~NL~NL~NL~NL~NL~NL~MK~MK~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~OM~NL~NL~NL~NL~NL~NL~OM~NL~NL~NL~NL~OM~OM~OM~OM~OM~NL~NL~OM~OM~NL~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~NL~NL~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~NL~NL~OL~OM~NL~NL~NL~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~NL~OM~NL~NL~NL~OM~OM~NL~NL~NL~NL~NL~NL~NL~NL~NL~MK~NL~NL~MK~NL~MK~MK~MK~NL~MK~MK~MK~MK~NL~NL~MK~NL~NL~NK~NK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~LJ~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~KI~LJ~KI~LJ~LJ~LJ~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~JH~KI~KI~KI~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~JH~JG~JH~JH~JH~JH~IG~IG~IG~IG~IG~IG~IF~IF~IF~JG~HF~JG~IF~HF~HF~HF~HF~HF~HF~HF~GE~HF~HF~HE~HF~GE~HE~HE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FE~GE~GD~FD~FD~FD~GD~FD~FD~EC~EC~EC~FD~FD~FD~FD~FD~FD~FD~EC~EC~FD~EC~EC~EC~EC~EC~EC~EC~EC~DB~EC~EC~DB~EC~EC~DB~DB~EB~EB~EB~DA~EB~DB~EB~EB~DA~DB~CA~DA~DB~DB~DB~CA~DB~DB~DB~DB~CA~DB~CA~CB~DB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~B@~CA~CA~CA~CA~CA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B?~B?~B?~A?~B@~B@~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A?~A?~A?~A?~A?~A@~A?~A?~A?~A>~A?~A?~A?~A?~A>~A>~A?~A?~A?~A>~A?~A?~A?~A?~A?~A>~A?~A?~A?~A?~A>~A>~A>~A>~A?~A>~A>~A>~A>~A>~@>~@>~@>~@>~A>~A?~@>~@>~@>~A>~A>~?=~A>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@=~@>~@>~@>~@>~@>~A>~@>~@>~@=~@=~@>~@>~@>~@>~@>~@>~@>~A>~@>~@=~@>~@>~@>~@>~@=~@>~@=~?=~?=~?=~@>~@>~?=~?=~@=~@>~@=~@>~@=~@=~@>~@>~@>~@=~@>~@>~@=~@>~@=~@>~@>~@>~@>~?=~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@=~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~@=~@=~@=~@=~@>~@=~@>~@>~@>~@>~@>~@>~@>~@=~@>~@=~@=~@=~@>~@=~@>~@>~@=~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@=~@=~@>~@=~@=~@>~@>~@=~@=~@=~@>~@>~@=~@>~@>~?=~@=~@=~@=~@=~@=~@=~@>~@>~@=~@=~@=~@=~@=~@=~@=~@>~@=~@=~@=~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@=~@=~@=~@=~@=~@=~@=~@>~@>~@>~@=~@=~@>~@=~@=~@=~@>~@>~?=~@>~?=~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~A>~@=~@=~@=~?=~@=~@=~@>~?=~@>~?=~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@=~@>~@>~@>~@>~@>~?=~@=~@=~@=~@>~@=~@>~@=~@=~@=~@>~@>~@>~@>~?=~@>~?>~?>~@>~@>~?=~?=~@>~@=~@=~@=~@=~@=~@=~A>~@=~@>~?=~@=~@>~@=~@=~?=~?=~?=~?=~?=~@>~?=~?=~@>~@=~?=~?=~?=~@=~?=~?=~?=~@>~?=~?=~@>~@>~@>~@>~@>~@>~?=~@>~?=~@>~?=~@>~@>~@>~@>~?=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~A?~@>~A?~A?~@>~A?~A?~A?~A?~A?~A?~@>~A?~A?~A>~@>~@>~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A?~A?~B?~B?~B?~B?~B?~B?~A?~A?~B@~B@~A?~B@~B@~B@~B@~B@~B@~B@~B@~BA~B@~B@~B@~B@~BA~B@~BA~B@~BA~B@~B@~CA~CA~B@~B@~B@~CA~CA~C@~B@~B@~CA~CA~CA~CA~CA~DB~CA~DB~CA~CA~DB~DB~CA~CA~DB~DB~DB~DB~DB~DB~EC~DB~EC~DB~DB~DB~EC~DB~EB~DB~DC~EC~DC~EC~DC~DC~EC~EC~EC~EC~EC~FD~FD~EC~EC~EC~FD~FD~FD~FD~FD~FD~FD~FD~FD~GE~FD~GE~GE~GD~GE~GE~GE~GE~GE~GE~GE~GE~GE~HF~GE~HF~GF~HF~HF~HF~HF~HF~HF~HF~HF~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~IG~IG~JH~IG~IG~JH~JH~JH~JH~JH~IG~JH~JH~JH~JH~KI~JH~KI~JH~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~LJ~LJ~KI~LJ~LJ~KI~LJ~LJ~LJ~KI~LJ~LJ~LJ~MJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~LJ~MK~MK~MK~MK~LJ~MK~NL~MK~MK~NL~MK~NL~MK~MK~NL~MK~MK~MK~MK~NL~NL~NL~NL~NL~OM~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~MK~NL~NL~NL~NL~NL~NL~NL~OM~OM~OM~OM~NL~OM~OM~NL~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~PN~OM~NL~OM~OM~OM~NL~NL~NL~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~PN~NL~OM~OM~OM~OM~OM~OM~OM~NL~OM~OM~OM~OM~OM~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~MK~NL~NL~MK~NL~NL~NL~NL~MK~NL~NK~NL~NK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~LJ~MK~LJ~LJ~KI~LJ~LJ~KI~KI~LJ~LJ~KI~LJ~LJ~LJ~LJ~KI~LJ~LJ~LJ~LJ~KI~KI~KI~KI~JH~KI~KI~KI~KI~KI~KI~KI~KI~JH~KI~JH~KI~JH~KI~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~JH~IG~IG~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~HF~IG~IG~HF~HF~HF~IG~HF~HF~HF~HF~HF~GE~HF~HF~HF~GE~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FD~GE~FE~FE~GE~GE~GD~GE~GE~GE~FD~FD~GE~FD~FD~FD~FD~FD~FD~FD~FD~EC~EC~EC~EC~FD~EC~EC~EC~EC~EC~EC~EC~EC~DB~EC~EC~EC~EC~EC~DB~EB~EB~DB~EB~DB~DB~DB~EC~EB~DB~DA~CA~CA~DB~DB~DB~DA~DB~DB~CA~DB~CA~CA~DB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~B@~A?~B@~A?~B@~B@~A?~A?~B?~A?~A?~B?~A?~A?~A?~B@~B@~A?~A?~A@~A@~A?~A?~A?~A@~A@~A@~A?~A?~A?~A?~A@~A?~A?~A?~A?~A?~A?~A?~A>~A?~A?~A?~A?~A>~A?~A>~A?~A?~A?~A?~@>~A?~A?~@>~A>~A?~A?~@>~A>~A>~A>~A>~A>~A>~A>~A>~@>~@>~A>~A>~@>~A>~A>~@>~@>~@>~@>~@>~A>~A>~@>~@>~@>~@>~@=~A?~@>~@?~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@=~@=~@=~@=~@>~@>~@>~@>~@=~@=~@=~@>~@=~@=~@>~@>~@=~@=~@=~@=~@>~@>~@>~@>~@>~@=~@=~@=~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@=~@>~@>~@=~@=~@=~@>~@=~@>~A>~@=~A>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~@>~@=~?=~?=~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@=~@>~@>~@=~@>~@>~@>~@>~@>~@>~@=~@=~@>~@=~@=~@=~@>~?=~@=~@>~@>~@>~@=~@=~@=~@=~@=~@=~@>~@>~@>~@=~@=~@>~?=~@=~@=~@=~@=~@=~@=~@>~@=~@=~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@=~@=~@>~@>~@=~@=~@=~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~?=~@>~@=~@>~@=~?=~@>~@=~@=~@=~@=~@=~@=~?=~@>~?=~@=~@>~@=~@=~@=~@>~@>~@=~@>~@=~@=~@=~@=~@=~@=~@=~@>~@>~@=~@>~A>~@>~@>~@=~@=~@=~@=~?=~@>~@=~@=~?=~@=~@>~@=~@>~@>~@>~@=~@>~?=~?=~?=~@>~@>~@=~@>~?=~@>~@>~?=~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~A>~A>~A>~A>~@>~A>~A>~A>~@>~A?~A?~@>~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~B?~B@~A@~A@~B@~B?~B?~A?~A?~B?~B@~A@~B@~B@~B?~B@~B@~B@~B@~B@~CA~BA~BA~CA~BA~CA~CA~B@~B@~CA~CA~CA~CA~B@~CA~CA~CA~CA~DB~DB~CA~CB~CA~DB~CA~DB~CA~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~DB~DB~DB~DB~EC~DC~DC~EC~EC~EC~EC~EC~EC~EC~EC~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~GE~GD~GD~GD~GD~GE~GE~GE~GE~GE~GE~GE~GE~HF~HF~GE~GF~GE~HF~HF~HF~HF~HF~IG~HF~HF~HF~HF~HF~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~JH~IG~JH~JH~JH~JH~JH~JH~KI~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~KI~LJ~LJ~LJ~KI~LJ~LJ~LJ~KI~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~MK~LJ~LJ~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~LJ~MK~MK~MK~NL~MK~NL~NL~NL~NL~NL~NL~NL~NL~OM~OM~NL~OM~NL~OM~NL~NL~OM~OM~OM~OM~OL~NL~NL~OM~OM~NL~NL~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~PN~PM~OM~OM~PN~PN~OM~PN~OM~OM~PM~PN~OM~OM~OM~OM~PM~PM~OM~PM~OM~PN~OM~PM~OM~OM~PM~PM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~NL~NL~OM~OM~OM~NL~OM~NL~OM~NL~NL~NL~NL~NL~OM~NL~OM~NL~NL~NL~NL~NL~MK~MK~NL~NL~NL~NL~MK~MK~MK~MK~MK~LJ~MK~MK~MK~LJ~LJ~LJ~MK~LJ~LJ~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~LJ~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~JH~KI~JH~KH~JH~JH~JH~JH~JH~JG~JH~JH~JG~JG~JG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IF~IG~IG~HF~IG~IF~IF~HF~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~GE~GF~GF~GE~GE~GE~GE~GE~GE~GE~GD~GE~GE~GE~GD~GE~FD~FD~FD~FD~FD~FD~FC~FD~EC~FC~FC~FD~EC~EC~EC~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EB~EC~DB~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~EB~DB~DB~DB~DB~DA~DB~CA~DB~DB~DB~DB~CA~CB~CA~CA~CB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~B@~B@~B@~B@~CA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~A?~A?~A?~A?~A?~B?~B?~B@~B?~B?~A?~B?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~A@~A?~A?~A?~A?~A?~A?~A?~A>~A>~A>~A>~@>~A?~A?~A?~A?~A?~A?~A?~A?~@>~@>~A?~@>~A?~A?~A?~@>~@>~@>~@>~@>~@>~@>~A>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~?=~@=~@=~@=~@>~@>~?=~@>~@=~@>~@>~@>~@=~@>~@>~?=~@>~@>~?=~@=~@=~@=~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@=~@>~@=~@=~@=~@=~@>~@>~@>~@>~@>~?=~?>~@>~@>~@>~?=~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@=~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@=~@>~@>~@=~@>~@>~@>~@=~@=~@>~@=~@>~@=~@=~@=~@=~@=~@=~@=~@=~@>~@=~@>~@=~@=~@>~A?~@>~@>~@>~@>~@>~@=~@>~@=~@>~@=~@=~@=~@=~@>~@=~@>~@>~@>~@>~@=~@=~@>~@=~@>~@>~@>~@=~@>~@>~@>~@>~@=~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@=~@=~@=~A>~@=~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@=~@=~@>~@=~@=~@>~@=~@>~@>~?=~@>~@>~@>~@=~@=~@=~@>~@>~@=~@=~@=~@>~@>~@=~@>~@>~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@>~?=~?=~?=~?=~@>~?=~@>~?=~?=~?=~@=~@=~@>~@=~@=~@=~?=~@>~@=~@=~@>~@>~@>~@>~?=~@>~@>~@>~@>~?=~@>~?=~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@=~@>~@>~@>~A>~A>~@>~A?~A>~A>~A?~@>~A?~A>~A>~A>~A>~A?~A?~A>~A?~A>~A>~B?~B?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~B?~B?~A?~B?~A?~A?~A?~B?~B?~B?~B@~B@~B?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~CA~B@~CA~B@~CA~CA~CA~CA~DB~CA~CB~CA~CA~CA~CA~CA~CA~CA~CA~CA~DB~CA~DB~DB~DB~CA~CA~DB~CA~DB~DB~DB~DB~DB~DB~DB~EC~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~EC~EC~EC~FD~FD~FD~FD~FD~GE~FD~FD~GD~GD~FD~GE~GE~GE~GE~GE~GE~GE~GE~GF~GF~HF~GE~GE~GE~HF~HF~HF~HF~HF~HF~IG~HF~HF~HF~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~IG~KI~JH~JH~KI~KI~KI~KI~KI~KI~KI~KI~JH~KI~KI~JH~KI~KI~LJ~KI~JH~KI~KI~KI~LJ~LJ~LJ~LJ~KI~LJ~LJ~KI~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~MK~MK~MK~MK~MK~LJ~MK~MK~MK~MK~MK~MK~NL~NL~NL~NL~NL~NL~MK~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~OM~NL~OM~NL~NL~NL~OM~OM~NL~NL~NL~OM~OM~OM~OM~OM~OM~OM~PM~OM~OM~OM~OM~OM~OM~NL~OM~OM~OM~PM~OM~PN~PM~PM~OM~PM~PM~PN~PN~OM~PM~OM~OM~PN~OM~PM~PN~PM~PM~OM~PN~PN~OM~PM~PN~PM~PN~PM~QN~PN~PM~PM~PM~PN~PN~PN~PM~PN~PM~PM~PM~PN~PN~PM~PM~PM~PM~PN~PM~PM~PN~OM~OM~PN~PN~OM~PM~PM~PM~OM~PM~PM~PM~PM~OM~OM~OM~OM~PM~OM~PN~PN~PN~OM~OM~OM~OM~PN~PN~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~NL~OM~NL~OM~OM~NL~NL~NL~NL~NL~NL~NL~NL~OM~NL~NL~MK~NL~NL~MK~MK~MK~MK~MK~LJ~MK~MK~MK~MK~MK~LJ~LJ~MK~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LI~LJ~LJ~LJ~LJ~LJ~LI~LJ~LJ~LJ~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KH~KH~KI~KI~KH~KH~KI~JH~JH~KI~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~JG~IG~HF~IG~IF~HF~IG~IG~IF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~GE~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GD~FD~GD~FD~FD~FD~FD~FD~EC~FD~FD~FD~FD~FD~FD~EC~EC~FC~EC~FC~EC~EC~EC~EB~DB~DB~DB~EC~EC~EC~EB~DB~DB~EB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DA~DB~CA~CA~CA~DB~DB~CA~CA~CA~CA~CA~C@~C@~CA~CA~B@~CA~CA~B@~CA~B@~B@~C@~C@~C@~C@~B@~B@~B@~B@~B@~B@~B@~B?~B@~B@~B@~B@~B?~B?~B?~B@~B@~B@~B?~B?~B?~B@~B@~B@~B@~B@~B@~A?~B?~B?~B@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A?~A?~A?~A?~A?~A?~A>~A>~A>~A?~A?~A?~A?~@>~A?~A?~A?~@>~A?~@?~A?~A>~A>~@>~@>~@>~A>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@=~A>~@=~@=~@=~@=~?=~@=~@=~@=~@>~@=~@=~@=~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@=~@=~@=~@=~@=~@=~@=~@=~@>~@=~@=~@=~@=~@>~@=~@=~@>~@=~@=~@=~@=~@>~A>~@=~@=~@>~@=~@>~@=~@=~@=~@>~@>~@>~@>~?=~@>~@>~@>~@>~@=~@>~@=~@>~@>~@=~@=~@=~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~?=~@>~@>~@>~@=~@>~@>~@=~@=~@>~?=~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~A>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~?=~@>~@>~@=~@>~@=~@>~@>~@>~@>~@>~@=~@>~@>~@=~@=~@=~@=~@=~@>~@=~@>~@=~@=~@=~@>~@>~@=~@>~@>~@>~@>~@=~@>~@>~A>~@=~@=~@=~@>~@>~@>~@>~@>~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~@>~@>~@>~@>~@>~@=~@>~A>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@=~@>~@=~@=~@>~@=~@=~@=~@>~@>~@=~@>~@=~@>~@>~@>~@=~@=~@=~@=~@=~@>~@>~@>~@=~@=~@=~@>~@>~@=~@=~@>~@>~@>~@=~?=~@>~@>~@>~@=~@>~@=~@>~?=~@>~@=~@=~@=~@>~@>~@>~?=~@>~?=~?=~?=~?=~?=~@=~@>~@=~@=~@>~@=~@>~@=~@>~@>~@>~?=~@=~@>~@>~?=~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@=~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~A>~@>~@>~@>~A>~A>~A>~@>~A>~A>~A>~A>~A>~A?~A?~A?~@>~A?~A?~A?~@>~A?~A>~A>~A>~A>~A>~A?~A>~A?~A?~A?~A?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~B?~A?~A>~B?~B?~B?~B?~B@~B@~B?~B?~B@~B@~B@~B?~B?~B?~B?~B?~B@~C@~B@~B@~B@~B@~B@~B?~B@~B@~B@~B@~B@~CA~CA~CA~CA~CA~CA~B@~CA~CA~B@~CA~CA~CA~CA~CA~DB~DB~CA~DB~CA~CA~DB~CA~DB~CA~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DC~DC~DC~EC~EC~EC~DC~FD~EC~EC~EC~EC~EC~EC~FD~EC~FD~EC~FD~FD~FD~FD~GD~FD~GE~GE~GE~FD~GE~GE~GE~GE~GE~GD~GE~GE~HF~GF~GF~GE~GF~HF~HF~HF~HF~IG~HF~HF~IF~IF~HF~IG~IG~IG~HF~IG~JG~JG~IG~IG~IG~IG~JH~JH~IG~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~KI~JH~JH~KI~KI~KI~KI~KI~LJ~KI~KI~LJ~LJ~KI~KI~KI~LJ~KI~KI~LJ~LJ~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~MJ~LJ~MK~MK~MK~MK~MK~NL~MK~MK~NL~NL~MK~NL~NL~MK~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~OM~NL~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~NL~OM~OM~OM~OM~OM~OM~PM~OM~PM~PM~PM~PM~OM~OM~PN~PM~PM~PM~PM~PM~PM~PN~PM~PN~PN~PN~PN~PN~PN~PN~PN~PN~QO~PN~QN~PN~PN~PN~QO~PN~PN~PN~PN~PN~QN~QO~PN~QO~QO~PN~PN~PN~PN~PN~PN~PN~PN~QO~PN~PN~PN~QO~QO~QO~PN~PN~PN~PN~PN~PM~PN~QN~QN~PN~PN~PN~PM~PM~PN~PM~PM~PN~PM~OM~PN~OM~OM~PN~PN~PM~OM~PM~PM~OM~PM~OM~PM~OM~OM~PN~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~NL~OM~OM~OM~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~MK~MK~MK~MK~NL~NL~MK~NL~MK~MK~MK~LJ~LJ~MK~LJ~MK~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~LJ~LJ~LJ~LJ~LJ~KI~LJ~LJ~LJ~KI~LJ~KI~LI~KI~LI~KI~LI~LI~KH~KH~KI~KI~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IF~IF~IG~HF~HF~HF~HF~IG~HF~HF~HF~HF~HF~GE~HF~HF~HF~GE~HF~GE~GE~GE~GE~HF~GE~GD~GD~GE~FD~GE~GD~GE~GD~GE~GE~GD~GD~FD~FD~GD~EC~FD~FD~FD~EC~FD~FD~FD~EC~FD~FD~EC~EC~EC~EC~EC~DB~EC~EC~EC~EC~EB~EB~EB~EB~EC~EC~EB~DB~DB~DB~DB~EC~DB~DB~DB~DB~DB~DB~DB~DA~DA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~B@~B@~CA~B@~CA~B@~B@~B@~CA~B@~B@~B@~B@~B@~B@~CA~B@~B@~B@~B@~B@~B?~B?~B@~B@~B@~A?~A?~A?~A?~A?~A?~A?~B?~A?~A?~A?~B?~A?~A?~B?~B?~A?~A?~A?~A?~A?~A>~A>~A>~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A?~A?~A>~A?~A?~@>~A>~A?~A?~A?~A>~A>~A>~A>~@>~A?~@>~@>~@>~A>~A>~@>~A>~A?~@>~A>~@>~@>~@>~@>~@>~@?~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@=~@=~@=~@=~@=~@=~@=~@=~@>~?=~@>~?=~@>~@>~?=~@>~@>~?=~@>~@=~@=~@=~@>~?=~@>~?=~@>~@=~@>~@=~@=~@=~@=~@=~@>~@=~@=~@=~@>~@>~@=~@>~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~A>~@>~@>~@>~@>~@>~@=~@>~@=~@=~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@=~@>~@>~@=~@>~@>~@>~@>~@=~@=~@>~@=~@>~@>~@=~@>~@>~@>~?=~@>~?=~@>~?=~A>~@>~@>~@>~@>~@>~@=~@>~@=~@=~@>~@>~@>~@=~@>~@>~@=~A>~A>~A>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@=~@>~A>~A>~@>~A>~@>~A>~A>~@>~@=~@>~@>~@=~@>~@>~@>~@=~@>~A>~@>~@=~@=~@=~@=~@=~@>~@=~@>~@>~@>~@>~@=~@=~@>~@=~@>~@>~A>~@>~@>~@>~@=~@>~@=~@=~@=~@=~@>~@>~@=~@=~@>~@=~@=~@=~@=~@=~@=~@>~@=~@=~@=~@=~@>~@>~A>~@=~@=~@=~@=~@=~@=~@>~@=~@=~@=~@=~@=~@>~A>~@=~?=~@>~@>~@>~@=~@=~@=~@=~?=~@>~@>~@=~@>~@>~@=~@>~@>~@>~@=~@>~@>~@>~A>~@=~@>~A>~A>~A>~@=~@>~A>~@=~@>~A>~A>~@>~@>~@>~A>~A?~A>~@>~A>~@>~@>~A?~@?~@?~A?~@?~@>~A?~A>~A?~A>~A>~A?~A?~A?~A>~A>~A>~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A?~B?~A?~A?~A?~B?~B?~A?~B?~B?~B@~B?~B?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B?~CA~B@~B@~B@~C@~C@~B@~B@~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CA~DB~DB~DB~DB~EB~EC~DB~DB~EC~EC~EC~EC~EC~EC~FD~EC~EC~FD~FD~FD~EC~FD~FD~FD~FD~FD~FC~FD~FD~FD~FD~GD~GE~GE~GE~GE~GD~GE~GE~GE~GE~GE~HF~HF~HF~HF~HF~HF~HF~HF~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~JH~JH~JH~JH~JH~JH~JH~KI~JH~JH~JH~JH~KI~JH~KI~JH~KI~JH~KI~KI~KI~KI~KI~KI~KI~KI~LJ~KI~LJ~KI~LJ~LJ~KI~LJ~LJ~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~NL~MK~NL~MK~MK~NL~NL~NL~NL~NL~NL~MK~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~OM~OM~OL~OM~NL~OM~OM~OM~OM~OM~PM~OM~PM~OM~OM~OM~PN~OM~OM~OM~PM~PM~OM~OM~PN~PM~PM~PM~PN~PN~PN~PN~PN~QO~PN~QO~PN~QO~PN~PN~QO~QO~PN~QO~QO~QO~QO~QO~QO~RP~RP~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~PN~PN~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~PN~PN~QO~QO~PN~PN~PN~PN~PN~QN~QN~PN~PN~PN~PN~PN~PN~QN~PN~PM~PN~PN~PM~OM~OL~PM~QN~PM~PM~PM~OM~OM~PN~OM~OM~OM~OM~OM~OM~OM~NL~OM~OM~OM~OM~OM~OM~OM~OM~NL~OM~NL~NL~OM~OM~OM~OM~NL~NL~NL~NL~NL~NL~NL~NL~NL~MK~MK~NL~NL~NL~NL~NL~MK~MK~NL~MK~MK~MK~MK~NL~NL~MK~MK~MK~MK~MK~MK~LJ~MK~LJ~LJ~LJ~LJ~LJ~LJ~KI~LJ~KI~LJ~LJ~LJ~LI~LI~LI~LI~LI~KI~KI~KI~KI~JH~KI~KI~JH~KH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IF~IG~HF~IG~HF~HF~IF~HF~HF~HF~HF~GE~HF~GE~HE~GE~HF~GE~GE~GE~HF~GE~HF~GE~GE~GE~GE~GE~GE~GE~GE~GD~GD~GD~GE~GE~GD~FD~FD~FD~FD~FD~FD~EC~FD~FD~FD~FD~FD~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~EC~DB~DB~DB~CA~DB~DB~CA~CA~DA~CA~CA~CA~DB~DA~C@~CA~B@~B@~CA~CA~CA~B@~CA~CA~CA~CA~CA~B@~B@~CA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~B@~B@~B@~B@~B@~A?~A?~B@~A?~A?~A?~A?~A?~B@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A>~A?~A>~A>~A>~@>~@>~A?~A?~A?~A?~A?~@>~A?~A?~@>~@>~@>~@>~A>~A>~A>~A>~@>~A>~A>~A?~A>~@>~A>~A?~@>~A>~@>~A?~@>~@>~A>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@=~@>~@>~@>~@>~@>~?>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~@=~@=~?=~@=~@=~@>~?=~?=~@>~@>~?=~?=~@=~@=~@=~@=~@=~@=~@>~@>~@=~@=~@>~@=~@=~@=~@=~@=~@>~@=~@=~@=~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@=~A>~A>~@=~@>~@=~@=~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~A>~@>~@>~A>~@>~A>~A>~@=~@>~@>~@=~@=~@=~@>~@>~@>~@=~@>~@=~@>~@=~@=~@>~@=~@>~@>~@>~?=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@=~@=~A>~@>~@>~A>~A>~@>~@>~@>~@>~A>~A>~@=~@>~@>~@>~A>~A>~@>~@>~@>~@>~@>~@>~@>~A>~@>~A>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~A>~@>~@>~@=~@>~@=~@=~@>~@>~@>~@=~@=~@>~@>~@=~@>~@=~@=~@>~@=~@>~@>~@=~@=~@=~@=~?=~@=~@=~@=~@>~@>~@>~@>~@=~@=~@=~@>~@=~@>~@=~@=~@>~@>~@=~@>~@=~@>~?=~@>~@>~@=~@>~@>~@>~@>~@=~@=~@>~@=~@>~A>~@>~@=~@=~@=~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~A>~@>~A>~A>~A?~@>~A>~A>~A>~A>~A?~A>~A>~A?~A>~A>~@>~A>~A>~A>~A?~A>~A>~A>~A?~B?~B?~A?~A?~B?~A?~B?~A?~A?~A?~A?~B@~A?~A?~A?~A?~A?~B?~B?~A?~A?~A?~B?~B?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B?~B@~B@~B@~C@~B@~B@~B@~CA~B@~B@~CA~CA~B@~B@~CA~CA~CA~CA~CA~CA~CA~CA~CA~DB~CA~CA~DB~DB~CA~DA~DA~DB~DB~DB~DB~DB~EB~DB~DB~DB~EB~EB~EB~DB~EC~EB~EC~EB~EB~EC~EC~EC~EC~FD~EC~EC~EC~EC~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~GE~FE~GE~GE~FD~GE~GE~GE~GE~GE~GE~GE~HF~HF~HF~HF~HF~IG~IG~HF~HF~HF~IG~IG~IG~IG~JG~JG~IG~IG~IG~IG~IG~JH~JH~JH~JH~JH~JH~JH~JH~JH~KI~KI~JH~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~MK~MK~MK~MK~LJ~MK~NL~NL~MK~MK~NL~MK~MK~MK~MK~NL~NL~MK~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~OM~OM~NL~NL~OM~OM~OM~OM~NL~OM~OM~OM~PN~PN~PM~OM~PN~PN~PN~OM~PN~PN~PN~PN~PN~QN~PM~QN~QN~PN~QO~QO~PN~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QN~QO~QO~QO~QO~QO~RP~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~RP~QO~RP~QO~QO~QO~QO~RP~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~PN~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~PN~PN~PN~PN~QO~QO~PN~QO~PN~PN~PN~PN~PN~PN~PN~PN~PN~PM~PM~PN~PN~OM~OM~PN~PN~PN~PN~OM~OM~PM~PM~PM~PM~OM~OM~OM~OL~OL~NL~OL~NL~OL~NL~NL~NL~OM~OM~OM~OM~OM~OM~NL~NL~NL~NL~OM~OM~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~MK~MK~MK~MK~MK~NL~MK~MK~MK~MK~MK~LJ~MK~LJ~MK~MK~MK~MK~MK~LJ~LJ~LJ~LJ~KI~LI~LJ~LI~LJ~LI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~JH~KI~KH~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~JH~IG~IG~IG~IG~IG~IG~IG~IF~HF~IG~IG~IG~IF~IG~IF~HF~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~GE~GE~GE~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FD~FD~GE~GD~FD~FD~FD~FD~FD~FD~FD~FD~FD~EC~FD~FD~EC~EC~EC~FD~FC~FC~EC~EC~EC~EC~EC~EC~EC~EC~EB~DB~DB~DB~DB~DB~EC~DB~DA~DB~DB~DB~DB~DB~DB~DB~CA~CA~DA~CA~CA~DB~CA~CB~CA~CA~CA~CA~B@~CA~C@~CA~CA~CA~CA~CA~C@~B@~CA~B@~CA~CA~CA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~B@~B@~A?~A?~B@~A?~B@~B@~A?~B?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A@~A?~@?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~@>~A?~@>~@>~A?~A?~A?~@>~@>~@>~@>~A?~@?~@>~@>~A?~@>~A?~A>~@>~A>~A>~@>~@?~@>~@>~@?~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@=~@>~@>~@=~@=~@=~@=~@=~@>~@=~@=~@>~@=~@=~@=~@=~@>~@>~@=~@>~@>~@=~@=~@=~@=~@>~@>~@>~?=~@>~@>~@>~@>~@=~@>~@=~@>~@>~@>~@>~@>~?=~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~A>~A>~@=~@=~A>~A>~@=~A>~@>~A>~A>~A>~A>~@>~@>~?=~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~A>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~?=~@>~@=~@>~A>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~A>~A>~A>~@>~@>~@>~@>~A?~@>~@>~@>~A?~@>~@>~A>~@>~A>~@>~A>~A>~A>~A>~@>~A>~@>~@>~@>~A?~A>~A>~A>~A>~A>~@>~@>~A>~A>~A>~@>~@>~A>~A>~@>~A>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~A>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@=~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~@>~@=~@=~@>~@=~@=~?=~@=~@=~@=~@=~@=~@=~A>~@=~A>~@=~@>~@=~?=~@=~@=~@>~@>~@>~@=~@=~@=~@=~@=~@>~@>~@=~@>~@>~@>~@>~@>~@>~@=~@>~A>~@=~@>~@>~@>~@>~@>~@=~@>~@>~A>~A>~@>~@>~@>~@>~@?~@>~@>~@>~@>~@>~@>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A?~A?~A?~A?~B?~B?~B?~A?~A?~A?~A?~A?~A?~A@~A?~A@~A@~A@~A@~A?~B@~A?~B@~B?~B?~B?~B?~B?~B@~B@~B@~B@~CA~BA~BA~BA~CA~B@~B@~B@~CA~CA~CA~B@~CA~CA~B@~CA~CA~CA~CA~CA~CA~CA~CA~CA~DB~CA~CA~CA~CA~CA~CA~DA~DA~DA~DA~CA~DB~DB~DB~DB~DB~DB~EB~DB~EB~EB~DB~EB~DB~EC~EC~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~FD~FD~FD~FD~FD~FD~FD~FD~GE~GE~GE~GD~FD~GE~GE~GE~GE~GE~GE~GE~GE~GE~HF~HF~HF~HF~HF~HF~HF~HF~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~JH~IG~IG~JH~JH~JH~JH~JH~JH~JH~KI~KI~JH~JH~JH~KI~KI~KI~KI~LJ~LJ~LJ~KI~LJ~KI~KI~LJ~LJ~KI~KI~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~MK~MK~LJ~MK~MK~NL~NL~NL~NL~NL~MK~NL~NL~MK~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~OM~OM~OM~NL~OM~NL~NL~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~PM~PM~PM~PN~PN~PN~PM~PN~PM~QN~PN~PN~PN~QO~PN~PN~QO~PN~QN~QN~QO~QO~QO~QO~QO~QO~RP~QO~QO~QO~QO~QO~QO~QO~QO~QO~RP~QO~QO~RP~RP~QO~QO~QO~RP~RP~RP~RP~RO~RO~RP~RP~RP~RP~RO~RP~RO~RO~RO~RO~RO~RP~RP~RP~RP~RP~RP~RP~RP~RP~QO~RP~RP~RP~QO~QO~RP~QO~QO~RP~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~PN~QO~PN~PN~PN~PN~PN~PN~PN~PN~PN~QO~PN~PN~PN~PN~PN~PN~PN~PM~PN~PN~PN~PM~PM~PM~PM~PM~OM~PN~OM~OM~PM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~NL~NL~OM~OM~OM~OM~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~MK~NL~MK~MK~MK~MK~MK~MK~MK~LJ~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~LJ~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~JH~KI~KI~KI~KI~KI~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~IG~JH~IG~IG~IG~IG~IG~IG~IG~HF~IG~HF~HF~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HE~HE~GE~GE~HE~HE~GE~GE~GE~GE~GE~FD~GE~GD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~EC~FC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EB~EC~EC~DB~EB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~CB~CA~CA~CA~CA~DA~DA~CA~DA~CA~CA~DA~CA~CA~CA~C@~CA~CA~C@~CA~B@~CA~CA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~B@~B?~B@~B?~B?~B?~B@~B?~B?~A?~A?~A?~B@~A?~B?~B?~B?~A?~A?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@?~A?~A?~@>~A?~A?~@>~@>~@>~@>~@>~A?~@>~@>~A?~A?~@>~@>~@>~@>~A?~A?~@>~A?~@>~@?~@?~@>~@?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@=~@>~A>~@>~@=~@=~@=~@=~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@=~@=~@=~@>~@=~@=~@=~@=~@=~?=~@=~@=~?=~?=~@>~@=~@>~@>~@>~@=~@>~?=~@>~@>~@=~@>~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~A>~A>~A>~A>~@>~@>~A>~@>~A?~A>~@>~A>~@>~A>~A>~A>~A>~A>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~A>~@>~@>~A>~A>~A>~@>~@>~A>~@>~@>~A>~A?~A>~A>~A?~A>~A>~A>~A>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~@>~A>~@>~A>~@>~@>~A>~A>~@>~@>~@>~@>~@>~@>~A>~A>~A>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~@>~A>~A>~A?~A>~@>~A>~A>~@>~A?~@>~@>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A>~@>~A>~A>~A>~@>~@>~@>~A>~A?~@>~A?~@>~@>~A>~A>~A?~@>~@>~A>~A?~@>~@>~A?~A?~@>~@>~A?~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@=~@>~@>~@=~@>~?=~@>~@>~@>~@>~@>~@>~@=~@>~@=~@>~@=~@=~@=~@>~@>~@=~@=~@>~A>~@>~@>~@>~@=~@>~@=~@>~@=~@=~@>~@=~@>~@=~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@=~@>~A>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@=~@>~@>~A>~A>~A>~@=~A>~@>~@>~A>~A>~A?~A>~@>~@>~@>~A?~A?~A?~A?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A>~A?~A?~A?~A?~A?~A?~A?~A?~A@~B@~B@~A?~A?~A?~A?~B?~B?~B@~A?~B?~B@~B@~A?~B@~B@~B@~B@~B@~B@~B@~B@~CA~B@~CA~CA~B@~B@~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~DA~CA~CA~CA~CA~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~FD~FC~FD~FD~FD~FD~FD~FD~FD~GE~GD~GD~FD~FD~GE~GE~GE~GE~GE~GE~GE~HF~GE~GE~HF~HF~HF~HF~HF~IG~HF~HF~IG~HF~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~IG~JH~JH~JH~JH~JH~KI~JH~JH~KI~KI~JH~JH~KI~KI~KI~KI~KI~KI~KI~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~MK~MK~LJ~LJ~MK~MK~MK~MK~MK~MK~MK~NL~MK~MK~MK~MK~NL~NL~NL~NL~MK~NL~MK~NL~NL~NL~NL~NL~NL~NL~OM~NL~NL~OM~OM~NL~OM~OM~OM~OM~OM~PM~OM~OM~PN~OM~PN~PN~PN~PN~PN~PN~QN~PM~PN~PN~PN~PN~QO~PN~QO~QO~QO~QO~QO~QO~QO~QO~RP~RP~QO~RO~RO~RO~RO~RO~RP~QO~RP~QO~RO~RO~RO~RP~RP~RP~QO~RP~RP~RP~RP~SP~SP~RP~RP~RP~SP~SP~RP~SP~SQ~SP~RP~RP~SP~RP~SP~SP~SP~SP~SP~RP~RP~RO~SP~RP~SP~RP~SP~RP~RP~SQ~SP~SP~RP~RP~RO~RP~RO~RO~RO~RO~RO~RO~RP~RP~QO~RP~QO~RP~QO~QO~RP~RO~QO~QO~QO~QO~QO~QO~QO~RP~QO~QO~RP~QO~PN~QO~QO~QO~QO~QO~QO~PN~QO~QO~QO~QO~PN~PN~PN~PN~PN~PN~QO~PN~PN~PN~PN~PN~PN~PN~PN~PN~PN~OM~PN~OM~OM~PM~OL~PM~OM~OM~OL~OL~OM~NL~OM~OL~NL~OM~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~OM~NL~NL~NL~NL~NL~MK~MK~MK~MK~MK~MK~LJ~MK~MK~MK~LJ~LJ~MK~MK~LJ~LJ~LJ~LJ~KI~LJ~LJ~LJ~KI~KI~LJ~KI~LJ~KI~KI~KI~LI~KI~KH~JH~JH~KI~KI~KI~KI~JH~JH~JH~JH~JH~JH~JH~JH~IG~JH~IG~IG~IG~IG~IG~IG~IG~IF~IG~IF~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~GE~GE~GE~GE~GE~GD~GE~GE~GE~GE~GE~FD~GE~GE~GE~GE~FD~FD~GE~FD~GD~FD~FD~FD~FD~FD~FD~FD~ED~EC~EC~EC~FD~FD~EC~EC~EC~EC~EC~DB~EC~EC~EC~EC~DB~DB~EC~DB~DB~DB~DB~DB~DB~DB~DA~DB~DA~DA~DA~DA~DA~C@~DA~CA~CA~DB~CA~CA~CA~DA~CA~CA~CA~CA~CA~B@~CA~CA~B@~B@~B@~CA~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~B@~A?~B?~A?~B?~A?~B?~B?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A>~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A>~A?~@>~A?~@>~A?~A?~A?~@>~@>~@>~@>~@>~A?~A?~A?~A?~@>~@>~A?~A?~@>~@>~@>~@>~@>~A>~@>~A?~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~@>~@>~@>~@>~@>~?=~@>~?>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@=~@=~@>~@=~@>~@>~@=~?=~@>~@>~?=~?=~@>~@>~?=~@=~@=~@=~@=~@=~@>~@=~@=~@=~@=~?=~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@=~@>~A>~A>~@>~A>~A>~@>~@>~@>~@>~@?~@>~@>~@>~@>~A>~@>~@>~A>~A>~A>~@>~@>~A?~@>~A>~@>~A?~A>~A>~A>~A>~A>~A>~A?~A?~A>~A>~@>~A>~A?~A>~A>~A>~A>~A>~A?~A?~A>~A>~A>~A>~A>~A>~A>~A>~@>~A>~@>~@>~A>~A>~A?~A>~A>~A?~A>~A>~A?~@>~A>~A>~A>~@>~A>~A>~A>~A>~A>~@>~A>~@>~A>~A>~A>~A>~A>~A>~A>~A>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A?~A>~@>~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~B?~A?~A>~A>~A>~A>~A>~A?~A?~A?~A>~A>~A>~A>~A>~A>~A?~A?~A?~A>~A?~B?~A>~A>~A?~A?~A?~@>~A?~A>~@>~A>~A>~A>~A>~A>~B?~A?~B?~A>~A?~A>~A?~A>~A>~A>~A>~A?~A>~@>~A>~A?~@>~@>~A?~@>~A>~@>~A>~A>~A?~A>~A>~A>~A>~@>~@>~A>~@>~@>~@>~@>~?=~@>~@>~@=~@>~@=~@>~@>~@>~@=~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~@>~@>~@=~@=~@=~@=~@=~@=~@=~@=~@>~@>~@>~@>~@=~@=~@=~@>~@>~@>~@>~@>~@>~?>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@?~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@=~A>~@>~@>~@>~@>~@>~@>~A>~@>~A>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~A?~@>~@>~A?~@>~A?~A?~A>~A>~A>~A>~A?~A?~A?~@?~@?~A?~A?~A?~A?~A?~A>~A?~A?~A?~A?~A?~A?~B?~B?~B?~B?~B?~A?~B?~B?~B@~B?~B?~B@~B@~B@~B@~B@~B@~B@~B@~C@~B@~B@~B@~B@~B@~B@~B@~B@~CA~CA~B@~CA~B@~CA~B@~CA~CA~CA~CA~CA~CA~CA~DA~CA~CA~CA~DB~CA~CA~DB~DB~DB~DB~DB~DB~DC~DB~DB~DB~DB~DB~DB~DC~EC~DC~EC~EC~EC~EC~EC~EC~EC~FD~EC~EC~EC~FD~FD~FC~FD~FD~FD~FD~FD~FD~FD~FD~GE~GD~FD~GE~GE~GE~GE~HF~GE~GE~GE~GE~GE~GE~HF~HF~HF~IG~IG~HF~IG~HF~HF~IG~IG~IG~IG~HF~IG~IG~IG~IG~IG~IG~JH~JH~JH~JH~JH~KI~KI~JH~KI~KI~JH~KI~KI~KI~KI~KI~KI~KI~KI~LJ~KI~KI~KI~KI~KI~LJ~LJ~LJ~KI~LJ~LJ~LJ~LJ~MK~LJ~MK~MK~LJ~MK~LJ~MK~MK~MK~MK~MK~NL~NL~MK~NL~NL~NL~NL~NL~MK~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~OM~NL~OL~OM~OM~OL~OM~OL~OL~PM~OM~OM~PM~PM~OM~PN~PN~PN~PN~PN~PN~QO~QO~PN~PN~QO~PN~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~RO~RP~RO~RP~QO~RP~RP~RP~RP~RP~SP~RP~RP~RP~RP~RP~RP~RP~RP~RP~RP~RP~SP~SQ~SQ~SP~SP~SQ~SP~SP~SP~SQ~SP~SP~SP~SP~SP~SP~SP~TQ~SP~SP~SP~SP~SP~SP~SP~SP~SQ~SQ~SQ~SP~SP~SP~SP~SP~SP~SP~RP~RP~SQ~RP~SP~SP~SQ~SP~SP~SP~SP~SP~RP~RP~SP~RP~SP~SP~SP~SP~SP~RP~SP~RP~RP~RP~RP~QO~RP~RO~RO~RO~RO~RO~QO~QO~QO~QO~RO~RO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~PN~PN~PN~QO~QO~PN~PN~PN~QO~PN~PN~PN~PN~PN~PN~PN~PN~PN~PN~PM~PM~PM~OM~OM~OM~OM~OM~OL~OL~OL~OL~OL~OL~OM~OL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~MK~MK~MK~MK~MK~MK~MK~MK~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~LI~LJ~LI~KI~KI~KI~KI~KI~KI~KI~KI~KI~JH~JH~JH~KI~KI~KI~KH~KH~JH~JH~JG~JH~JH~JH~IG~JH~JH~JH~IG~IG~IG~JH~IG~IG~HF~IG~IG~HF~IG~HF~HF~HF~HF~IF~HF~HF~HF~HF~HF~HF~HF~HF~GE~GE~GE~GE~HF~HF~GE~GE~GE~GE~GD~GD~GD~GD~GD~GD~GD~FD~FD~FD~FD~FD~FD~FD~FC~FC~EC~FD~FD~EC~EC~EC~EC~EC~DB~EC~EC~EC~EC~DB~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DA~DA~DA~DB~CA~CA~CA~CA~BA~CA~CA~CA~CA~CA~CA~CA~CA~CA~C@~C@~CA~CA~C@~C@~C@~C@~C@~C@~B@~C@~B@~B@~B@~B@~B@~A?~B@~B@~B@~A?~B@~B@~A?~B@~B@~B@~B@~B?~B?~A?~A?~A?~A?~A?~A?~A?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A>~B?~A>~A>~A>~A>~A>~A>~A>~@>~@>~A?~A>~A>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~?=~@>~@=~@=~@>~@>~@>~@>~@>~?=~@>~@>~?=~@>~@>~@>~@>~@=~@>~@>~@>~@=~@=~@>~@=~@>~@=~@>~@=~@>~@>~@>~@>~@>~@>~?=~?=~@=~?=~@=~?=~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@=~A>~A>~A>~A>~A>~@=~@=~A>~A>~A>~A>~@>~A>~A>~A>~A>~@>~A>~@>~A>~A>~A>~A>~A>~A>~A>~@>~A>~A?~A>~A>~@>~A>~A>~A>~A>~A>~A?~A?~A>~A>~A?~A>~A>~A>~A?~A?~A?~A>~@>~@>~@>~A?~A?~@>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A?~A?~A>~A>~A>~A>~A>~A?~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A?~B?~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~B?~A?~A>~A>~B?~B?~A>~A?~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A>~A?~A?~A?~A?~A?~A?~A?~A>~A?~A>~B?~B?~A?~A>~A?~B?~A>~A?~B?~A>~A>~A>~A>~A>~A>~A>~A?~A?~A?~A?~A>~A>~A>~A>~A>~@>~A>~A>~A>~A>~@>~A>~@>~@>~@>~@>~A>~A>~@>~@>~A>~A>~A>~A>~A>~@>~@>~@>~A>~@=~@>~@=~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~?=~@>~@=~@>~@>~@=~@>~@>~@>~@>~@=~@>~@>~@>~?=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@=~A>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@=~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~A>~A?~A?~@?~@?~A>~@>~@>~A>~@>~@>~A>~A?~A>~A?~A?~A>~A?~A?~A?~@?~A?~@?~A?~A?~@?~A?~A?~A?~A?~A?~A>~A?~A?~A?~A?~A>~A?~A?~A?~A?~A?~B@~B@~A@~B@~B?~B@~B@~B@~B@~B@~B@~B?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~CA~B@~B@~B@~B@~CA~CA~B@~CA~CA~CA~DA~C@~CA~DA~DA~DB~DB~CA~CA~CA~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DC~DC~EC~EC~DC~EC~EC~FD~EC~EC~EC~EC~FC~EC~EC~FC~FC~FD~FD~FD~FD~FD~FE~FE~FE~FE~GE~GE~GE~GE~GE~GE~GE~GE~GE~HF~GE~GE~HF~HF~HF~HF~IG~HF~HF~HF~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~JH~IG~JH~JH~JH~JH~JH~KI~JH~JH~KI~KI~JH~KI~KI~KI~LJ~KI~KI~LJ~KI~LJ~LJ~KI~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~MK~MK~MK~MK~MK~MK~NL~MK~NL~MK~NL~MK~NL~NL~NL~MK~NL~MK~NL~NL~MK~NL~NL~NL~NL~NL~OM~NL~OL~NL~OL~OL~OL~OL~OL~OL~OM~OM~PN~OM~PN~PN~PN~PN~PN~PN~PN~PN~QO~QO~QO~PN~QO~PN~QO~QO~QO~RP~QO~QO~QO~QO~QO~RP~RP~QO~QO~QO~QO~RO~RO~RP~RP~RP~RP~SP~SP~SP~SQ~RP~RP~RP~RP~SP~RP~SP~SP~SP~SP~SQ~SQ~SQ~SQ~SQ~TQ~SP~SQ~TQ~TQ~SQ~SQ~TQ~TQ~TQ~SQ~TQ~TQ~SP~TQ~TQ~SQ~SQ~SP~SQ~SQ~SQ~SQ~TQ~TQ~TQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~TQ~SP~SQ~SQ~SQ~RP~SQ~SQ~SQ~SQ~SQ~SQ~SP~SQ~SP~SP~RP~SP~SP~RP~SP~RO~RO~RP~RP~RP~RP~RO~SP~RO~RO~RO~RO~RO~RP~RO~RO~RO~RP~QO~QO~QO~QO~RO~RO~QO~QO~QO~PN~QO~QO~QO~QO~QO~PN~PN~PN~PN~PN~QO~PN~PN~PN~PN~PN~PN~PN~PN~PN~PN~PN~OM~OM~OM~PM~PM~PM~OM~OL~OM~PM~OL~OM~OL~OM~OM~OL~NL~OM~NL~NL~NL~NL~NL~NL~NL~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~LJ~LJ~LJ~LJ~LJ~LJ~KI~LJ~KI~KI~KI~LJ~LJ~KI~KI~KI~KI~KI~JH~KI~KI~KI~KH~KH~KH~KH~JH~KH~KH~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~JH~JH~IG~IG~IG~IG~IG~IG~HF~IG~HF~IG~IG~HF~HF~IF~HF~HF~HE~HE~HE~GE~GE~GE~GE~GE~HF~GE~GE~GE~GE~GE~GE~GE~GD~FD~FE~GE~GE~FD~GD~FC~FD~FC~FD~FC~EC~FD~FC~FC~FC~FC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DA~CA~CB~CA~DB~DA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~B@~C@~C@~C@~CA~C@~C@~CA~B@~B@~B@~B@~B@~B@~C@~B@~B@~B?~B@~B?~B@~A?~A?~A?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~A?~B@~A?~A?~A?~A?~A?~A?~A>~A?~A>~A?~A>~@>~@>~A>~A>~A>~A>~A>~A>~@>~A>~A>~A>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~A?~A?~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~?=~?=~@>~@>~?=~@>~?=~?=~@>~?=~?=~@>~?=~?=~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~?=~@>~@>~?=~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~?=~@>~@>~@>~@>~@>~@>~A>~@>~A>~@>~@>~A>~@>~A>~@=~A>~A?~A>~@>~A>~A>~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A>~A>~A?~A>~A>~A>~A?~A?~A?~A>~A>~A?~A?~A>~A?~@>~A?~@>~A?~A?~A?~A?~A?~A?~A>~A>~A?~A?~A>~A>~A>~A>~A?~A?~A?~A>~A>~A?~A?~A?~A>~A>~@>~A?~A?~A?~A>~A?~A>~A?~A>~A?~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A?~B?~A>~A?~A?~A>~A?~A>~A?~A>~A>~A>~A?~A>~A>~A>~A?~A>~A>~A>~A>~A>~A>~B?~A>~A?~A>~B?~A?~B?~B?~A>~A>~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A>~A>~A?~A?~A?~A>~B?~B?~A>~A>~A>~A>~A>~A?~A>~A>~A>~A?~@>~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~@>~A?~A?~@>~@>~A?~@>~@>~A?~A>~@>~@>~A>~A>~@>~@>~A>~A>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@=~@=~@=~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~@=~@=~@>~@=~@>~@>~@>~@=~@=~@>~@>~@>~A>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~A>~A>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@?~@>~@>~@>~@>~@>~A>~A>~A>~A>~A>~A>~A?~A?~A>~A>~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~A?~A?~B@~B@~B@~B@~B@~B@~B@~B?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~CA~B@~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~DB~DA~DA~DA~DA~DB~DB~DB~DB~DB~DB~EB~EB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~FD~EC~FD~FD~FD~GD~FD~GD~FD~GE~FE~FE~FE~GE~GE~GE~GE~GE~GE~HE~GE~GE~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~IF~IF~IF~IG~IG~IG~IG~IG~IG~IG~JH~JH~JH~JH~JH~JH~JH~JH~KI~JH~KI~JH~KI~KI~KI~JH~JH~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~KI~LJ~LJ~LJ~LJ~LJ~MK~LJ~LJ~LJ~LJ~MK~MK~MK~MK~MK~MK~MK~MK~NL~MK~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~OM~NL~NL~NL~OM~NL~OL~OM~OM~OM~OM~OM~OM~PM~PM~PM~PN~PN~PN~PN~PN~PN~PN~PN~PN~QO~PN~QN~QO~QO~QO~QO~QO~QO~RO~QO~QO~QO~QO~RO~RO~RO~RO~RO~RO~RO~RP~SP~RP~SP~RP~SP~RO~SP~SP~SP~SP~RP~SQ~SP~SP~SQ~SQ~SQ~SQ~SQ~SQ~SQ~TR~SQ~TR~TR~TR~TR~TQ~TR~TR~TQ~TR~TQ~TR~TR~TR~TR~TR~TQ~TR~TR~TR~TR~TR~TQ~TQ~TQ~TQ~TQ~TR~TR~TR~TR~TQ~TQ~TR~TQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~TR~TR~SQ~SQ~SQ~SQ~SQ~SQ~TQ~SQ~SQ~SQ~SQ~SQ~SQ~SP~SQ~SQ~SQ~SQ~SQ~SQ~SP~SQ~SQ~SQ~SQ~SP~SP~RP~SP~SP~RO~RP~SP~RP~SP~RO~RP~RO~RO~RP~RO~RO~RO~RO~RO~RO~RO~QO~QO~QO~QN~QN~QN~QO~PN~PN~QO~QN~QN~QN~QN~QN~QN~PN~PN~PN~PN~OM~OM~PN~PN~PN~OM~PN~OM~OM~OM~OM~PN~OL~PM~OL~OM~NL~OL~OL~NL~OL~NL~NL~NK~NK~NL~NK~NK~MK~MK~MK~MK~MK~MK~MK~MK~MK~LJ~MJ~LJ~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~KI~LJ~LJ~LJ~KI~LJ~KI~KI~KI~KI~KI~KI~KI~KI~KI~JH~JH~KI~KI~KI~KI~JH~JH~JH~JH~JH~JH~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~HE~HE~GE~GE~HF~GE~GE~GE~GE~GE~GE~GD~GD~GE~GE~GE~GE~GD~FD~FD~FD~GE~FD~FD~FD~EC~FD~FD~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~DB~CA~DB~DA~DA~CA~CA~CA~CA~CA~CA~CA~CA~B@~CA~CA~C@~C@~CA~C@~CA~CA~C@~B@~B@~B@~B@~C@~C@~B?~B@~C@~B@~B@~B@~A?~B@~B@~B@~B@~B@~B?~B?~B@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A>~A?~A?~A>~A?~A?~@>~A?~A>~A>~A>~A>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~A>~@>~A>~A>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@=~@=~@>~@>~@=~@>~?=~@>~@>~@>~@=~@=~@>~@>~@=~@>~@>~@>~?=~?=~?=~@>~@>~@>~@>~@>~@=~@>~@>~@=~?=~@=~@=~@=~@=~@=~@=~@>~@>~@>~?=~@>~@>~@>~?=~?=~@>~@>~@>~@>~@>~@>~@>~@>~A>~A>~@>~A>~A>~@>~A>~@>~@>~A>~@>~A?~@>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A>~A>~A?~A>~A>~A>~A>~A?~A?~A?~B?~A>~A>~A>~B?~A?~A>~B?~B?~A>~A?~A?~A>~A>~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A?~A>~A?~B?~A>~A?~A?~A>~A?~A?~A?~A?~A>~A>~A?~A>~A>~A>~A?~A>~A>~A?~A?~A>~A?~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A?~A?~A>~A>~A>~A?~A?~A?~A>~A>~B?~A?~A>~A?~A?~A>~A>~A?~A>~A>~A?~A?~A?~A?~B?~B?~A?~A?~A?~B?~A>~A>~B?~B?~A?~B?~B?~B?~A?~B?~B?~A>~A>~A?~A?~A>~A?~A>~A>~A>~A?~A?~A?~A?~A>~A?~A?~B?~A>~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A?~A>~A>~A?~A?~A?~A>~@>~A?~A>~A>~A?~A?~A?~A>~A>~A>~@>~@>~@>~A>~A>~A>~A?~@>~A>~@>~@>~@>~A>~A>~A>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~A>~@=~@=~@>~@>~@=~@=~@=~@>~@=~@>~A>~A>~A>~@=~@=~@=~@=~@>~@=~@=~@=~@=~@>~@>~@>~@>~@>~@=~@>~@=~A>~@=~@=~@>~@>~@=~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@=~A>~A>~A>~A>~@>~@>~@>~@>~@>~@>~A?~A?~A?~A>~@>~@>~A?~A?~A>~@>~A>~@>~A?~@>~A?~@?~A?~A?~A?~A?~A>~A>~A?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~A?~B@~B@~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~CA~B@~B@~B@~CA~CA~CA~CA~B@~CA~CA~CA~CA~CA~CA~CA~CA~CA~DB~DA~DB~DB~DA~DA~DB~DA~DB~DB~DB~CA~DB~DB~DC~DC~DC~EC~EC~DB~EC~EC~EB~EC~EC~EC~FD~FD~EC~FD~FD~FD~FD~FD~FD~FD~FD~FD~FD~FE~FD~FE~GE~GE~GE~GE~GE~GE~GE~GE~HF~HF~HF~GE~HF~HF~HF~HF~HF~HF~HF~HF~IG~IG~IF~IG~IG~IG~IG~JG~IG~IG~JH~IG~JH~JH~JH~JH~JH~KI~JH~KI~KI~KI~KI~KI~JH~KI~KI~KI~KI~KI~KI~LJ~KI~KI~KI~KI~LJ~MK~LJ~LJ~LJ~MK~LJ~LJ~MK~MK~MK~MK~LJ~MK~MK~MK~MK~MK~NL~NL~MK~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~OL~OL~OL~OL~OL~OM~OL~OM~OM~OM~OM~PN~OM~OM~PN~PN~PN~PN~PN~PN~PN~PN~PN~QO~QO~QO~QO~QO~QO~PN~QO~QO~QO~RO~RO~RO~QO~RO~RO~RO~RO~RO~RO~RO~RO~RO~RP~RP~RP~SQ~SP~SP~SP~SP~SP~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~TR~SQ~TR~TR~TR~TR~TR~TR~UR~TR~TR~TR~US~UR~US~TR~TR~UR~UR~TR~TR~US~TR~TR~TR~UR~UR~UR~UR~UR~UR~UR~UR~UR~UR~TR~TR~UR~TR~TQ~UR~TR~UR~UR~TR~TQ~TR~TR~TR~TR~TQ~TR~TR~TR~TR~TQ~TQ~TQ~TQ~SQ~SQ~SQ~SQ~TR~TR~SQ~SQ~SQ~SQ~SQ~TR~SQ~SQ~RP~RP~SQ~SQ~SQ~SQ~SP~SQ~SP~SP~SQ~SP~RP~SQ~RP~RP~RP~RP~RP~RP~RP~RO~RO~RO~RO~RP~RO~RO~RO~RO~QO~RO~QN~RO~QO~QO~QO~QO~PN~PN~PN~PN~PN~PN~QO~QN~QN~PN~PN~OM~PN~OM~PN~PM~OM~OM~OM~PN~PM~PM~OM~OM~OL~OM~OL~OL~OM~OL~OL~NL~NK~NL~NL~NK~MK~MK~NL~MK~MK~MK~MK~MK~MK~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~LJ~KI~KI~LJ~LJ~KI~KI~KI~KI~KI~KI~KI~KI~KI~KH~JH~KH~JH~JH~JH~JH~JH~JH~JH~IG~IG~IG~IG~IG~IG~IF~IG~IG~HF~IG~HF~HF~IG~HF~HE~HF~HF~HF~HF~HF~HE~GE~HF~GE~HF~GE~GE~GE~GE~GE~GE~FD~GE~FD~FD~GE~FD~FD~FD~FD~FD~GE~FD~FD~FD~EC~EC~FD~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DA~DB~DB~DA~DB~DA~DA~DA~DA~DA~DA~CA~CA~DA~CA~DA~CA~CA~C@~CA~CA~CA~DA~B@~B@~CA~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~B@~B@~B@~B@~B@~A>~B?~B?~A?~A?~B?~B?~B?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A>~A>~A>~A>~A?~A?~A>~A?~A?~A?~A>~A>~A>~A>~A>~A>~@>~A>~A>~@>~A?~A>~A>~@>~@>~@>~A>~@>~@>~@>~@>~A>~@>~A>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@=~@>~@>~@=~@>~@>~@>~@>~@=~@=~@>~@>~@>~@>~A>~@=~@=~@=~@=~?=~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~@=~@>~@>~@=~@=~@=~@=~@>~@=~@=~@=~@>~@>~@=~@=~@=~@>~A>~A>~@>~@>~A>~A>~@=~A>~@>~A>~A>~A>~@=~@>~@>~A>~A>~@>~A>~@>~@>~A?~A?~A>~A>~A>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A>~A?~A?~A>~A?~A?~A>~A>~A?~A?~A?~A>~A?~A>~A?~A>~A>~A?~A>~A>~A?~A?~A>~A?~A>~A?~A?~B?~A>~A>~A?~A?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A?~A?~A?~B?~A?~A>~A?~A>~A?~A?~A?~A?~A?~B?~A?~A>~B?~A?~B?~A>~A>~A?~A?~A?~A?~A>~A?~A>~A?~A?~A>~A?~A>~A?~A?~A?~A>~A?~A?~A?~A?~A?~A?~A?~A>~A?~A?~A?~A>~A?~A>~A>~A>~A?~A>~A?~A?~A?~A?~A?~A?~A?~B?~B?~A?~A>~A?~B?~B?~A?~A?~B?~B?~B?~B?~B?~B?~B?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A?~A?~A>~A>~A?~A>~A?~A?~A?~A?~A?~A?~A?~A>~A>~A>~B?~A>~A>~A?~A?~B?~A>~A>~A>~A?~B?~@>~A>~A>~@>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~A>~@=~@=~@>~@=~@=~@=~@>~@>~@=~@=~@=~@>~@>~@>~@>~@=~@=~@>~A>~@>~@=~@=~@=~@=~@>~@=~@=~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~A?~A?~@?~@?~@>~A>~A>~A?~A?~A>~A>~A>~A>~A>~A>~A?~A?~A?~A?~A>~A>~A>~A?~A?~A>~A?~A?~@>~A?~A?~A?~A?~A?~A>~A?~A?~A?~A?~B@~A?~B?~B@~A?~B?~A?~B@~A?~B?~B@~A?~B@~B?~B?~B?~B@~B@~B@~B?~B@~B@~B@~B@~B@~CA~CA~B@~B@~CA~B@~B@~CA~C@~CA~CA~CA~CA~DB~DB~CB~DB~DA~DA~DA~DA~DA~DB~DB~DB~DB~DC~DC~DC~EC~EC~DB~EC~EC~EC~EC~EC~DB~EC~EC~EC~FD~EC~EC~EC~FD~FD~FD~FD~FD~FD~FE~FD~FD~FD~FD~FE~GE~FD~GE~GD~FD~GE~GE~GE~GE~GE~HF~GE~GE~HE~HE~HE~IF~IF~IG~IF~HF~IG~IG~IG~IG~IG~IG~IG~JH~JG~JH~JH~IG~JH~IG~IG~JH~JH~JH~JH~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~KI~KI~KI~LJ~LJ~LJ~LJ~MK~LJ~LJ~LJ~MK~LJ~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~NK~NL~MK~NL~NL~NL~NL~OM~NL~OL~NL~OL~OL~OL~OL~OL~OM~OL~PM~PM~PM~PN~PN~PN~PN~PN~PN~PN~PN~PN~PN~PN~QO~QO~QO~QO~QO~QO~QN~QO~RO~RO~RO~QO~RO~RO~RO~RO~RO~SP~RP~RP~RP~RO~RP~RP~SP~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~TR~TR~TR~TR~TR~TR~SQ~TR~TR~TR~TR~TR~TR~UR~TR~TR~TR~UR~UR~US~UR~US~UR~UR~US~UR~US~UR~US~UR~TR~UR~UR~UR~UR~UR~UR~UR~UR~US~VS~UR~UR~UR~UR~UR~UR~UR~UR~UR~UR~UR~UR~UR~TR~UR~UR~TR~UR~UR~UR~UR~UR~TR~UR~UR~UR~TQ~UR~UR~TR~UR~UR~TR~TR~TR~TR~TR~TR~TR~TR~TR~TQ~SQ~TR~TR~TR~TQ~TQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SP~SQ~SQ~SQ~RP~RP~SP~RP~SP~RP~SP~RO~RP~RP~RP~RP~RO~RO~RO~RO~RO~RO~RO~RO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~PN~PN~PN~QO~QN~QN~PN~QN~PN~QN~PN~PM~OM~PN~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~NL~OM~OM~NL~NL~NL~NL~MK~NL~NL~NL~MK~MK~MK~MK~MK~MK~MK~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~LJ~LJ~KI~KI~KI~KI~KI~KH~KI~KI~KI~JH~JH~KI~KH~JH~JH~IG~JH~JH~JH~JH~IG~IG~IG~IG~IG~IG~IG~IF~IF~IG~IF~IF~HF~IF~HF~HF~HF~HF~HF~HF~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~FD~GE~GE~GD~GD~GD~GD~FC~GD~GD~FC~FC~FD~FD~FC~FC~FC~FC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~CB~DB~CA~CA~DA~CA~CA~DA~CA~DA~DA~CA~DA~C@~C@~C@~DA~C@~DA~C@~C@~CA~C@~C@~C@~B@~B@~CA~B@~B@~B@~B@~B@~B@~B@~B@~A?~A?~B@~A?~A?~A?~A?~B?~B@~A?~B@~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A>~A?~A?~A?~A?~A?~A>~A?~@>~@>~A>~A>~@>~A?~@>~@>~@>~@>~@>~@>~A>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~A>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~?=~@>~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@=~@>~?=~@>~@>~?=~@>~@=~@>~@>~@>~@=~@>~@>~@>~?=~@>~@>~@>~@>~@=~@>~@>~@=~@=~@>~@>~@=~@=~A>~@=~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~@>~A>~A>~A>~A>~A>~A>~A?~A?~A>~A>~A?~A>~A>~A>~A?~A>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A?~A?~A?~A?~A?~A?~A?~A>~A?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~B?~A?~B?~B?~A?~B?~A?~B?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A?~A?~A?~A?~B?~B?~A?~A>~B?~B?~B?~B?~B?~A?~B?~B?~A?~A?~A?~A>~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~B?~B?~A?~B?~A?~B?~B?~A?~B?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A?~A?~B?~B?~A?~A?~B?~A?~A?~B?~A?~B?~B?~B?~A?~B?~B?~A?~A?~A?~A?~A?~B?~B?~A?~A?~B?~B?~B?~B?~A?~B?~A?~A?~A?~A?~A?~B?~A?~B?~B?~B?~B?~A?~B?~A?~B?~A>~A?~B?~A>~A?~A>~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A>~A>~A?~A>~A?~A?~A?~A?~A>~A>~A>~A>~A>~A>~A>~A>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@=~@>~@=~@>~@>~@>~@>~@=~@>~@=~@>~@=~@>~@=~@=~@=~@>~@>~@=~@>~@=~@>~A>~@>~@=~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~A?~A>~A>~@>~@>~A>~A?~@>~A?~@>~A?~A?~A?~A?~@>~A?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~B?~A?~B?~A?~B@~B@~A?~A?~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~CA~B@~B@~B@~B@~B@~CA~B@~CA~CA~CA~CA~CA~CA~CA~DB~CA~CA~CA~CA~CA~DA~DA~DB~CA~DB~DB~DB~DB~DB~EC~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~EC~DB~EC~FC~FC~FD~FD~FD~FD~FD~FD~FD~FD~GE~GE~GE~FD~GE~GE~GE~GE~GE~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~IF~IG~IG~IG~IG~IG~IG~IG~IG~JH~JH~JH~JH~JH~JH~JH~JH~KH~JH~JH~KH~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~MK~MK~MK~MK~MK~MK~MK~NL~NL~NL~NL~NK~NK~MK~NK~NL~OL~OL~OL~OL~OL~OM~OM~OM~OM~OM~PM~OM~OM~PN~PN~OM~PN~PN~PN~PN~PN~PN~QO~QO~PN~PN~QN~QN~QN~QN~QN~QO~QO~QO~RO~QO~QO~QO~RO~RP~RP~RP~RP~RP~RP~RP~RP~SQ~RP~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~TQ~TQ~SQ~TR~TR~TR~TR~TR~TQ~TR~UR~TQ~UR~UR~TQ~UR~UR~UR~US~US~US~UR~US~US~UR~VS~US~VS~VS~VS~VS~UR~US~US~US~US~US~VS~VT~UR~US~VS~VS~US~VS~VS~VS~VS~VS~VS~UR~VS~VS~UR~US~US~US~UR~UR~UR~UR~US~UR~US~US~US~US~UR~UR~VS~US~TR~UR~UR~UR~UR~UR~US~VS~UR~UR~TR~TR~TR~TR~UR~UR~US~TR~TR~TQ~TR~TR~TQ~TQ~TR~TQ~TQ~TQ~TR~TQ~TQ~TQ~TQ~TQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SP~RP~SQ~SP~RP~RP~RP~SQ~RP~RO~RP~RP~RP~RP~RO~RO~RP~RO~RP~QO~QO~QO~QO~RO~QN~QN~QO~QO~QO~QO~QO~PN~QO~PN~PN~PN~PN~QN~PN~PN~PN~PN~PN~PN~OM~PM~OM~OM~OM~OM~OM~OM~OM~OM~OL~NL~NL~NL~NL~NK~NK~NK~NL~MK~NL~MK~MK~MK~LJ~MK~LJ~LJ~LJ~LJ~LJ~MK~MK~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~LJ~KI~KI~LI~KI~KI~KI~KI~KI~KI~JH~KI~JH~KI~JH~JH~JH~JH~JH~JH~IG~IG~IG~IG~IF~IG~IF~IG~HF~IG~HF~HF~HF~IG~HF~HF~HF~HF~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~FD~FE~FD~FD~FD~ED~FD~FD~FD~FD~EC~FD~EC~FC~EC~EC~EC~EC~FC~EC~EC~EC~EC~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CB~CB~CB~DB~DA~CA~CA~CA~CA~CA~CA~C@~CA~C@~C@~C@~C@~CA~CA~C@~C@~C@~C@~C@~B@~C@~C@~C@~B?~B?~B@~B?~B?~C@~B?~B?~B?~B?~B?~B?~A?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A?~A>~A?~A?~A>~A?~A?~A>~A?~A?~A>~A>~@>~@>~A?~A?~A?~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@=~@>~@>~@>~@>~@>~@>~@=~@>~@=~@>~@>~@=~@>~@>~?=~@>~@>~@>~@>~@>~?=~@>~@>~@=~@=~@=~@>~@=~@>~@>~@>~@>~?=~@=~@>~@>~@>~@>~@>~@>~@>~@>~A>~A>~A>~@>~@>~A>~@>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~@>~A>~A?~A>~A>~@>~A?~A>~A?~A>~A?~A>~A>~A>~A?~A>~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A>~A>~A>~A>~B?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~B?~B?~A>~B?~B?~A>~B?~B?~A>~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~B?~A?~A?~B?~A?~A?~B?~A?~A>~B?~A?~B?~B?~B?~B?~B?~B?~A?~A?~B?~B?~B?~B?~B?~B?~B?~A?~A?~B@~A?~A>~A?~B?~B?~B?~A?~A?~B?~B?~B?~B?~B?~B?~B?~B?~A?~B?~A?~A?~B@~A?~A?~A?~A?~B?~B?~A?~B@~B?~B?~B?~A?~A?~A?~B?~B?~A?~B@~B@~B?~B?~A?~B?~B?~B?~A?~A?~B?~B?~A?~A?~B?~B@~B?~B?~B?~B?~B?~B?~B?~B?~A?~B?~B?~A?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~B?~B?~A?~B?~B?~A?~A?~A?~B?~B?~A>~A?~B?~A?~A?~A?~B?~A?~B?~A>~B?~B?~A?~B?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A>~A>~A>~A>~A>~@>~@>~@>~A>~@>~@>~@>~@>~@>~A>~A>~@=~@=~A>~@>~@=~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~A>~A>~@>~@>~@>~@>~A>~A>~A>~A>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~A?~A>~@>~@>~@>~A>~A>~A?~A>~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~A?~B@~A?~B@~A?~B@~A?~A?~A?~B@~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~CA~B@~B@~CA~B@~CA~CA~CA~CA~B@~CA~CA~CA~CA~CA~CA~DA~CA~CA~DA~DA~DB~CA~CA~DA~DB~DA~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EB~EC~EC~EC~EC~EC~EC~EC~FD~FD~EC~FC~FD~FC~FD~FD~FD~GD~FD~GD~GD~GE~GE~FD~GE~GE~GE~GE~GE~GE~HF~GE~HF~GE~GE~HF~HF~HF~HF~HF~HF~HF~IG~HF~HF~IG~IG~IG~IG~IG~IG~IG~JH~IG~JH~JH~JH~JH~JH~KH~KH~JH~JH~KI~JH~KI~JH~KI~KI~KI~KI~KI~KI~LJ~KI~LJ~LJ~LJ~MK~LJ~LJ~LJ~LJ~LJ~MK~MK~MK~MK~MK~NL~MK~MK~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~OL~OL~OM~OM~OM~PM~PM~OM~PM~PM~PM~PM~PM~PM~PN~PN~PN~PN~PN~PN~PN~PN~PN~PN~PN~PN~QO~QN~QO~QO~QO~QO~RO~RO~QO~RP~RP~RP~RP~SP~RP~SQ~SQ~SQ~SQ~RP~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~TQ~TQ~TQ~TR~TR~UR~TQ~UR~TR~TR~TQ~UR~UR~TR~UR~UR~UR~UR~US~UR~VT~US~VS~VS~VS~VS~VS~VS~VT~VS~VT~VS~VS~VS~US~VT~VT~VT~VT~US~VT~VT~VT~VT~VT~VT~VS~VT~VS~VT~VS~VT~VT~US~VS~VS~VT~VT~VT~VT~VT~VS~VT~VT~US~VT~VS~US~VS~VT~VS~VS~VS~VS~US~US~US~US~VS~US~VS~UR~US~US~VS~UR~UR~US~US~UR~UR~UR~UR~UR~UR~TR~UR~UR~TQ~UR~TR~UR~UR~TQ~TQ~TQ~TQ~TQ~TQ~TQ~TR~TR~TR~TR~SP~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SP~RP~RP~RP~RP~RP~RO~RO~RO~RO~RO~QO~QO~QO~QO~QO~RP~RO~QO~RO~RO~QO~QO~QO~QO~QN~QO~QO~PN~QO~PN~QO~PN~PN~PN~PN~OM~PN~PN~OM~OM~OM~OM~OL~OM~OL~NL~NL~NL~NL~NL~NL~NL~NL~MK~MK~MK~MK~MK~MK~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~LI~KI~KI~KI~KI~KI~KH~KI~KH~KH~JH~KH~JH~JH~JH~JH~JH~JG~JG~IG~IG~IG~IG~IG~IG~IG~IG~HF~HF~IF~HF~HF~HE~HF~HE~HF~HF~HF~GE~GE~GE~GE~GE~GD~GE~GD~FD~GE~GD~FD~FD~FD~FD~FD~FD~FD~FC~FD~FD~EC~EC~EC~EC~FC~FC~EB~EC~EB~EB~EB~EB~EB~EB~EB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CA~CA~DA~CA~CA~DA~CA~CA~CA~CA~CA~CA~CA~C@~C@~CA~CA~C@~C@~C@~C@~C@~C@~B@~B@~B@~B@~B@~B@~B@~A?~B@~B?~B?~B?~B?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A>~A?~A>~A>~A>~A?~A>~A>~A>~A?~A>~A>~A?~A?~@>~@>~@>~@>~@>~A>~A>~@>~A>~A>~A>~A>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@=~@=~@>~@>~@>~@>~@>~@=~@=~@=~?=~@>~@>~@=~?=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~?=~A>~@>~A>~@>~?=~@>~@>~@>~@>~@>~@>~@>~?=~@>~@>~@>~A>~@=~@>~@>~A>~@>~A>~@>~@=~A>~A>~A>~A>~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~B?~A>~A?~A>~A?~A>~A>~A>~A>~A>~A?~A?~A?~A?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A?~A?~B?~A?~A?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~A?~B@~A?~B?~B@~B?~B?~B?~A?~B?~B?~A?~B?~B?~B?~B?~B?~B?~B?~A?~B?~B?~B?~B@~B@~A?~B?~B?~B?~A?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~B?~A?~A?~B?~B?~B?~B?~B@~B?~B?~B?~B?~B?~B?~B?~B@~B?~B?~B@~B@~B@~A?~B?~B?~B?~B?~A?~B?~B?~B?~B?~B?~B?~B?~B?~A?~B?~A?~B?~B?~A?~B?~A?~A?~A?~A?~A?~A?~B?~A?~A?~A?~A?~B?~A?~B?~A?~A?~A?~A?~A?~B?~A>~A>~B?~A?~B?~B?~A?~A>~A?~A?~A?~A?~A?~A?~A?~A>~A>~A?~A>~A?~A>~A>~A>~A>~A>~A>~A>~A?~A>~A>~@>~A>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~A>~A>~@>~A>~A>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~A>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~A>~A?~A>~A>~@>~@>~@>~@>~@>~@>~@>~A?~A?~A?~@?~@?~A?~A?~A?~@?~A?~A?~@>~A?~A?~A?~A>~A>~A?~A>~A?~A?~A?~B?~A?~A>~B?~A?~A?~A?~A?~A?~A?~A?~A?~B@~A?~A?~A?~A?~B?~A?~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~CA~B@~B@~B@~CA~CA~C@~CA~CA~CA~CA~DA~CA~DA~DB~DB~DB~DB~DB~DA~DB~DB~DB~DA~DB~EB~EB~DB~DB~EC~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~FD~EC~FC~FD~FD~FD~FE~FE~FD~GD~FD~FD~GE~FD~GE~GE~GE~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~IF~HF~IF~IF~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~JH~JH~JH~JH~JH~JH~KI~KI~JH~KI~KI~KI~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~MK~MK~MK~MK~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~OM~NL~NL~NL~OM~OM~OL~OL~OM~PM~PM~OM~PM~PM~PN~PM~PN~PN~QN~PN~PN~QO~QO~QO~PN~PN~QO~QN~QN~QN~RO~RO~RO~RO~RO~RO~RO~RO~RP~RP~RP~SP~SP~SP~SP~SQ~SQ~SP~SQ~SQ~SQ~SQ~SQ~TR~TQ~TR~TQ~TQ~TQ~TR~TQ~TQ~UR~TQ~UR~UR~TR~UR~UR~UR~UR~US~UR~UR~VS~UR~US~UR~US~US~US~VS~VS~VS~VS~VT~VS~VS~VS~VT~VT~VS~VT~VT~VT~VT~VT~VT~VT~WT~WU~WT~WU~WT~WT~WU~WT~WU~WU~WT~VT~WT~WU~WT~WT~WT~WT~WT~WT~WU~VT~WU~WT~WT~VT~VT~VT~WT~VT~WT~VT~WT~VT~VT~VT~WT~WT~WT~WT~VT~VT~VT~VS~VT~WT~VT~VS~VS~VT~VT~VS~VS~VS~VS~VS~US~US~UR~US~UR~TR~US~TR~TR~UR~UR~UR~UR~UR~UR~UR~TR~TR~TR~TR~US~TR~TR~TR~TR~TR~TR~TQ~TQ~TQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~RP~RP~RP~SQ~SQ~SQ~RP~RP~RP~RP~RP~RP~RP~QO~RP~RO~RP~RP~RP~RO~RO~QO~QO~RO~QO~QO~QO~QO~QO~QO~QO~QN~QO~PN~PN~PN~PM~PN~PM~PM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~NL~NL~MK~NL~NK~NL~MK~MK~MK~MK~MK~MK~MK~LJ~LJ~MK~LJ~LJ~MK~LJ~LJ~LJ~LJ~KI~KI~KI~KI~KI~LI~KI~KI~KI~KI~KH~KI~KI~KI~KI~KI~JH~JH~JG~IG~JG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IF~IG~HF~HF~HF~HF~HF~HE~HE~HE~HE~HE~GE~GE~GE~GE~GE~GE~GE~FD~FD~FD~GE~FD~FD~FD~GD~FD~GD~FD~FC~FC~FC~FD~EC~FC~FC~FC~EC~EC~EC~EC~EC~EC~EC~DB~DB~DB~DB~DB~EC~DB~DB~DB~DB~CA~DB~DB~DA~CA~DA~DA~CA~C@~DA~DA~CA~CA~CA~CA~CA~C@~CA~C@~CA~C@~C@~B@~B@~C@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B?~A?~B?~B?~B?~B?~B?~A?~B?~A?~A?~B?~A?~A?~A?~A?~A?~A?~A>~A>~A>~@>~A?~A>~A>~@>~@>~A?~A?~A?~A>~A>~A?~A>~@>~@>~A?~@>~A?~A?~A?~@>~@>~@>~A?~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~A>~A>~@>~@=~@=~@>~@>~@>~@>~@>~@=~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~A>~@>~A>~A>~A>~@=~A>~@=~@=~@>~@>~@>~@>~@>~A>~A>~@>~@>~A>~@>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A>~A>~A>~A?~A>~A?~B?~B?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A?~A?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~B?~B?~B?~B?~B?~B@~B?~B@~B?~B?~B?~B?~B?~B?~B?~C@~C@~B?~B@~B?~B@~B?~B?~B@~B?~C@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~C@~B?~B?~B?~C@~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~C@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~B@~B?~B@~B@~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~C@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~B?~B?~B?~A?~A?~A?~A?~A?~A?~A?~B?~B?~A?~A?~A?~A?~A?~A?~B?~A?~A?~A?~A?~A?~A?~A>~B?~B?~B?~B?~A>~A>~A>~B?~A?~B?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~@>~A>~A>~A>~A>~A>~@>~A>~@>~@>~A>~@>~@>~A>~A?~@>~@=~@>~@>~@>~@>~@>~@>~A>~@>~A>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@=~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~A>~A>~@>~A?~A?~A?~A?~A?~A?~A?~@>~@>~A?~A?~@>~@>~@>~@>~@>~A>~A>~A?~A?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~B@~A?~B@~A?~A?~A?~A?~B?~A?~B@~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~CA~CA~B@~CA~CA~CA~CA~CA~CA~CA~CA~CA~DA~DB~CA~DB~CA~DB~DB~DB~DB~DB~DB~DB~DB~EB~EC~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~FC~FD~FD~FD~FD~FD~FC~FD~FD~GD~GD~GD~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~HF~GE~GE~HF~HF~HF~HF~HF~IG~HF~IF~IG~IF~IG~IG~IG~IG~JH~IG~IG~IG~JH~JH~JH~JH~JH~JH~JH~JH~KI~KI~KI~KH~KI~KI~KI~KI~KI~LJ~LJ~LJ~LI~LJ~LJ~LJ~LJ~MK~MK~MK~MK~MK~MK~MK~NL~NL~NL~NL~NL~NL~NL~NL~OL~NL~NL~OL~OM~OL~OL~OM~PM~OM~PM~PN~PN~PN~PN~OM~PN~PN~PN~PN~PN~PN~QO~PN~PN~PN~QN~QN~QO~QN~RO~QO~RO~RO~RO~RO~RP~RP~RP~RP~RP~RP~SP~SP~RP~SQ~SQ~SQ~SP~SQ~TR~SQ~SQ~SQ~TQ~TQ~TQ~TQ~TQ~TR~TR~TR~UR~TQ~TR~UR~UR~UR~UR~UR~UR~US~UR~US~US~VS~VS~VS~US~VS~VS~VS~VS~VS~VS~VT~VT~VT~WT~WT~WT~WT~VT~VT~VT~WT~WT~WT~WT~WT~WT~WT~WT~WT~WT~WU~XU~XU~XU~XU~XU~XU~XU~WU~WU~XU~WU~XU~XU~XU~WU~WT~WT~WT~WU~XU~WU~WT~WU~WT~WU~WU~WU~WT~WT~WT~WU~WU~WU~WT~WT~XU~WT~WT~WT~XU~VT~WU~VT~VT~VT~WT~VT~VT~VT~VT~VS~VS~VT~WT~VT~VT~VT~VT~US~US~US~US~VS~US~US~US~US~UR~UR~US~UR~US~TR~TR~UR~UR~UR~UR~TR~TR~TR~UR~UR~TR~TR~TR~TR~TQ~TQ~TQ~SP~SP~TQ~TQ~SQ~SP~SQ~SQ~SQ~SQ~SQ~SQ~SP~SP~SQ~SP~SP~SP~SP~RO~RP~RP~RP~QO~QO~QO~QO~QO~QO~QO~QO~QO~QO~QN~QN~QN~QN~PN~PN~PN~PN~PN~PN~PN~PN~OM~PN~OM~PN~OM~OL~PM~OL~OL~NL~NL~NL~NL~NL~NK~NK~NK~NL~NL~MK~NL~MK~MK~MK~MJ~MJ~MK~MK~MK~LJ~LJ~MJ~LJ~LJ~LJ~LJ~LJ~LI~LJ~KI~LJ~LI~KI~KI~KI~KI~KI~KH~KH~JH~JH~JH~JG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IF~IF~IF~HF~IF~HF~HF~HE~HF~HE~HE~GE~HE~HE~GE~GE~GE~GD~GE~GE~GE~GE~FD~GE~FD~FD~FD~FD~FD~FD~EC~FD~FD~FC~FD~FC~FC~FC~EC~FC~EC~EC~EC~EC~EC~EB~DA~DB~DB~DB~DB~DB~DB~DB~DA~DB~CA~DA~DB~DA~DA~DA~DA~DA~DA~DA~CA~CA~CA~C@~CA~C@~CA~C@~C@~C@~C@~C@~B@~C@~B@~B@~B@~B?~B?~B@~B@~A?~B?~B@~B@~A?~A?~A?~A?~B?~A?~B@~B@~B@~A?~B@~B?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A>~@>~A?~A?~@>~A?~A?~@>~A>~@>~A>~A?~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@=~@=~A>~@=~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@=~@=~@=~@>~@>~@>~@>~@>~@=~@=~@=~@=~?=~@>~@>~@>~@>~A>~@>~@>~@>~@>~A>~@>~@>~A>~A>~@=~@=~A>~A>~A>~A>~A>~A>~A>~A?~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A?~A?~A?~A?~A?~B?~A?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~B?~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~A>~B?~B?~B?~B@~B?~B?~B?~B?~B?~B?~B@~B?~B?~B?~B?~B?~B?~C@~B@~B?~B@~B?~B?~B?~B?~B?~B@~B@~B?~B@~B?~B?~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~B@~B@~B?~B?~C@~C@~B?~C@~C@~B?~B?~B?~C@~B@~C@~B?~B?~B?~B?~B?~B?~B?~B@~B?~B?~C@~B?~B?~C@~B?~B?~B?~B?~B?~B?~B?~B?~B?~C@~C@~B?~B@~B@~B?~C@~C@~B?~B?~B@~C@~B@~B@~B?~B@~B?~C@~B?~C@~C@~C@~B?~C@~C@~B?~C@~B?~B?~B?~B?~C@~C@~C@~B?~C@~C@~C@~C@~B?~B?~B?~B?~C@~B?~B?~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~B?~B?~B?~B?~A?~B?~B?~A?~A?~A?~B?~A?~A?~B?~A?~A?~A?~A?~A?~A?~A?~B?~B?~A?~B?~B?~A?~A?~A>~B?~B?~A?~A>~A>~A?~A>~A?~A?~A>~A>~A>~A>~A>~A>~A>~@>~A>~A>~A>~A>~A>~@>~@>~A>~A>~A>~A>~A>~A>~@>~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~A>~A>~A>~@>~@>~@>~@>~@>~A>~@>~@>~A>~A>~@>~@>~@>~@>~@>~A?~@>~A>~A>~A>~@>~A>~@>~A>~A>~@>~A>~A>~A>~A>~A>~A?~A?~@>~A>~@>~A>~A>~@>~@>~@>~@>~A>~A>~A>~A>~A?~A?~A?~A>~A?~A>~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~B?~B?~A?~A?~A?~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~C@~C@~C@~C@~CA~B@~C@~C@~DA~CA~CA~C@~CA~CA~CA~CA~CA~CB~DB~DB~DB~DC~DB~DB~DB~DB~DB~DB~DB~DB~EC~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~FD~FD~FD~FD~FD~FD~FD~FD~GD~GE~GE~FD~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~HF~HF~HF~HF~HF~HF~HF~IG~IF~IG~IG~IG~IG~IG~IG~IG~JH~JH~IG~IG~JH~JH~JH~JH~KI~JH~JH~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~MK~MK~NL~NL~NL~NL~NL~NL~NL~NL~NL~NL~OL~OL~OM~OM~OM~PM~OM~OM~PN~PN~PN~OM~PN~PN~PN~PN~QO~PN~PN~QO~PN~QO~QO~QO~QN~QO~RO~QO~RO~RO~RO~QO~RO~RP~RP~RP~RP~RO~RP~SP~RP~RP~SQ~SQ~SQ~SQ~TQ~SQ~SQ~SQ~SQ~SQ~TQ~TR~UR~UR~UR~UR~UR~UR~UR~UR~UR~US~VS~VS~UR~VS~US~UR~VS~VS~VT~VT~VS~VS~VT~VT~WT~VT~VT~WT~WT~WT~WT~WU~VT~WU~WU~XU~WU~WU~XU~XU~XU~XU~XU~XU~WU~WT~WU~WU~XU~WU~WT~XU~XU~XU~XU~WU~WU~XV~XU~XU~XU~YV~XU~XU~XU~XU~WU~WU~WU~XU~XU~XU~XU~XU~XV~XU~XU~XU~XU~WU~XU~XU~XU~XU~WU~XU~XU~XU~WU~WT~WU~WT~WU~WT~XU~WU~XU~WT~WT~WT~WT~WT~WT~WT~VT~VT~VS~VS~VS~VS~VS~VT~WT~VT~VT~WT~VT~VT~VT~VT~VT~VT~VS~VT~US~US~US~US~UR~UR~US~US~UR~UR~UR~UR~UR~TR~UR~UR~UR~UR~TQ~TQ~TR~TQ~TQ~SQ~TQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SP~SP~SP~SQ~RP~SP~SP~SP~SP~SP~RP~RP~RP~RO~RO~RO~RO~RO~RO~QO~QO~QO~QN~QN~QN~QN~PN~PN~PN~PN~PN~PN~PN~PN~OM~PN~PN~PN~OM~OM~OM~OM~OM~NL~NL~OM~NL~NL~NL~NK~NL~NL~MK~MK~MK~MK~MJ~MK~MK~MK~MK~MJ~LJ~LJ~LJ~LJ~LJ~LJ~LI~LJ~LI~LJ~LI~LI~LJ~LI~KI~KI~KI~KI~KH~KH~KH~KH~JG~JG~JH~IG~IG~IG~JH~IG~IG~IG~IG~IG~IG~IF~IF~HF~HE~HF~HF~HF~HF~HF~GE~GE~GE~HF~GE~GE~GE~GE~GE~GE~GD~GE~GD~GD~GD~GD~FC~GD~FC~FD~FD~FD~FD~FC~FC~FC~FC~FC~EC~EC~EC~EC~EC~DB~EC~EC~DB~EC~EC~DB~DB~DB~DB~DA~DA~DA~DA~DA~DB~DA~DA~DA~CA~DA~CA~C@~CA~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~C@~C@~B@~B@~C@~B@~B@~B@~B?~B?~B?~B?~B?~B?~B@~A?~A?~A?~A?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A>~A?~@>~@>~A>~A>~@>~@>~@>~B?~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~A>~@>~@>~@>~@>~@=~@>~@>~A>~@=~@>~@>~A>~A>~@=~@>~@>~@>~A>~A>~A>~A>~@=~@>~A>~@>~@>~@>~@>~A>~A>~@>~@=~@>~@>~A>~A>~@>~A>~@=~A>~A>~@>~A>~@>~A>~A>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A>~A>~A>~A>~A>~A?~A>~@>~A>~A?~A?~A?~A?~A?~A?~A>~A?~A?~A?~A?~B?~A?~B?~B?~B?~B?~B?~B?~B?~A>~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~C@~B?~B?~B?~B?~B?~B?~B@~B?~C@~C@~B@~C@~B@~B@~B@~C@~B@~B@~B@~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~B@~B@~B@~B@~B?~B?~B?~B?~C@~B?~C@~C@~C@~C@~B@~B@~B@~B@~B@~B?~B?~B?~C@~B?~C@~C@~C@~C@~C@~C@~C@~B?~C@~C@~C@~C@~C@~B?~B?~B?~C@~B?~C@~B@~B@~B@~C@~B@~C@~B@~B?~C@~C@~B@~C@~C@~C@~B@~C@~B@~C@~B?~B@~C@~B@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~C@~B@~B@~B@~B@~B@~C@~C@~B@~C@~B@~B@~B@~B?~B?~C@~C@~B?~C@~C@~C@~C@~C@~C@~C@~C@~B@~B?~C@~B@~B@~B?~B@~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A?~A?~A>~A?~B?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A>~A>~A>~@>~A?~@>~@>~A?~A>~@>~@>~@>~@>~@>~A>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~A?~A>~A>~A>~A>~@>~@>~@>~A>~A>~@>~A>~@>~@>~@>~A?~@>~A?~A?~@>~A?~A>~A?~A?~A>~A?~A?~A>~B?~B?~A?~A?~A?~A?~B?~B?~B?~B?~B?~A?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~C@~C@~C@~C@~C@~C@~B@~CA~CA~CA~CA~CA~CA~DA~DA~DB~DB~CB~CB~DB~DB~DB~DA~DB~DB~DB~DB~EC~DB~DB~DB~DB~DB~EC~EC~EC~EC~EC~EC~EC~EC~EC~FD~EC~EC~FD~FD~FD~GD~FD~FD~FD~GE~GE~FD~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~HE~HE~HF~HF~HF~HF~HF~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~JH~JH~JH~KH~KI~KH~KH~KI~KH~KI~KI~LJ~KI~KI~KI~KI~LJ~LJ~LJ~MK~LJ~LJ~MK~LJ~MK~MK~MK~MK~MK~NL~NL~NL~NL~NL~NL~NL~NL~NL~OM~NL~NL~NL~OM~OM~OM~OM~OM~PN~PN~PN~PN~PN~QO~QN~PN~QN~QO~PN~QO~QO~QO~QO~QO~QO~RO~RO~RO~RO~RO~RP~SP~SP~RP~RP~SP~RP~SQ~SQ~SQ~SQ~SQ~SQ~SQ~SQ~TR~TR~TQ~TR~TQ~TR~TR~TR~TR~UR~UR~UR~TQ~UR~UR~VS~VS~VS~VS~UR~VS~VS~VS~VT~VT~VS~WT~VS~VT~WT~VT~VT~WU~WU~VT~WU~WU~WU~WT~WT~WT~WU~XU~XU~XU~XU~XU~XU~WU~XU~XU~XU~XU~XU~XU~WT~XV~WU~WU~XV~XV~WU~XV~XU~XV~XU~XU~XV~XV~XV~XV~XV~XU~YV~YV~YV~XV~XV~XV~XU~XV~XV~XU~XV~XU~XV~YV~XV~XV~XU~XV~XV~XV~YV~XV~XU~XU~XV~XU~XV~XU~XU~XU~XU~XU~XU~XU~XU~XU~XU~XU~WT~XU~XU~XU~WT~WT~WT~WT~WT~WU~WU~WT~WU~WT~WU~WU~WT~WT~VT~VT~VT~VT~VS~VT~VT~US~VT~VT~VT~VT~US~VS~US~US~VS~US~US~UR~UR~UR~UR~UR~UR~UR~UR~TR~TR~TQ~TQ~TQ~TR~TQ~TQ~TR~TQ~SQ~TQ~SQ~SQ~SQ~SQ~SQ~SQ~SP~RP~SP~RP~RP~RP~SP~SP~RO~RO~RO~RO~RP~RO~RO~RO~QO~QO~QN~QN~QN~RO~PM~QN~PN~QO~PN~QN~PN~OM~PN~PN~OM~PN~OM~OM~OM~OL~OL~OM~NL~OL~NK~MK~MK~NK~NK~NL~NK~NK~NK~MK~MK~MK~MK~MK~MK~MK~MK~LJ~LJ~MK~LJ~LJ~LJ~LI~LJ~LI~LI~LI~LI~KI~KI~KI~KH~JH~KI~KI~JH~JH~JH~JH~JH~IG~JH~IG~IG~IG~IG~IG~IG~IG~IG~IF~IF~HF~IF~HF~HF~HF~HF~HF~GE~GE~GE~GE~GE~GE~GE~GD~GD~GD~GD~FD~FD~FD~FD~FD~FD~FD~FC~FC~FC~FC~FC~FC~EC~EC~EC~EC~EC~EC~EC~EB~EB~EB~EB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~CA~CA~CA~CA~CA~CA~CA~C@~CA~CA~CA~CA~C@~CA~C@~C@~C@~C@~CA~CA~B@~C@~B@~B@~B@~B@~B@~B@~B@~B@~B?~B?~B@~C@~B@~B?~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~A>~A?~A?~A>~A?~A?~A?~A>~A?~A>~A?~@>~A>~@>~@>~A>~A>~A>~A>~A>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~@>~@>~@>~@>~A>~A>~@>~A>~@>~@>~A>~A>~@=~A>~@>~@>~@>~@>~A>~@>~A>~A>~@>~A>~@>~A>~A>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~A>~A>~A>~A>~A>~A>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A?~B?~A>~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~B@~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B@~B?~B?~B?~B?~B?~B?~C@~B?~B?~C@~C@~C@~C@~B?~B?~B@~B@~C@~C@~C@~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~C@~B?~B?~B?~B?~C@~C@~C@~B?~B@~B@~B@~B@~B@~B@~C@~C@~B@~C@~C@~B?~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~B@~C@~B@~C@~C@~B@~B@~C@~C@~C@~B@~C@~C@~B@~C@~C@~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~C@~B@~B?~C@~C@~C@~B@~C@~C@~C@~C@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~A?~A?~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A>~B?~B?~B?~B?~B?~B?~A?~A?~A?~A?~A?~A?~A>~A>~A>~A>~A?~A?~A?~A>~A>~A>~A?~A?~A>~A>~A?~@>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~@>~A?~@>~@>~A?~@>~@>~@>~A>~@>~@>~@=~A>~A>~@=~@>~@>~@>~@=~@=~A>~@>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~A>~A>~A>~@>~A>~A>~@>~@>~@>~A>~@>~@>~@>~@>~@>~A?~@>~@>~@>~A>~A>~A>~@>~A?~A>~A>~@>~A?~A?~A?~A?~A?~A?~A>~A?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B@~B?~B@~B@~B@~B@~B@~B@~B@~CA~B@~B@~C@~C@~CA~B@~CA~C@~C@~CA~CA~C@~DA~DA~DA~DA~DA~CA~CA~DA~CA~CA~DA~DB~DB~DB~DB~DB~EC~DB~DB~DB~DB~DB~EC~DB~EC~EC~EC~EC~EC~EC~EC~FD~FD~FD~FD~FC~FD~FD~FD~FD~FD~FD~FD~GE~GE~FD~GE~GE~GE~GE~GE~GE~GE~GE~GE~HE~HF~HF~HE~IF~HF~IF~IF~IF~IG~HF~HF~IG~IG~IG~IG~IG~JH~JH~JH~JH~JH~JH~JH~KH~KH~KH~KH~KI~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~LJ~MK~LJ~LJ~LJ~MK~LJ~MK~MK~MK~MK~MK~MK~NL~NL~NL~NL~OM~OM~OL~OL~OM~OL~OM~OM~OM~OM~PM~PM~PN~PN~PN~PN~PN~QO~PN~PN~PN~PN~QO~QO~QO~RO~QO~RO~RO~RO~RP~RO~RO~SP~SP~SP~RO~SP~RP~SQ~SQ~SQ~SQ~SQ~SQ~TQ~SQ~TQ~TQ~TQ~TQ~UR~UR~UR~UR~US~US~US~US~US~US~VS~US~VS~VS~VS~VS~VS~VS~VT~VT~WT~WT~VT~WT~WT~WT~WT~WT~WU~WT~WT~VT~WU~WU~XU~WU~XU~XU~XU~XU~XU~XU~YV~XU~XU~XU~XU~XU~XV~XU~XV~YV~XU~XV~XU~XV~YV~XV~XV~XV~XV~YV~YV~YW~XV~YV~XV~XV~XV~YV~YW~YW~YW~YW~YW~YW~XV~YW~YV~YW~XV~YV~YV~YV~YV~YW~XV~YV~XV~YW~YW~YW~YW~YW~YW~YW~YW~XV~YV~YW~YW~YW~YW~YW~XV~XV~YV~YV~YV~YV~YV~XU~YV~XU~XU~XU~XU~XU~XV~WU~WT~XU~XU~XU~WU~XU~XU~XU~WT~XU~WU~WT~WU~WT~WT~WT~WU~VT~VT~VT~VT~VT~VS~VS~VS~VT~VT~VT~VT~VT~US~VS~US~TR~TR~UR~UR~UR~UR~UR~UR~TR~TR~UR~TR~TR~TR~TR~TQ~TR~TR~SQ~SQ~TR~SQ~SQ~SQ~SQ~SQ~SQ~RP~RP~SP~SP~SP~SP~SP~RO~RO~RP~RO~RP~QO~QO~QO~QN~QO~QN~RO~QN~QO~PN~PN~PN~PN~PN~PN~OM~PN~OM~OL~OM~OM~OM~OM~OM~OM~NL~NL~NL~OL~OL~NL~NK~NL~NK~NK~NL~NL~NL~MK~MK~MK~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LI~LI~LJ~KI~KI~KI~KH~KI~KH~JH~KH~JH~JH~JG~JH~JH~IG~IG~IG~IG~IG~IG~IF~IG~IF~HF~HE~IF~HF~IF~HF~HF~HF~HE~HF~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GD~GD~GD~GD~FD~FD~FD~FD~FC~FC~GD~FC~EC~EC~EC~EC~EC~EC~EC~EC~EB~DB~DB~EC~DB~DB~DB~DB~DB~DB~DB~DB~CA~DB~CA~CA~CA~CA~DA~CA~C@~CA~CA~CA~CA~C@~CA~CA~CA~C@~C@~C@~B@~B@~B@~B@~C@~B?~B@~B@~B@~C@~B@~B@~B@~B@~B@~B?~B?~B?~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A>~B?~A?~A>~A?~A?~A?~@>~B?~A>~A>~A>~B?~A>~A>~A>~A>~@>~@>~@>~@>~@>~A>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@=~@>~@>~A>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~A>~A>~@>~@>~@>~A>~@=~@>~@>~@>~@>~A>~A>~A>~A>~A>~@=~A>~@>~A>~A>~A>~A>~@>~A>~@>~@>~A>~A>~@=~@>~A>~A>~A>~A?~A>~A>~A>~A?~A?~A>~A>~A>~A>~A>~A?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~B?~B?~B?~B?~A>~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~C@~B?~B@~B@~B@~B@~B@~C@~B@~B@~C@~C@~B@~C@~C@~C@~C@~B?~C@~B@~B?~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~B@~C@~C@~B?~C@~C@~C@~C@~C@~B@~C@~B?~C@~C@~C@~B@~B@~B@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~DA~CA~CA~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B?~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~B@~C@~C@~B?~B?~C@~C@~B?~B?~B?~B?~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~A?~A?~A?~A?~A?~B?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A>~A?~B?~B?~A>~A?~A>~A?~A>~A>~A>~A>~A>~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~@>~@>~@>~A>~@>~@>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~@>~@>~@>~@>~@>~A>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~A?~@>~A?~@>~@>~@>~@>~@>~@>~@>~@>~A?~A?~@>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A?~B?~B?~A?~A?~B@~B@~B@~A?~B@~B@~B@~B@~B@~B@~C@~B@~C@~CA~C@~C@~C@~C@~CA~C@~CA~CA~CA~CA~CA~DA~DA~DB~DB~DB~DB~DB~DB~DB~DB~DB~DB~EC~EC~EC~EC~DB~DB~DB~DB~EC~EC~EC~EC~EC~FD~EC~FD~FD~GD~EC~EC~GD~GD~GD~GD~GD~GD~GD~GD~GE~GD~GE~GE~GE~GE~GE~GE~GE~GE~HE~HF~HF~HF~IF~IF~IF~IF~IF~IG~IG~IG~IG~IG~JG~JG~JH~JG~JH~IG~JH~JH~JH~JH~KH~KH~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~NL~NL~NL~NL~NL~NL~OM~OM~OM~OL~OM~PM~PN~PM~OM~PN~PN~PN~PN~PN~PN~QO~QO~QO~QO~QO~QO~RO~RO~RO~RO~SP~RO~RO~RO~SP~RP~RP~SP~SP~SP~SQ~SQ~SQ~SQ~TR~SQ~SQ~TR~TR~SQ~TQ~TQ~TQ~TR~UR~UR~UR~UR~UR~UR~UR~VS~US~VS~VS~VS~VT~VT~VT~VT~WT~VT~WT~WU~XU~WU~WU~WU~XU~WU~WT~XU~WU~WT~WU~WU~XU~XU~XU~WU~XV~XU~XU~XU~XU~XU~XV~XU~YV~YV~YW~YV~XV~XV~XV~XV~XV~XV~YV~YV~YW~YV~YV~YW~YV~YW~YW~YW~ZW~YW~YW~ZW~YW~YW~ZX~ZW~YW~YW~ZW~YW~ZW~ZW~YW~ZW~ZW~ZW~YW~YW~ZW~ZW~ZW~ZW~ZW~ZW~YW~YW~YW~YW~ZW~YW~ZW~YW~ZW~YW~YV~ZW~YW~ZW~YW~YW~YW~YW~YV~YV~YV~YW~YW~YV~XV~XV~XV~YV~XV~XV~XV~XV~XV~XU~WU~XU~XU~WU~XU~XU~WU~WU~XU~XU~XU~XU~XU~WT~WT~WT~WT~WT~WT~WU~WT~WT~WT~WT~WT~VS~VT~WT~VT~VS~VT~VS~VT~VT~VS~VT~US~VS~US~US~US~VS~VS~VS~US~UR~UR~TR~TR~TR~TQ~SQ~SQ~TQ~TQ~SQ~SQ~SQ~SQ~SQ~SP~SQ~SP~RP~SQ~SP~RP~RP~RP~RO~QO~QO~QO~QN~QN~QN~QN~QN~QN~PN~PN~PM~PN~PN~OM~PN~PN~PN~PM~OM~OM~OM~OM~OM~OM~OL~OL~NL~NL~NL~NL~NL~NL~NK~NK~NK~NL~NK~MK~MK~MJ~LJ~MK~MK~MK~LJ~MK~LJ~LI~LI~KI~LI~KI~KI~KI~KI~KI~KI~JH~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~IG~IG~IG~IF~HF~IG~IG~HF~HF~HF~IF~HF~HF~HF~HF~HE~HE~GE~HF~GE~GE~FD~GE~GE~FD~FD~FD~GE~GD~GD~FD~FD~FC~FD~FC~FD~FD~FC~EC~FC~EC~EC~EC~FC~EC~EC~EB~EB~EB~EB~DB~DB~DB~DB~DB~CA~DB~DB~DB~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~C@~C@~C@~C@~CA~C@~C@~C@~B@~B@~B@~B@~B@~B@~B@~B@~B@~A@~B@~B@~B@~B?~B?~B@~B?~B?~A?~B?~B?~B?~B?~A>~B?~B?~A?~A?~A?~A?~A?~B?~A?~A?~A?~A?~A>~A?~B?~A>~A?~A>~A>~A>~A?~A>~A>~A>~A>~A>~A>~@>~A>~A>~A>~A>~A>~A>~A>~@>~A>~@>~@>~@>~A>~@>~@>~@>~@>~@>~A>~@>~@>~@>~A>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~A>~A>~@>~@=~@>~@>~@=~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~@>~A>~A>~@>~A>~A>~A>~A>~A>~A>~@>~A>~A>~A>~A>~A>~A>~A>~@>~A?~A>~A>~@>~@>~@>~@>~A?~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A?~A>~A>~A>~A?~A?~A?~A?~A?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B@~B@~B@~B@~C@~B@~B@~C@~C@~B@~B@~B@~B@~C@~B@~B?~C@~C@~C@~B@~B@~B@~C@~C@~C@~B@~C@~C@~B@~B@~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~B@~C@~C@~C@~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~C@~C@~C@~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~CA~CA~C@~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~C@~C@~C@~C@~C@~C@~C@~B@~C@~C@~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~C@~B@~C@~B@~C@~C@~B@~B?~B?~B?~B?~B?~B?~B?~B@~B@~B?~B@~B?~A?~B@~B@~B@~B?~B?~B?~B?~B?~B?~B?~B?~A?~A?~A?~A?~A?~B?~A>~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~@>~A?~A?~A?~B?~A?~A?~A>~A?~A?~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~@>~@>~@>~A>~@>~A>~A>~@>~@>~A>~@>~@>~@>~@>~@>~@>~@>~A?~@>~@>~@>~@>~@>~@>~A>~@>~@>~@>~@>~@>~@>~A>~A>~@>~@>~@>~@>~@>~A?~@>~A?~A?~A?~@>~A?~@>~@>~A?~@>~A?~A>~A?~A?~A?~A?~A?~B@~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~B?~B?~B?~C@~B@~B@~B@~B@~B@~B@~B@~B@~C@~C@~C@~C@~C@~C@~CA~CA~CA~CA~DA~CA~CA~CA~DB~CA~CA~DA~CA~DB~DB~EB~DB~DB~DB~DB~DB~EC~DB~EC~DB~DB~EB~EB~EB~EB~DB~EC~EC~EC~EC~FC~FD~FD~FC~FC~FD~FD~GD~GD~GD~GD~GE~FD~GE~FD~FD~FD~HF~GE~GE~GE~GE~GE~GE~HE~HF~GE~HF~HF~HF~HF~IG~HF~IG~IG~IG~IG~IG~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~KI~KI~KI~KI~KI~LJ~KI~LJ~MK~LJ~LJ~LJ~LJ~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~MK~NL~NL~NL~NL~NL~OM~OL~OM~OM~OM~OM~PN~OM~PN~PN~PN~PN~PN~PN~QO~QO~QO~QO~QO~QO~QO~RP~QO~RP~RP~RP~RP~RO~RP~SP~SQ~SQ~SP~SQ~SQ~SQ~SQ~SQ~TR~TQ~TQ~TQ~TQ~TQ~TQ~UR~TR~UR~TR~US~UR~VS~VS~US~VS~UR~VS~US~VS~VS~VT~VT~VT~WT~WT~WT~WT~WT~WT~XU~WU~XU~XU~XU~XU~XU~XU~XU~XU~XV~XU~XU~YV~XU~XU~XU~XU~YV~YV~YV~YV~YV~YV~XV~XV~YV~YW~YW~YV~ZW~YW~YW~YW~ZW~YW~YV~ZW~ZW~YV~YW~YV~YV~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZX~ZW~[X~[X~[X~ZX~[X~ZX~[X~[X~ZW~[X~[X~ZW~ZW~ZW~ZX~ZW~[X~ZW~ZW~ZW~[X~ZX~ZW~ZW~ZW~ZW~ZW~ZW~ZW~YW~ZW~ZW~ZW~ZW~ZW~YW~YV~YW~YV~YW~YW~YV~YW~YV~YV~YW~YW~YW~YW~XV~YV~YV~YV~XV~XV~XV~XV~XV~XV~WU~XU~XU~WU~WU~XU~WU~XU~WU~XU~WU~WT~WT~WT~WT~WT~VT~VT~WT~WT~WT~WU~WT~WT~XU~XU~WT~XU~XU~WT~XU~XU~WT~WT~VT~VT~US~US~US~US~UR~UR~UR~TQ~TR~TQ~TQ~TQ~TQ~SQ~SQ~RP~SQ~SQ~SQ~SQ~SQ~RP~RP~SP~RP~RP~RP~RP~RO~RO~QO~QO~QO~PN~QN~QN~PN~PN~PN~PN~OM~OM~PN~PM~PN~OM~OM~OM~OM~OM~OM~NL~NL~NL~NL~NL~NK~NL~NK~MK~NK~MK~MK~MK~MJ~MJ~LJ~LJ~MK~MK~MK~LJ~LJ~LJ~KI~LJ~LJ~LJ~KI~KI~KI~KH~KH~KI~JH~JH~KI~KH~JH~JH~JH~JG~JG~JG~IG~JG~IF~JG~IG~HF~HF~IF~IF~HF~HF~HF~HF~HF~HF~HE~HF~HE~HE~HE~HE~HF~GE~GE~GE~GE~GE~FD~FD~FD~FD~FD~FD~EC~FD~EC~FC~FD~FC~FC~EC~EC~EC~FC~EC~FC~EB~EB~EB~EB~EB~EB~DB~DB~DB~DB~DB~DB~DA~DA~DA~DA~DA~DA~CA~CA~CA~CA~CA~CA~CA~CA~C@~C@~C@~C@~C@~CA~B@~B@~B@~B@~B@~B@~B@~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A>~B?~B?~A?~B?~B?~A?~B?~A?~B?~B?~B?~A?~A?~A>~A>~A?~A?~A?~A?~A?~A?~A>~A?~A?~A?~A?~A?~A?~A>~@>~A>~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~@>~@>~A>~@>~@=~A>~A>~A>~A>~A>~@>~@>~A>~A>~A>~A>~@>~A>~A>~A>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~@>~A>~A>~A>~A>~A>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~A>~A>~A>~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A?~A?~A>~A>~A?~A?~A>~A>~A>~A>~A>~B?~A>~B?~B?~B?~B?~B?~B@~B?~B@~B@~B?~B@~B?~B?~B?~B@~B@~B@~B@~B?~B?~B?~B?~B?~C@~C@~B@~B@~B@~B@~B@~B?~B?~C@~B@~C@~C@~C@~C@~C@~C@~B@~B@~C@~C@~C@~C@~C@~CA~C@~C@~CA~B@~B@~C@~C@~C@~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~CA~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~C@~C@~C@~C@~DA~CA~C@~C@~C@~C@~C@~C@~C@~CA~C@~C@~C@~DA~C@~C@~C@~C@~C@~C@~C@~DA~DA~C@~C@~C@~C@~C@~C@~C@~CA~C@~C@~C@~C@~DA~C@~C@~C@~C@~C@~C@~C@~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~DA~DA~CA~CA~CA~CA~CA~C@~C@~C@~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~C@~C@~C@~B@~B@~B?~B?~C@~C@~B?~C@~B?~B?~B?~B?~B?~B?~B?~B@~B?~B?~B?~B?~B?~B?~C@~B@~B@~B?~A?~A?~A?~A?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A?~A?~A?~A>~A>~A>~A>~B?~A?~A>~A?~A?~A>~A>~A?~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~@>~A?~A>~A>~@>~A>~A>~A>~@>~A>~A>~@>~@>~@>~@>~A>~@>~@=~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~@>~A>~@>~A>~@>~A>~@>~A?~@>~@>~A?~A>~@>~A?~A?~A?~A>~A>~A?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~B?~B?~A?~B@~B@~B@~B?~B?~B?~B@~B@~B@~B@~C@~B@~C@~C@~C@~C@~CA~C@~CA~CA~B@~C@~CA~CA~CA~CA~CA~DA~CA~CA~DB~DB~DB~DA~DA~DA~DB~DB~EB~DB~DB~DB~DB~DB~EC~DB~EC~EC~EC~EC~FC~EC~FC~EC~FC~FC~FC~FD~FC~FC~GD~GD~GD~FD~FD~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~HF~HE~HE~HE~HE~HE~IF~IF~IF~IF~IF~HF~IG~IG~JH~IG~IG~IG~IG~IG~JH~JH~JH~JH~JH~JH~JH~KI~KI~KI~KI~LI~KI~LI~LI~KI~LJ~LJ~LJ~MK~MK~MK~MK~MK~MK~MK~MK~MK~NK~NK~MK~MK~NL~NL~NL~NL~OM~OM~OM~OM~PM~OL~OL~PM~PN~PN~PN~PN~PN~PN~QO~QO~QO~QO~QO~RO~QO~RO~QO~RP~RO~RO~SP~RO~RP~RP~SP~SP~SQ~SQ~SQ~SQ~SQ~SQ~TR~SQ~SQ~TQ~TQ~TQ~TQ~TQ~TR~TR~UR~UR~UR~UR~US~US~US~VS~US~VT~VS~VT~WU~WT~VT~VT~WT~WU~WT~WT~WT~WT~WU~XU~XU~XU~XU~XU~XU~XU~XU~XU~XU~XU~XU~XV~XV~YW~YV~YV~XV~XV~XV~XV~YW~YW~YW~YW~ZW~ZW~ZW~ZW~ZW~ZX~ZW~ZW~ZX~ZX~ZW~ZW~YW~ZX~ZW~ZW~ZW~ZW~ZW~[X~[X~ZW~ZX~ZW~[X~[X~[X~[X~ZX~ZX~[Y~[Y~\Y~\Y~\Y~\Y~\Y~[X~[Y~[X~[Y~[X~[X~[Y~ZX~[X~[X~[Y~[X~\Y~ZX~[Y~[Y~[Y~[X~[X~[X~[X~ZW~ZW~[X~ZW~ZW~ZW~ZX~ZW~ZW~ZW~ZX~ZW~ZW~ZX~ZW~ZW~YW~ZW~ZW~YW~YV~ZW~ZW~ZW~YW~YW~XV~YW~YW~YW~YV~XV~XV~YV~YV~YV~XV~XV~YV~XU~XV~XV~XV~XV~XU~XU~XU~XU~XU~XU~XU~XU~XV~XV~XV~XV~XU~XU~XU~XU~XV~XV~XV~YV~XV~XV~WU~WU~WU~VT~VT~VT~VT~WU~WU~VT~US~US~UR~TQ~TQ~UR~TQ~TQ~SQ~TQ~TQ~SQ~SQ~SQ~SQ~SQ~RP~RP~SQ~RP~RP~RP~RP~RO~QO~RO~QO~QO~QN~QN~PN~PN~PN~PN~PN~PN~PM~PN~PN~OM~OM~OM~OM~OM~OL~OL~OL~OL~NL~NL~NL~NL~NL~NL~MK~NK~NL~NK~NL~MK~MK~MK~MK~MK~MK~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~KI~KH~KI~KH~JH~JH~JH~JH~KH~KH~JG~JG~JG~JG~JG~IF~IF~JG~IG~IG~IF~IF~HE~HF~HF~HF~HF~HF~HE~HF~HE~GE~HF~HE~GE~GE~GE~GE~GE~GE~GE~FD~FD~FD~FD~FD~FD~FD~FD~FD~FC~FC~FC~FC~EC~FD~FC~FC~FC~EB~EB~EB~EC~EB~DB~DA~EB~DA~EB~EB~DA~DA~DA~DA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~C@~C@~C@~C@~CA~B@~C@~C@~C@~C@~C@~B@~B@~B@~B@~B@~B@~B?~B?~B@~B@~B?~B?~B@~B@~B?~B?~B?~A?~B?~B?~B?~B?~B?~B?~A?~A?~A?~A?~A?~A?~A?~B?~A?~A?~B?~A?~A?~A?~A>~A?~A?~A?~A?~A>~A>~A>~A>~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~@>~A>~A>~A>~A>~@>~A>~A>~A>~A>~A>~@>~@>~A>~A>~A>~A>~A>~@>~A>~@>~A>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~@>~A?~A?~A?~@>~A?~@>~@>~A>~A>~@>~@>~@>~A>~A>~@>~A>~A>~A>~A?~A>~A>~@>~@>~A>~A>~A?~A?~A?~A?~A?~A?~B?~A?~A?~A?~B?~B?~B?~B?~B?~A?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~C@~B@~C@~B?~B@~B@~B@~B@~B@~B@~B@~B@~C@~B@~B@~C@~B@~C@~B@~C@~B@~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~CA~B@~B@~C@~C@~C@~CA~C@~C@~C@~C@~C@~DA~C@~DA~C@~C@~DA~CA~DA~DA~DA~C@~C@~DA~C@~C@~C@~C@~DA~C@~C@~DA~DA~C@~DA~DA~DA~DA~CA~CA~DA~DA~C@~DA~C@~C@~C@~DA~DA~C@~C@~CA~DA~CA~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~DA~C@~C@~C@~DA~CA~CA~C@~C@~C@~C@~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~C@~C@~DA~C@~DA~C@~C@~DA~DA~DA~DA~DA~CA~CA~DA~DA~C@~DA~DA~C@~DA~DA~DA~DA~DA~DA~DA~C@~CA~DA~C@~DA~CA~CA~CA~CA~CA~CA~CA~DA~CA~CA~CA~CA~CA~C@~CA~C@~C@~C@~C@~CA~CA~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~B@~C@~C@~C@~C@~B?~C@~B@~B?~B?~B?~B?~B?~B@~C@~B?~B?~B?~B?~B?~C@~B?~B?~A?~B?~B?~B?~B?~B?~B?~A?~B?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A?~A?~A?~A>~A?~A?~A>~A?~A?~A?~A>~A?~A?~A>~A>~A>~A?~A?~A?~A>~A>~A?~A>~A>~A>~A?~A?~A>~A>~A>~A>~A?~A>~A>~A>~A>~A>~A?~A>~@>~A?~A?~@>~@>~@>~@>~@>~@>~A>~A>~A>~A>~A>~@>~@>~A>~A>~A>~A>~A>~A>~A>~@>~@>~@>~A?~@>~@>~A>~A>~A?~A?~A>~A>~@>~A?~@>~A?~A?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~@>~A?~A?~A?~A?~A?~B?~A?~B@~A?~B?~B@~B?~B?~B@~B@~B?~B@~B?~B@~C@~B@~B@~B@~B@~B@~B@~CA~B@~CA~C@~C@~C@~CA~CA~CA~CA~CA~CA~CA~DB~CA~CB~DA~DB~DB~DB~EB~EB~EB~DB~DB~DB~DB~DB~DB~EC~EC~DB~EC~EC~FC~FC~FC~FC~EC~FD~FD~GD~GD~FD~FD~GE~FD~GD~GD~GE~FD~GE~GE~GE~GE~GE~GE~HE~GE~HE~HE~HE~HE~HE~HF~HE~IG~IG~IF~IG~IG~IG~IG~IG~IG~IG~JH~JH~KH~JH~JH~JH~JH~KI~KI~JH~KI~KI~KI~LJ~LJ~KI~KI~LJ~LJ~MK~LJ~MK~MK~MK~MK~MK~MK~NL~NL~MK~NL~NL~NL~NL~NL~NL~NL~NL~NL~OM~OL~PM~OM~PM~PN~PN~PN~PN~PN~PN~PN~QO~QO~QN~QO~QO~RP~RP~QO~QO~RP~RP~RP~SP~SQ~SP~SP~SP~SQ~SQ~RP~SQ~SQ~SQ~SQ~TQ~TQ~TR~UR~TQ~TR~TR~TQ~TR~TR~UR~UR~UR~US~UR~US~VS~US~VT~VT~VT~VT~WT~WU~WU~WT~WT~WU~WU~WU~XU~WT~XU~XU~XV~XV~XV~YV~XV~YV~YV~YV~YW~YV~YW~YW~YW~YV~YW~YV~ZW~YW~YV~YW~YW~YW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~[X~ZX~ZX~[X~[X~ZX~ZX~[X~ZX~[X~[X~[X~ZX~[X~[X~[Y~\Y~[Y~[Y~\Z~\Z~]Z~\Y~\Y~\Y~\Y~\Y~\Z~\Z~\Y~\Y~]Z~\Y~\Y~\Y~\Y~\Y~\Y~\Y~\Z~\Y~\Z~\Z~\Z~[Y~\Y~\Y~\Y~[Y~[Y~\Y~[Y~[X~[X~[Y~[X~ZX~[X~[X~[X~[X~[Y~ZX~ZX~ZW~[X~[X~ZW~ZW~ZW~ZW~ZW~YW~ZW~ZW~ZW~ZX~YW~YW~YV~YW~YW~YW~YV~YW~YW~YW~YV~YW~YW~YV~YV~YW~XV~YV~XV~XV~XV~XV~XV~XV~XU~XV~XV~YV~YV~YV~YV~YW~YV~YW~YW~YW~YW~YV~YV~YV~WU~WU~WU~WU~WU~WU~XU~WU~WT~VT~WT~VT~VT~US~TR~TR~TR~TQ~TQ~TQ~TQ~TQ~SQ~SQ~SQ~SQ~SP~SP~RP~SP~SQ~RP~RP~RO~RP~RP~RP~QO~QO~QO~QO~QO~QO~PN~QN~PN~PN~PN~PN~PN~OM~OM~PN~OM~OM~OM~OM~OM~OM~OL~OL~NL~NL~NL~NL~NL~MK~NK~MK~MK~MK~MK~MK~MK~MJ~MK~MK~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~KI~KI~KI~KI~KI~KI~KI~KH~KI~KH~KH~JG~JH~JG~JH~JH~JH~IG~IG~IG~HF~IF~IF~HF~IG~HF~HF~IF~HF~HE~HE~HF~HE~HF~HF~HE~HE~HE~GE~GE~GE~GE~FD~FD~FD~FD~FD~FD~FD~FD~FD~FC~EC~FC~EC~FC~EB~FC~EC~EB~FC~EC~EB~EC~EC~EB~EB~EB~EB~EB~EB~DA~DB~DB~DB~DB~CA~DB~DB~CA~CA~DB~CA~CA~CA~CA~B@~C@~CA~CA~C@~CA~CA~B@~C@~CA~C@~B@~B@~B?~B@~B@~B@~B@~B@~B@~B@~B@~B@~A?~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A>~A>~A>~A?~A>~A>~A?~A?~A>~A>~A>~A>~A>~B?~A>~A>~A>~A>~A>~A>~@>~A>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A>~A>~A>~A>~A>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~@>~A>~@>~A>~@>~@>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~@>~A>~@>~A?~A>~A>~A?~A>~A>~A>~A>~A>~A?~A?~A>~A>~A>~A?~A?~A>~A>~A>~A>~A>~A>~A>~A>~B?~A?~A>~A?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B@~B@~B?~C@~B?~B?~C@~C@~B?~B@~B@~B@~C@~C@~B?~C@~B?~B?~C@~B@~B@~B@~C@~C@~B@~B@~C@~C@~B@~B@~C@~C@~C@~CA~C@~C@~CA~CA~CA~CA~CA~CA~CA~C@~C@~C@~DA~DA~C@~CA~DA~CA~DA~DA~DA~C@~DA~DA~CA~DA~CA~CA~CA~CA~DA~DA~DA~DA~DA~DA~DA~DA~C@~C@~DA~C@~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~C@~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~C@~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~CA~DA~DA~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~CA~DA~CA~C@~CA~DA~C@~C@~C@~CA~CA~C@~CA~C@~C@~DA~C@~C@~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~B@~C@~B?~C@~C@~C@~C@~C@~C@~B?~C@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A>~B?~B?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A>~A?~A?~A>~A>~A>~B?~A>~A?~B?~A?~A>~A>~A>~B?~A?~A?~A?~A?~A?~A?~A>~A>~B?~A>~A>~A?~A?~A?~A>~A>~A>~A>~A?~A>~A>~A>~A>~A>~@>~A>~@>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~@>~A>~A>~A>~@>~@>~A>~A>~A>~A?~A?~A?~A?~A?~A?~A?~@>~A?~@>~A>~A>~A>~A>~A?~A>~A>~A>~A?~A?~A?~A?~A?~A?~B@~B@~A?~A?~B?~B@~A?~A?~B?~B?~B?~B@~B@~B@~B?~B?~B@~B@~B?~B@~B@~B@~B@~C@~C@~B@~B@~C@~C@~CA~C@~CA~CA~CA~CA~CA~CB~CA~CA~DA~DA~DA~DA~DA~DB~DA~DB~DB~DA~EB~DB~DB~DB~DB~EC~DB~EB~EC~EC~EC~FC~EB~FC~FD~FD~FD~FD~FD~FD~GD~FD~FD~FD~GD~GD~GD~GD~FD~GE~GE~GE~GE~GE~GE~GE~HE~HF~HF~IF~HF~HF~IF~IF~HF~IG~IG~IG~IG~IG~IG~IG~IG~JH~JH~JH~JH~JH~KH~KH~KI~KI~KI~JH~KI~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~MK~MK~NL~NL~NL~NL~NL~NL~NL~NL~OM~NL~NL~OM~OM~OM~OM~PM~PM~PM~PN~PN~PN~PN~PN~PN~PN~QO~QO~QO~QO~RP~QO~RP~RP~RO~RP~SP~SP~SQ~SP~SQ~SQ~SQ~SQ~SQ~SQ~TQ~TQ~TR~TQ~TQ~TR~UR~UR~UR~UR~UR~TQ~US~UR~US~US~US~US~US~US~VS~VS~VS~VT~WT~VT~VT~WU~WT~WT~XU~XU~XU~XU~XU~XU~XV~XU~XV~XV~XV~YV~YW~YW~YW~YW~YV~ZW~ZW~ZW~YW~ZW~ZW~YV~ZW~YV~ZW~ZW~ZW~ZX~ZX~ZW~ZX~ZX~[X~ZW~[X~ZX~[X~\Y~[X~[X~[X~[X~[Y~[Y~[X~[X~[X~[Y~\Y~[X~\Y~[Y~[Y~\Z~]Z~]Z~^[~][~]Z~][~][~^[~^[~^[~^[~]Z~]Z~^[~^[~][~][~]Z~]Z~]Z~]Z~][~\Z~][~]Z~]Z~]Z~\Y~]Z~]Z~\Z~\Z~\Y~\Y~\Y~\Y~\Y~\Y~\Y~\Y~\Y~[Y~[Y~\Y~\Y~[Y~\Y~[Y~[X~[Y~[Y~[Y~ZX~[Y~[X~[X~[X~[X~[X~[X~[X~[X~[X~[X~[X~ZW~ZW~ZW~ZW~ZW~ZW~YW~YW~ZW~YW~YW~ZW~ZW~YV~YV~YV~YW~YW~YV~ZW~ZW~ZW~ZX~ZW~ZW~ZW~YV~ZW~YW~ZW~ZW~YV~YV~YW~XV~YV~YV~YV~YV~XV~XV~YW~XV~XU~WU~XU~WT~WT~WT~WT~VT~VS~UR~US~TR~TR~TR~TQ~TR~TQ~TQ~TR~TQ~SQ~RP~RP~RP~SP~RP~RP~RP~RP~RP~RP~RO~RP~QO~RO~QO~QO~QO~QO~PN~QO~PN~PN~PN~PN~PN~PN~PN~PN~PM~OM~OM~OM~OM~OM~OM~OL~OL~OL~NL~OL~NL~NK~NK~MK~MK~NK~MK~MK~MK~MJ~MK~MJ~MK~LJ~MK~LJ~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KI~KH~KH~KH~JH~JH~JG~JG~JG~IG~IG~IG~IG~IG~IG~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HE~HE~HE~GE~GE~GE~GE~GE~FD~GD~FD~FD~FD~FD~FD~FD~FD~FD~EC~FD~EC~EC~EC~EC~EB~EC~FC~EB~EC~DB~EB~EB~EB~DB~EB~EB~DA~DB~DB~CA~DB~DB~DB~DB~DB~CA~CA~CA~CA~C@~CA~CA~B@~CA~CA~CA~CA~B@~CA~B@~B@~B@~C@~C@~B@~B@~C@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B?~B?~A?~B@~B?~B?~B?~B?~B?~B?~A?~B?~A?~B?~A?~A?~A?~A?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A>~A>~A?~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A?~A?~A?~A>~A>~A>~A>~A>~B?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~@>~@>~@>~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A?~A?~A?~A>~A>~A>~A>~A>~A>~A?~A>~A?~A>~A>~A?~A>~A?~A?~A?~A?~A?~B?~A>~A?~A?~A?~B?~A?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B@~B?~B@~B?~B?~B?~B?~B?~B?~C@~B?~C@~B?~B?~B?~C@~C@~B?~C@~C@~B@~C@~C@~C@~B@~C@~C@~CA~CA~C@~C@~C@~C@~C@~DA~CA~DA~DA~DA~DA~DA~C@~C@~C@~C@~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~DA~DA~CA~CA~CA~DA~DA~DA~CA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~CA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~DA~DA~DA~DA~DB~DB~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DB~DB~DA~DA~CA~DB~DB~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~C@~DA~DA~DA~DA~DA~DA~CA~CA~DA~C@~C@~DA~CA~DA~C@~C@~C@~C@~C@~C@~CA~C@~C@~CA~C@~C@~C@~C@~B@~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~B@~C@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~B?~B?~B?~B?~B?~A?~A?~A?~A?~A>~B?~B?~A>~B?~A>~B?~A>~B?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A>~A?~B?~B?~A?~A?~A?~A>~A?~A>~A?~A>~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~B?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A?~A>~A?~A>~@>~A>~A>~A>~A>~A>~A>~B?~A>~A>~@>~A?~A?~A?~@>~A?~A?~B?~B?~A>~B?~B?~A?~B?~A?~A?~A?~A?~B?~B?~B?~A?~B?~B?~B?~A?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~B@~B@~B@~B@~C@~C@~B@~C@~C@~C@~CA~CA~CA~CA~C@~CA~DA~CA~CA~DA~CA~DA~DA~DA~CA~DB~DB~DB~DB~DB~DB~DB~EC~DB~EC~DB~EB~DB~EC~EC~ED~EC~FC~FC~FD~FC~FD~FD~FD~FD~FD~FD~FD~GD~GD~GD~HE~HE~GE~GE~GE~GE~GE~HE~HE~HE~HE~HE~HF~HF~HF~HF~HF~HF~IG~IG~IG~IG~IG~IG~JH~JH~JH~JH~JH~JG~JH~JH~JH~KI~KI~KI~KI~KI~KI~LI~LI~LJ~LJ~LJ~MK~LJ~MK~MK~MK~MK~MK~NL~NL~NL~OL~OL~OM~NL~NL~OM~OL~OL~OL~OM~OM~OM~PM~PM~PM~PN~PN~QO~PN~PN~PN~PN~PN~QN~QN~QN~QO~QO~QO~RO~RP~RO~RP~SQ~RP~RP~SP~SQ~SP~TQ~TQ~TR~TR~TR~TQ~UR~UR~UR~UR~UR~UR~UR~UR~US~US~US~US~VT~VT~VS~VS~VT~VS~VS~VT~WT~WT~WT~WT~WT~WT~WT~WT~XU~XU~XU~XV~XV~XV~WU~XV~XV~YV~YV~YV~ZW~YV~ZW~ZW~YV~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZX~[X~ZX~[X~[X~[Y~ZX~[Y~[X~[Y~[Y~[Y~[Y~[Y~[Y~\Y~[Y~\Y~\Z~\Y~\Y~\Z~]Z~]Z~]Z~]Z~]Z~][~][~^[~^[~^\~^[~^[~^[~]Z~^[~^\~^[~^[~_\~^\~^\~^[~^[~^[~^[~^[~^[~^[~^[~^[~^[~^[~][~]Z~]Z~][~][~][~]Z~][~][~]Z~][~][~]Z~]Z~\Z~\Z~\Z~]Z~\Z~\Z~\Z~\Z~\Z~\Z~\Y~\Y~[X~\Y~\Y~\Z~\Y~\Z~[Y~[X~[X~[Y~[X~[X~ZX~ZX~ZX~ZX~[X~ZW~ZX~ZW~ZW~YW~ZX~ZW~ZX~ZW~ZW~ZW~YV~ZW~ZW~ZW~ZW~ZW~ZX~ZX~ZX~[X~ZW~ZW~ZW~ZW~ZW~ZW~ZW~YW~YW~YW~YW~ZW~YW~YW~YV~YV~YV~YV~XV~XU~XU~WT~WU~XU~XU~XU~VT~VS~US~UR~TR~UR~TR~TR~TR~TR~TR~TQ~TQ~SQ~SP~SQ~SQ~SP~SP~SP~SP~SQ~RP~RP~RP~RP~RP~RP~RO~QO~QO~RO~RO~QN~QN~QN~QN~PN~PN~PN~PN~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~NL~NL~NL~NL~NL~MK~NL~MK~NK~NK~MK~MK~MK~MJ~MK~MJ~MJ~MJ~MJ~LJ~LI~LI~LI~KI~KI~LI~KH~KI~KI~JH~JH~KH~JH~JH~JH~JH~JH~JG~IG~IG~IG~IG~IG~IF~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HE~GE~GE~GE~GE~GE~GE~GE~GE~FD~GE~FD~GD~FD~FD~FD~FC~FD~FD~FC~FC~FC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EB~EC~EB~EC~EC~EB~DB~DB~DB~DB~DB~DA~DA~DA~DA~DA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~B@~B@~C@~C@~C@~C@~B@~B@~B@~B@~B@~B?~B?~B?~B?~B?~B@~B?~B@~A?~A?~A?~A?~A?~B?~A?~A?~B?~A?~A?~A?~A?~A?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A?~A>~A?~A>~A?~A?~A?~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A>~A?~A>~A?~A>~A>~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A?~A>~A>~A>~A?~A>~A>~A>~A>~A?~A?~A>~A>~A>~A>~B?~A>~A?~A>~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~B?~A?~A?~B?~A?~B?~A?~A?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~C@~B?~B?~B@~B?~B@~B?~C@~B?~B?~B@~B@~C@~B?~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~DA~DA~DA~C@~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DB~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~DA~CA~DA~DB~DB~DB~DB~DB~DA~DB~DA~DB~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DB~DB~DA~DB~DA~DA~DB~DB~DB~DA~DB~DB~DB~DB~DB~DB~DA~DA~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DB~DA~DA~DA~DA~CA~DA~DA~DA~DA~DA~CA~CA~DA~DA~C@~C@~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~B@~C@~B?~C@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~A?~A?~A?~A?~A?~A>~A?~B?~A?~A?~A?~B?~B?~B?~A?~A?~A?~A>~A>~A>~B?~B?~B?~B?~A?~A>~A?~A?~A?~A?~A?~A?~A>~A>~A>~A?~A?~A?~A?~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~@>~A?~A>~A>~A?~A>~A>~A>~A>~A>~A?~A>~A>~A?~A>~A?~A?~A?~B?~A?~B?~A>~B?~A?~B@~B?~B?~B?~A>~B?~B?~B?~B?~B?~B?~B?~A?~A?~B@~B?~B?~B?~B?~B@~B@~B@~B@~B@~C@~B@~B@~B@~C@~C@~C@~CA~C@~C@~C@~C@~C@~B@~C@~C@~CA~CA~CA~CA~CA~DB~CA~DA~DA~DA~DA~DA~DA~DA~DB~DB~EC~EB~DB~EB~EC~EB~EC~EC~EC~FC~EC~FC~FC~FD~EC~FD~FD~FD~FD~FD~FD~GE~GE~GE~GE~GE~GE~GE~GE~GE~GE~HF~GE~GE~GE~GE~HE~HF~HF~IF~HF~IF~IG~IG~IG~JH~IG~JH~IG~JH~IG~JH~JH~JH~JH~JH~KI~JH~JH~KI~KI~KI~KI~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~NK~MK~NK~NL~NL~OL~OM~NL~NL~OM~OL~OL~OL~OM~OM~OM~OM~PN~OM~OM~PN~PN~PN~PN~QO~PN~QO~QO~QO~QO~QO~QO~QO~QN~RP~RP~RP~SP~RP~SP~SP~SQ~SQ~TQ~SQ~SQ~TR~TR~TQ~TR~TR~TR~TR~UR~UR~US~US~US~US~VS~VS~VT~VT~VT~VT~VT~WT~WT~WT~WT~WT~WT~WT~WT~WT~WU~WT~XV~XU~XU~XU~XV~XV~XV~YW~YW~YW~YW~YW~ZW~ZW~[X~ZX~ZX~ZW~[X~ZW~ZW~ZX~ZW~ZX~[X~[Y~ZX~ZX~[Y~[X~[X~[X~[X~[X~\Y~\Y~\Y~\Y~\Y~\Y~\Y~\Y~\Y~\Y~\Y~\Z~\Y~\Y~\Y~]Z~\Y~]Z~]Z~]Z~]Z~^[~^[~][~^[~^\~_\~^\~_\~_\~^\~^\~^\~^\~^\~^\~_]~_]~_\~_\~^[~_\~_]~_\~^\~_\~^\~^[~_\~^\~^\~^\~^[~^[~^[~^\~][~^[~^[~^[~^[~^[~][~^[~^[~][~^[~]Z~]Z~]Z~^[~][~]Z~]Z~][~]Z~]Z~\Z~\Z~\Y~]Z~\Y~\Y~\Y~\Z~\Y~\Z~\Y~\Y~\Y~\Y~[Y~ZX~[Y~[X~[X~[X~[X~[X~[X~[X~ZX~[X~[X~ZX~ZX~ZX~ZX~ZX~ZX~[X~[X~[X~[Y~[Y~ZX~ZX~[X~[Y~[X~ZX~ZX~ZW~ZW~ZW~YW~YV~YW~YV~ZW~ZW~YW~YV~YV~XV~XV~XU~WU~XV~XV~XV~XV~WT~VT~VS~US~UR~UR~UR~TR~UR~UR~UR~TR~UR~TQ~TQ~TQ~TQ~TQ~SQ~SQ~SQ~SQ~SQ~RP~SP~SQ~SP~RP~RP~RP~RP~RP~RP~RO~RO~QO~QO~QN~QN~QN~QN~PM~PN~PM~PN~OM~OM~OM~OM~OM~OM~OM~OM~OM~OM~NL~NL~NL~NL~NL~NL~NL~NK~MK~MK~MJ~MK~MJ~LJ~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~KI~KI~KI~KI~KI~KI~JH~JH~JH~JH~JG~JH~JH~JH~JH~JH~JH~IG~IG~IF~IG~IG~IF~HF~HF~HF~HF~HF~HF~HF~HE~HF~HF~HF~GE~GE~GE~GE~GE~GE~GE~GE~GE~GD~GD~FD~FC~FD~FD~FC~FC~FC~FC~FC~EC~EC~EC~EC~EC~EB~EB~EB~EC~EB~EC~EB~DB~EB~EB~EB~DB~DA~DB~DB~DA~DA~DA~DB~DA~DA~DA~DA~CA~DA~CA~CA~CA~CA~C@~CA~CA~CA~B@~C@~C@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B?~B?~B?~A?~A?~B?~B?~A?~A?~A?~B@~A?~A?~A?~A?~A?~A>~A?~A?~A?~A?~A?~A?~A?~A>~A?~A?~A>~A?~A?~A?~A?~A?~A?~A?~A>~A?~A?~A>~A>~A?~A?~A?~A?~A>~A>~B?~A>~A>~A>~A>~A>~A?~A?~@>~A?~A?~A?~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A>~A?~@>~@>~@>~A?~A?~@>~A?~@>~A>~A?~A?~A?~A>~A?~A?~A>~A>~A>~A?~A?~A?~A?~A?~A>~A>~B?~B?~B?~B?~A?~B?~B?~A?~B?~B?~A>~A?~A?~B?~B?~B?~B?~B@~B?~B?~B?~B@~B@~B?~B@~B@~B@~B?~B@~B?~C@~C@~B@~B@~B?~B@~B@~B?~C@~B@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~B@~DA~CA~C@~C@~C@~C@~C@~C@~C@~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~DA~DA~DA~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DB~DA~DB~DB~DB~EB~DA~DB~DB~DA~DA~DA~DB~DA~DB~DA~DA~DB~DA~DA~DA~DA~DA~CA~DA~DB~DA~DA~DA~DA~DB~DA~DA~DA~DB~DB~DB~DB~DB~DB~DB~DB~DA~DA~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~DB~DA~DB~DB~DB~DB~DB~DB~DA~DB~DB~DA~DB~EB~DA~DB~DB~DA~EB~EB~DA~EB~EB~DA~DA~DA~DA~DB~DA~DA~EB~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~CA~CA~CA~CA~CA~CA~C@~CA~C@~C@~C@~C@~C@~C@~CA~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B?~B?~B?~B?~B?~B?~C@~C@~B?~B?~B?~B?~B@~A?~B?~B?~B?~B?~B@~A?~B?~B?~A?~A?~B?~A?~B?~A?~A?~B?~A?~A?~A>~A?~A?~A?~A?~A?~A?~A?~A?~B?~A>~A?~A?~A?~A?~A?~A?~A?~A>~A>~A?~A?~A?~A?~A?~A?~A?~A>~A>~A>~A?~A?~A?~A?~A?~A>~A?~A>~A>~A>~A?~A?~A>~A>~A>~A>~A>~A>~A?~A?~A>~A>~A>~B?~A?~B?~A>~A?~A>~A>~A?~A?~A?~A?~B?~B?~A>~B?~B?~A?~A?~A?~B@~A?~A?~A?~A?~B?~B?~B?~B?~B?~B@~B@~B@~B?~B?~B@~B?~B@~B?~B@~B@~B@~B@~B?~C@~B@~C@~C@~B@~B@~C@~C@~C@~CA~C@~C@~C@~CA~CA~CA~CA~CA~DA~DA~CA~CA~DA~DA~DA~CA~CA~CA~DA~DB~DB~DB~DB~EB~EB~EB~EC~EC~EC~EC~FC~FC~FC~EB~FC~FC~FC~FC~FC~FC~FD~FD~FD~FD~FD~GD~GE~GE~GE~GE~HE~HE~HE~HE~HF~HE~HF~HF~HF~IF~IF~IF~IG~IF~IG~IG~IG~IG~IG~IG~JH~JH~JH~JH~JH~JH~JH~KI~KH~KI~JH~KI~KI~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~MK~MK~NK~NK~NL~NL~NL~NL~NL~OM~OM~OM~OL~OM~OM~OM~OM~OM~PN~PN~PN~PN~PN~PN~PN~QN~QN~QN~QN~RO~QO~QO~QO~RO~QO~QO~QO~QO~RP~RP~RP~SQ~SQ~SQ~SQ~TQ~TQ~TQ~SQ~TQ~TQ~TR~TR~UR~US~US~US~US~US~US~VT~VT~VT~VT~VT~VT~WT~VT~WT~WT~WU~WT~WT~WU~WU~WU~WU~WU~XU~XV~XV~XV~XV~YW~YV~YW~YV~ZW~ZW~ZW~ZW~ZX~ZX~ZX~[X~ZX~[X~[X~[Y~ZX~[Y~[Y~[Y~[Y~[Y~[Y~\Y~\Z~\Z~[Y~]Z~\Y~\Z~\Y~\Z~\Y~\Y~\Z~\Y~\Y~]Z~\Z~\Z~]Z~]Z~]Z~]Z~]Z~]Z~][~^\~^[~^[~^\~^\~^\~^\~^\~^\~_]~_]~`]~`]~`]~_]~`]~_]~`^~`^~`]~`]~`]~`^~`]~_\~`]~`]~`]~_]~_\~_\~`]~`]~_]~`]~_\~_\~_\~_\~_\~_\~_\~_\~_\~_\~^\~_\~_\~^[~^[~^[~_\~^\~^[~^[~^[~_\~][~^[~^[~^[~]Z~]Z~]Z~^[~]Z~]Z~]Z~]Z~]Z~]Z~]Z~]Z~\Z~\Y~\Y~\Z~\Y~\Y~\Y~\Y~[X~[X~[X~[X~[Y~\Y~[X~[X~[X~[X~[X~[X~ZX~ZX~[X~[X~[Y~[Y~[X~[X~[X~[Y~[Y~[Y~[X~[X~[X~ZW~ZW~ZW~YW~ZW~ZW~ZX~ZW~YW~YV~YV~YW~YW~YW~YW~XV~XU~WT~WT~VS~VS~VS~VS~US~US~US~US~US~US~US~US~TR~UR~TR~TR~TQ~SQ~SQ~SQ~SP~SP~SP~RP~SP~RP~SP~RP~RP~RP~QO~RO~QO~QO~QO~QO~QN~QN~QN~QN~PN~PN~PN~PN~PN~PN~OM~OM~OM~OM~OM~OM~OM~OM~OM~NL~NL~NL~NL~MK~MK~MK~MK~MK~MK~MK~MK~MJ~LJ~LJ~LJ~LI~LJ~LI~LJ~LI~KI~KI~KI~KI~KH~KI~KH~JH~JH~KH~JH~JH~IG~JG~JH~IG~IG~IG~IG~IF~IG~HF~HF~HF~HF~HF~HF~HF~HF~HE~GE~HE~GE~HE~GD~GE~GE~HE~GE~GE~GD~GE~GD~GD~GD~FD~FC~FD~FD~FD~FD~FC~FC~FC~FC~EC~EC~EC~EC~EC~EC~EC~DB~DB~EB~EB~EB~EB~EB~EB~DB~DA~DA~DB~DA~DA~CA~DA~DA~DA~DA~DA~DA~DA~DA~CA~C@~CA~B@~CA~CA~CA~C@~C@~C@~C@~C@~B@~B@~B@~B@~C@~B@~C@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A>~B?~B?~B?~B@~B?~B?~A?~A?~A?~A?~A>~A>~A>~A?~A?~A?~A?~A?~A?~A?~B?~A?~A>~A?~A?~A>~@>~A?~A>~B?~A?~B?~B?~A?~A?~A>~A>~A>~A>~A>~A>~A>~A>~A?~B?~A?~A?~A?~A?~A?~A?~A>~A?~A>~A>~A>~A>~A>~A>~A?~A?~A?~A>~A?~A>~A>~A>~A>~A>~A>~A>~A>~A?~A?~A?~A?~A?~A?~B?~B?~B?~B?~A>~A>~B?~B?~B?~B?~A?~A?~B?~B?~B?~A>~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~B@~B@~B?~B?~B?~C@~B@~B@~B@~B?~C@~C@~C@~C@~C@~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~DA~C@~C@~C@~C@~C@~C@~C@~C@~DA~CA~DA~CA~DA~DA~DA~DA~DA~DA~DA~CA~DA~DA~DA~DA~DB~DA~DB~DB~DA~DA~DA~DB~DA~DA~DA~EB~DA~DA~DA~DA~DA~DA~DA~EB~EB~DB~DB~DB~DB~DB~DA~EB~DA~EB~EB~EB~EB~DB~EB~EB~EB~EB~DA~DA~EB~DA~DA~EB~DA~EB~DA~EB~DA~EB~EB~EB~EB~EB~EB~DB~DB~DB~DB~DB~DB~DB~EB~EB~EB~EB~EB~EB~DA~EB~EB~EB~DB~DB~DA~DA~EB~DA~EB~DA~EB~EB~DA~DA~EB~DA~DA~DA~DA~EB~DA~DB~DB~DB~EB~DB~DB~DB~DB~DB~EB~DB~DA~DB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DB~DB~DB~DB~DB~DB~EB~DB~EB~EB~DB~EB~DB~DA~EB~DA~DA~EB~EB~DA~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~DA~DA~DA~CA~C@~CA~CA~C@~C@~C@~C@~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~B?~B?~B@~B?~B?~C@~B?~B?~B?~B?~B?~B@~B?~B?~A?~A?~B?~B?~B?~B?~B@~B?~B?~B?~B?~B?~B?~B?~A?~A?~A?~A?~A?~A?~A?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A>~A?~A?~B?~A?~A?~A?~A?~A>~A>~A?~A>~B?~A?~A?~A?~A?~A?~A?~B?~B?~A?~A?~A>~B?~A?~A>~A?~A?~A?~A?~A>~A?~A?~A?~A>~A>~A>~A?~A?~A?~A?~A?~A?~B?~B?~A>~B?~B?~B?~B?~B?~B?~B?~A?~A?~A?~B@~A?~A?~B?~B@~B?~B?~B@~B?~B?~B@~B@~B?~B@~B@~B?~B?~B@~B@~C@~CA~B@~C@~B@~B@~C@~C@~B@~B@~C@~CA~CA~DA~CA~CA~CA~CA~CA~DB~DB~CA~DB~DB~CA~DA~DA~DB~DA~DA~DB~DB~DB~DB~EB~EB~EC~EB~EB~FC~FC~FC~FC~FC~FC~FD~FD~FC~FD~FD~FD~FD~FD~GE~GE~GE~GE~GE~GE~GE~GE~HE~HF~IF~HF~HF~HF~HF~IF~IF~IF~IF~IG~IF~HF~IG~IG~IG~IG~JG~JH~JH~JH~JH~KI~KI~KH~KH~KH~KI~KI~KI~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~NK~NK~MK~NL~MK~NL~NL~NL~NL~OM~OL~OM~OM~OM~OM~OM~PN~PN~PN~PN~PN~PN~QN~PN~PN~QO~QO~QO~RO~QO~QO~QO~QO~RP~QO~RP~RP~RP~SP~RP~SP~SP~SQ~TQ~SQ~TQ~TQ~TQ~TQ~TR~TR~UR~TR~UR~US~US~US~US~US~VS~VT~WT~VT~WT~WU~WU~XU~WT~WU~WU~WT~WU~WU~WU~WU~WU~XV~WU~XV~XV~YV~XV~YV~YW~YW~YV~ZW~ZW~ZX~ZW~ZX~[X~ZX~ZX~[X~[X~[Y~[Y~\Y~\Y~\Y~\Y~\Y~\Z~\Z~\Z~\Z~]Z~\Z~\Z~\Z~\Z~]Z~]Z~]Z~]Z~]Z~][~][~][~^[~]Z~]Z~]Z~^[~^[~^[~^[~]Z~_\~_\~_\~_\~_]~`]~_]~_]~`]~_]~`]~`^~`^~a^~a^~`]~`]~a^~a^~a_~a^~a_~a^~`]~a^~`^~`^~`^~`^~`]~`]~`]~`]~`]~`]~_\~a^~a^~`]~`]~_]~_]~_]~`]~`]~_]~_]~_]~_]~_]~_\~_]~_\~_\~_\~_\~_\~_\~_\~_\~_\~^[~^\~^[~^[~^[~^[~^[~^[~]Z~^[~^[~]Z~]Z~]Z~][~][~]Z~][~]Z~\Z~\Z~]Z~\Y~\Y~\Y~\Z~\Y~\Y~\Z~\Y~\Y~\Y~\Y~[Y~ZX~[Y~ZX~ZX~[X~\Y~\Y~\Y~[Y~\Y~[Y~\Y~[X~[X~ZX~[X~[X~[X~[Y~[Y~ZX~ZW~YW~ZW~YW~YW~ZX~ZX~YW~YV~XV~WU~WT~WT~WT~VS~VS~VS~US~US~US~US~UR~US~US~US~UR~UR~TR~TR~TR~TR~TR~TR~TQ~TR~SP~SP~SP~SP~SP~RP~SP~RP~RP~RP~RP~RP~RP~RO~QO~QO~QO~QN~QN~PN~PN~PN~PN~PN~OM~OM~OM~PM~PM~OM~OM~OM~OM~NL~NL~NL~NL~NL~NL~NK~NK~MJ~MK~LJ~MK~MK~MK~LJ~MK~MJ~MJ~MJ~LJ~LJ~KI~KI~KI~KI~KH~JH~JH~JH~JH~KI~JH~JH~JH~JH~JH~JH~IG~IG~IG~IG~JG~IF~IF~IF~IF~IG~HF~HF~IG~HF~GE~HE~HE~HE~HE~GE~HE~HE~GE~HE~GD~HE~GD~GD~FD~GD~FD~FD~FD~FC~FC~FC~FC~FC~FC~EC~EC~EC~EB~EB~EB~DB~EB~EB~EB~EB~EB~EB~EB~EB~DA~DB~DB~DB~DB~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~CA~C@~DA~C@~CA~CA~CA~CA~CA~DA~C@~CA~C@~B@~B@~CA~B@~B@~B@~B@~C@~B?~B@~C@~B@~B@~B?~B@~B?~B@~B?~B?~B?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~B?~B?~A?~B?~A?~A?~A?~A>~A?~A>~A>~A>~A>~A?~A?~A?~A>~A>~A?~A?~A>~A>~A?~A?~A?~A?~A?~A>~A?~A>~A>~A?~A?~A>~A>~A>~A>~A?~A?~A?~A?~A>~A>~B?~A?~A?~A?~A?~A?~A>~A?~A>~A?~A?~A>~A>~A>~A>~A>~A>~A?~A>~B?~A?~B?~A>~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~B?~B?~B?~B?~B?~B?~B@~B?~B@~B@~B?~B?~B?~B?~B?~B@~B?~C@~C@~B@~C@~C@~B@~B@~B@~C@~B@~C@~CA~C@~C@~C@~C@~C@~CA~DA~DA~C@~DA~DA~C@~C@~C@~DA~DA~DA~C@~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DB~DB~DB~DA~DB~DB~EB~DB~DA~DA~DA~DB~DB~EB~DA~EB~EB~EB~EB~EB~DA~DA~DA~DA~DA~DA~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~DA~EB~DA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DB~EB~DA~EB~EB~EB~EB~EB~EB~EB~DB~DB~DB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EC~EB~EB~EB~EB~EB~EB~EB~EC~EB~EB~EB~EB~EB~EB~EB~DA~EB~DB~DB~EB~EB~DB~EB~EB~EB~DA~EB~DA~EB~EB~DA~DA~DA~DA~DA~DA~DA~DB~DA~DA~CA~CA~CA~CA~DA~DA~DA~DA~DA~DA~C@~DA~DA~DA~C@~C@~C@~C@~C@~C@~C@~C@~CA~C@~C@~CA~C@~C@~CA~C@~C@~C@~C@~B?~C@~C@~C@~C@~C@~C@~C@~C@~C@~B?~B?~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~A?~B?~B?~B?~B?~B?~B?~B?~B?~A?~B?~B?~B?~A?~A?~A>~A?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~B?~A?~A?~A?~A>~A>~A>~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A?~A?~A>~A>~B?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A>~B?~B?~B?~A>~B?~A?~A?~B?~A?~A?~A?~A?~A?~A?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~C@~C@~C@~C@~C@~CA~CA~CA~B@~CA~CA~CA~CA~CA~CA~CA~CA~DA~CA~CA~DB~DB~CA~DB~EB~DB~EB~EB~EB~EB~EB~EC~FC~EB~FC~FC~FC~FC~FC~FD~FC~FD~FD~FD~FD~FD~GE~GE~FD~GE~GD~GE~GE~HE~HE~GE~HE~HF~HF~HF~HF~HE~HF~IF~IF~IF~IF~IG~IG~IG~IG~JH~IG~IG~JH~JH~JH~JH~JH~JH~KH~KH~KH~KI~KI~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~LJ~MJ~MK~MK~MK~MK~MK~MK~NL~NL~MK~NL~NL~OM~OM~OM~OM~OM~OM~PM~PM~OM~PN~PN~PN~PN~QN~QO~QO~RO~RO~RO~RO~RP~RP~RP~RP~RP~RP~RP~RP~RP~RP~SQ~SQ~SQ~SQ~TR~TR~TR~TR~UR~UR~TR~UR~UR~US~US~US~VS~VT~VT~VT~VT~VT~WT~WT~WT~WU~WT~XU~XU~WU~XU~XU~XU~YV~YV~XU~XU~XV~YV~XV~YV~YW~YW~YW~ZW~YW~ZX~ZX~ZW~ZX~[X~[X~[X~[X~[X~[X~[Y~\Z~\Y~\Y~\Z~\Z~][~\Z~]Z~\Z~]Z~][~]Z~^[~][~^[~^[~^[~^[~]Z~]Z~^[~^[~^[~^[~^[~^[~_\~_\~_\~_\~^\~_\~_\~`]~_\~`]~`^~_]~`]~_]~`]~`]~`^~a^~a_~a^~a^~a_~a_~a^~b`~a_~a_~a_~a_~a_~b_~a^~a_~a^~b_~a^~a^~a^~a^~a^~a^~a^~a^~a^~a^~a^~a^~a_~a^~a_~a_~`^~`]~a^~`^~`^~_]~`]~`^~_]~_]~`]~`]~`]~`]~_]~_]~_]~_\~^\~_\~_\~_\~_\~^[~_\~^[~^[~^[~^[~][~^[~][~][~][~][~][~]Z~][~]Z~]Z~]Z~]Z~\Y~\Y~\Y~[Y~\Z~[Y~\Y~[X~\Y~\Y~[X~[X~[X~[X~[Y~\Z~\Z~]Z~\Y~[Y~[Y~[Y~\Y~[X~[X~[Y~[X~[X~ZX~ZX~ZW~ZW~ZX~[Y~[Y~ZX~YW~YV~XV~XU~XU~WU~WU~VT~VT~WT~WT~VS~WT~WT~VS~VS~VS~VS~US~UR~UR~VS~US~US~UR~US~UR~TR~TR~TR~SQ~SQ~TQ~RP~SQ~SP~SP~SP~SP~SP~RP~RP~RP~RP~RO~RO~RO~QN~QO~QN~QO~PN~PN~PN~PN~PM~OM~OM~PM~OM~OM~OM~OM~NL~OL~NL~NL~OL~NL~NK~NL~MK~MK~MK~MK~MK~MK~MK~MK~MJ~MJ~LJ~LJ~LI~LI~LI~KI~KI~KI~JH~KI~JH~JH~JH~JH~JH~JH~JG~JG~JG~JG~JG~JG~JG~IG~IG~IG~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~HF~GE~GE~HE~HE~HE~HE~HE~HE~GD~GD~GD~FD~FD~FD~FD~FD~FD~FD~EC~EC~EC~EC~EC~EC~EC~EB~EC~EC~EC~DB~EB~DB~EB~DA~EB~DA~EB~DA~DA~DB~DB~DB~DA~DA~CA~DA~DA~CA~DA~DA~DA~C@~DA~DA~C@~C@~C@~CA~C@~C@~C@~C@~C@~C@~C@~C@~B@~B@~B@~C@~C@~B?~B?~B?~B?~B@~B@~B@~B@~B@~A?~B?~B?~B?~B?~A?~B?~A?~B?~A?~A>~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A?~A?~A?~A?~A?~A?~A?~A>~A?~A?~A?~A?~A>~B?~A>~B?~B?~A>~B?~A?~A?~A?~B?~A?~B?~B?~B?~B?~A?~A?~A?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~B?~B?~B?~B@~C@~B@~A?~B@~B@~B@~B@~B@~B?~B?~B?~C@~C@~C@~B?~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~DA~CA~C@~C@~C@~C@~C@~C@~C@~CA~DA~DA~CA~DA~C@~DA~C@~C@~DA~DA~DA~CA~DA~DA~DA~DA~DA~DA~DA~DA~CA~DB~DB~DA~DA~DA~DA~DA~DA~EB~EB~EB~EB~DA~EB~EB~EB~EB~EB~EB~DA~EB~DA~EB~EB~EB~EB~EB~EB~EB~DB~EB~EB~EB~EB~EB~EB~FC~EB~EB~EB~EB~FC~EB~EB~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EC~EB~EB~EB~EB~EB~DA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~EB~EB~DA~EB~EB~EB~EB~EB~EB~DB~DB~EB~DB~DA~DA~DB~DA~DA~DA~DB~DA~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~C@~DA~DA~C@~C@~C@~CA~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B?~C@~C@~C@~B@~C@~C@~B@~B?~B@~B@~B@~B?~B?~B?~B?~B@~B@~B@~B?~B?~B?~C@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~B?~B?~B?~B?~A?~B?~A?~A?~A?~A?~B@~A?~A?~B?~B?~A>~A?~A?~A?~A?~A?~B?~A?~A?~A>~A>~A?~A?~A?~A>~A?~A?~A?~A>~B?~A?~A?~B?~A?~A?~A?~A?~A?~A>~A?~A?~A?~A?~B?~A?~B?~A?~A>~A>~B?~B?~A>~B?~B?~B?~B?~A?~B?~B?~A?~A?~B?~A?~A?~A?~A?~A?~A?~B@~B?~B?~B?~B@~B?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B?~B@~B@~B@~B@~B@~C@~C@~C@~B@~CA~CA~CA~DB~CA~CA~CA~CA~CA~CA~CA~DB~DB~DB~DB~DB~DB~DB~DB~DB~EB~EC~EB~EB~EC~EC~EC~EC~EC~EC~EC~FC~FD~FD~FD~FD~FD~FD~FD~FD~GE~FD~FD~GE~GD~GE~GE~GE~HE~HE~HE~HE~IF~IF~HF~HF~HF~HF~HF~IF~IF~IG~IG~IG~IG~IG~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~JH~KI~JH~KI~LJ~KI~LJ~LJ~LJ~LJ~LJ~LJ~MJ~MJ~MJ~NK~MK~MK~NK~NK~NL~NK~NL~NL~NL~NL~OM~OM~OL~OM~OM~PN~PN~PN~PN~PN~PN~PN~PN~PN~PN~QO~RO~RO~RP~RP~RP~RP~RP~SP~RP~RP~RP~SP~SP~SP~SQ~TR~TR~TR~TR~TR~TR~TR~UR~TR~TR~US~US~VT~VS~VS~VS~VT~VT~VT~WT~WT~WT~WU~WT~XU~XU~XU~XV~WU~XV~YV~YV~YV~YW~YW~YW~XV~YW~YW~YW~ZW~ZW~ZX~ZX~ZX~[X~[X~[Y~[Y~[Y~[Y~[Y~\Y~\Y~\Z~]Z~]Z~\Z~]Z~][~][~^[~][~^[~][~][~^[~^[~^[~^[~^[~_\~_\~^[~^[~^[~_\~_\~_\~_\~_\~_\~_\~_]~_]~_]~_]~_]~_]~`]~`]~`]~`]~a^~a^~`^~a^~a^~a^~a_~b_~b`~b`~b`~b`~c`~b`~b`~b`~b`~b`~b`~b`~b`~c`~b_~b_~b_~b_~b_~b`~b`~b_~b`~b`~b_~a_~b`~a_~b`~b`~b`~a^~a^~b_~a^~b_~a_~`^~`^~a^~`]~a^~a^~`]~`]~a^~`]~a^~`]~`]~`]~`]~_\~`]~_]~_]~^\~_\~_\~_\~_\~_\~^[~_\~_\~^[~^[~^[~^[~^[~]Z~^[~^[~]Z~]Z~]Z~]Z~\Y~\Y~\Z~\Y~\Y~\Z~\Y~[X~[Y~[X~\Y~[X~[X~[X~[X~\Y~\Y~\Y~\Y~\Z~\Y~\Z~\Y~\Y~\Y~[X~[X~[X~[Y~[Y~[Y~ZX~YW~XV~XV~XV~WU~XU~XU~XU~WU~WU~WU~WT~WT~WT~WT~WT~WT~WT~VT~WT~VS~VS~VS~VS~VS~VS~VT~US~VS~US~US~TR~UR~TQ~TR~TQ~TQ~TQ~TQ~SQ~SP~SQ~SP~SP~RP~RP~RO~RP~RO~QO~QO~QO~QO~QN~PN~PN~QN~PM~PM~PM~PM~PM~OM~OM~OM~OM~OM~OM~OM~NL~NL~NL~NL~NL~MK~NK~MK~MK~MK~MJ~MJ~MJ~LJ~LJ~MJ~LI~LI~LI~LJ~LI~LI~KI~KH~KI~JH~KI~JH~JH~JH~JG~JH~JG~JG~JH~JH~IG~IG~IG~IG~IG~IG~IG~IG~HF~HF~HF~HF~HF~HE~HE~HE~GE~HE~HE~GE~GE~HE~GE~GE~GD~GE~FD~FD~GD~FD~GD~GD~FC~FC~FC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EC~EB~EB~EB~EB~EB~DB~DB~DB~DB~DA~DA~DA~CA~DB~CA~DA~DA~DA~DA~CA~CA~C@~CA~CA~DA~DA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~C@~C@~B@~C@~B@~B@~B@~B@~B@~B@~B?~B@~B?~A?~B@~B@~B@~B?~B?~B?~B?~A?~A?~B?~B?~A?~A?~B?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A>~A>~A>~A?~A>~A>~A?~A?~A?~A?~A>~A>~B?~B?~B?~A?~A?~B?~A?~A?~B?~B?~B?~A?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B@~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B@~B?~B?~B@~B?~B@~B@~B?~B@~B?~C@~B?~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~DA~DA~CA~CA~CA~CA~CA~CA~DA~DA~DA~DA~DA~DA~CA~CA~DA~DA~DA~DA~DA~DA~DA~DA~EB~EB~DA~EB~EB~DA~DA~EB~DA~EB~DA~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EC~EB~EC~EB~EB~EB~EB~FB~EB~FB~EB~EB~EB~EB~EB~EB~EB~EB~FC~EB~EB~EB~FC~EB~EB~EC~EC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~EB~EB~EB~EB~EB~EB~EC~EB~EB~EB~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~FC~EB~EB~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~EB~EB~EB~FC~EB~EB~EB~EB~FC~EB~FC~EB~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DB~DA~EB~EB~EB~DA~DB~DB~DB~DB~DA~DA~DB~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~C@~C@~C@~C@~C@~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B?~C@~B@~B@~B@~B@~C@~B?~B?~B@~B@~B@~B@~B@~B@~C@~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~B@~A?~B?~B?~B?~B?~B?~B?~B?~B?~A?~A?~A?~B?~B?~B?~A?~A?~A?~B?~A>~A?~B?~A?~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A>~A>~A?~A?~A?~B?~B?~B?~B?~A?~A?~A?~A?~A?~A?~A?~A?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~B?~A?~A?~B?~B?~B?~B?~B?~B?~A?~B@~A?~B@~B@~B@~B@~B@~B?~B?~B?~B@~C@~B?~C@~C@~B@~B@~B@~B@~B@~B@~B@~B@~CA~B@~B@~C@~C@~CA~CA~B@~C@~CA~CA~CA~CA~CA~CA~DA~DA~CA~DB~DB~DB~DB~DB~DB~DB~EC~DB~EC~EC~EC~EC~EC~EC~EC~FC~FC~FC~FC~FC~FC~FD~FD~FD~FD~FD~FD~FD~FD~GE~FD~GE~GE~GD~GE~HE~GE~GE~HF~HF~HF~HF~HE~HF~HF~HF~IG~IG~IG~IG~IF~IG~JH~JG~JG~JH~JH~JH~JH~JH~JH~JH~KI~KI~KI~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~MJ~MJ~MJ~MK~MK~MK~MK~MK~MK~NK~NL~NL~NL~OL~OL~NL~NL~OM~OM~PM~PN~PN~PN~PN~PN~PN~QO~QN~QN~QN~QN~RO~RO~QO~RO~RP~SP~RP~RP~RP~SP~RP~SP~SP~SQ~SQ~TQ~TR~TR~TR~UR~TR~TR~UR~UR~US~VS~US~VS~VS~VS~VS~VS~VT~WT~WT~WT~WU~XU~XU~WU~XU~XV~XV~YV~XV~XV~XV~YV~YV~YV~YV~ZW~YW~YW~YW~[X~ZW~ZX~ZX~ZX~[X~[X~\Y~[Y~\Y~\Y~\Y~\Y~\Z~\Y~]Z~]Z~^[~]Z~^[~^[~]Z~^[~^[~^[~^[~^[~^\~^\~_]~_]~_\~_\~_\~_\~_\~_\~`]~_\~`]~_]~`^~`^~a_~a_~`]~a^~`]~`]~`^~`^~a^~a^~a_~a^~b_~b_~a_~b`~a_~b`~b`~b`~b`~b`~c`~ca~c`~c`~c`~da~da~da~ca~da~ca~ca~ca~da~c`~c`~c`~b`~b`~b`~b`~b`~c`~b`~c`~b`~c`~c`~c_~b_~b_~b_~b_~b`~b_~b_~b_~b_~a^~b_~a^~a^~a^~a^~b_~a^~a^~a^~a^~`^~a^~`]~`]~_]~`]~_]~_]~_\~_\~_\~_\~_\~_\~_\~_\~_\~_\~^[~][~^[~^[~^[~^[~^[~]Z~]Z~^[~]Z~]Z~]Z~\Y~\Y~\Y~\Y~\Y~\Y~\Y~\Y~[X~\Y~[X~[X~[X~[X~[X~[Y~[X~[X~\Y~[X~[X~ZX~YW~ZW~YW~YV~YV~YV~XV~XV~YV~YV~XU~XU~XU~WU~XU~XU~WT~WT~WT~WU~WT~WT~WU~WT~WT~WT~VT~VT~UT~VT~VS~VS~VS~VS~VS~UR~UR~UR~UR~TQ~TR~TQ~TQ~TQ~TQ~TQ~SQ~SQ~SP~SP~RO~RO~RO~RP~RO~RP~RO~QO~QO~QO~QN~QO~PN~PM~PN~PN~PN~OM~PN~OM~OM~OM~OM~OM~NL~NL~NK~NL~MK~NL~NL~NL~MK~MK~NK~MK~MJ~MJ~MJ~MJ~MJ~LJ~LI~LJ~LJ~KI~LI~KH~KH~KI~KI~KI~JH~KI~JH~JH~JH~JH~IG~IG~IG~IG~IG~JG~IG~IG~IG~IG~HF~HF~IF~HF~HF~HF~HE~HF~HF~HE~HE~GE~HE~HE~GE~HE~HE~HE~GD~GD~GE~GD~GD~GD~GD~FC~FD~FC~FC~FC~FC~EB~EC~EC~EC~EC~EC~EC~EC~EC~EB~DB~DB~DB~DB~DA~DB~DB~DA~DB~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~DA~DA~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~B?~B@~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~B?~A?~A?~A?~A?~A?~B@~A?~B?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~A?~B?~A?~B?~B?~A?~A?~B?~A?~A?~A?~A?~A>~A>~A?~A?~A?~A?~B?~B?~B?~B?~B?~A?~A?~A?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~C@~B?~B?~C@~C@~B?~C@~B?~C@~C@~C@~C@~B?~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~DA~DA~DA~DA~DA~DA~CA~CA~DA~DA~DA~DA~DA~DA~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~FC~EC~FC~EC~EC~EB~EB~EC~EC~EB~EB~EB~EB~FC~FB~FC~EB~FB~EB~FC~EB~FC~FB~EB~FC~EB~FC~FC~EB~FC~FB~EC~FB~FC~EB~EB~FC~EB~EB~FB~FB~FB~FC~FB~FC~FB~FB~EB~EC~FC~EC~EC~EC~EB~EC~EB~FC~FB~EB~EB~FC~EB~EB~FC~FC~EB~FB~FB~FC~FB~FC~FB~FB~FC~EB~FC~FC~FC~EB~FC~FC~EC~EC~EC~EC~EB~EC~EB~FC~FC~EB~EB~EB~FC~FC~FC~FC~FC~FC~EC~EC~EC~EB~EB~EB~EB~EC~EB~EB~EB~FC~FC~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~DB~DB~DB~DB~DB~DB~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~C@~C@~C@~DA~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~C@~C@~C@~B?~C@~C@~C@~C@~C@~C@~B@~B@~C@~C@~B@~B@~B@~B@~B@~B?~B@~B@~C@~B@~B?~B?~B@~B?~B?~B?~B?~B?~B@~B@~B?~B?~B?~B?~B@~B?~A?~A?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~A?~A?~B?~A?~A?~A?~A?~A?~A?~A?~B?~A?~A?~A?~A?~A?~A?~A?~B?~B?~A?~A>~A?~B?~A?~B?~A?~B?~A?~A?~A?~B?~A?~A?~A?~B@~B?~B?~A?~A?~B?~B?~B?~B?~B?~B@~B@~B@~B@~B@~B@~B@~B@~B?~B@~B@~C@~B@~C@~B@~B@~B@~B@~B@~B@~C@~B@~C@~C@~B@~CA~B@~C@~CA~CA~CA~CA~CA~CA~CA~CA~DB~DB~DA~DA~DB~DB~DB~DB~DB~DB~DB~DB~EC~EB~EB~EB~EC~FC~EC~EC~EC~EC~EC~FC~EC~FC~EC~FC~FD~FD~FD~FD~GD~FD~GD~GE~GE~GD~GE~GE~GE~GE~HF~HE~HF~HF~HF~IF~HF~IF~IF~IF~HF~IG~IG~IG~IG~JH~IG~JH~JH~JH~JH~JH~JH~JH~JH~KI~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~LJ~LJ~MK~MK~MK~MK~MK~MK~NK~MK~NL~NK~NL~NL~NL~NL~NL~OL~OM~OM~OM~PM~PN~PN~PN~PN~PN~PN~PN~QN~QN~QO~QO~QO~QO~RP~RO~RP~RP~SP~RP~RP~RP~RP~SQ~SQ~TQ~TQ~TQ~TQ~TR~TR~UR~UR~TR~UR~US~UR~US~VT~WT~WT~VS~WT~VS~WU~WT~WT~WT~XU~XU~XU~XU~XV~XV~YV~YW~YV~YW~YW~ZW~ZX~ZW~ZW~ZW~ZW~ZW~ZX~ZX~ZX~[X~[X~[X~\Y~\Y~\Y~\Y~\Y~\Z~\Y~\Z~\Z~\Z~]Z~][~][~][~^\~^[~^\~_\~_]~^\~_]~`]~_]~_^~_]~_]~_]~_]~_]~`]~_]~`]~`]~`]~`]~`]~a^~a^~a^~a_~a_~`]~`]~`]~a^~a_~a_~a_~a_~a_~b_~a_~b_~b`~b`~b`~b`~b`~ca~c`~c`~b`~c`~ca~da~da~da~db~ca~da~da~da~ca~ca~ca~ca~ca~ca~ca~d`~c`~da~c`~da~ca~c`~da~da~ca~ca~da~c`~b`~c`~b`~b`~c`~c`~c`~c`~b`~b_~b_~b_~b_~b_~b_~b_~b_~a^~a^~a^~a^~a^~`^~a^~`]~`]~`]~`]~`]~_\~_\~_\~`]~`]~_\~`]~_\~_\~_\~_\~_\~^[~^[~^[~^[~][~][~][~]Z~]Z~]Z~]Z~]Z~]Z~]Z~\Y~\Z~\Y~\Y~\Y~[X~\Y~[X~[X~[X~[X~[X~[X~[X~ZW~ZW~ZW~ZW~ZW~ZW~ZW~YV~YV~YW~YV~YV~YV~YV~YV~XU~XU~XU~XU~XU~XU~XU~XU~XV~XV~WU~WU~WU~VT~WT~WT~WT~VT~VS~VS~VS~UR~VS~UR~UR~UR~US~UR~UR~UR~TR~TR~SQ~SQ~SQ~SP~SP~SP~SP~RO~RP~RO~RP~RP~QO~RO~QO~QN~QO~QO~QN~QN~QN~QN~PN~PN~OM~PM~OM~OM~OM~OL~OM~OL~OL~NL~NL~NL~NL~NL~NK~MK~NK~MK~MK~MK~MK~LJ~LJ~LJ~LJ~LJ~LJ~LI~LI~LI~KI~KI~KI~KI~KI~JH~JH~JH~JH~JH~JH~IG~IG~IG~IG~IG~IG~IG~IG~IF~IF~IF~IF~IF~HF~IF~HF~HF~HF~HE~HE~HE~HE~HE~GE~GE~GE~GD~GD~GD~GD~GD~GD~GD~FD~GD~FC~FC~FC~EC~EC~FC~FC~EC~EB~EB~EB~EB~EC~DB~EB~DB~EB~EB~DB~DB~DB~DA~DB~DB~DA~DA~DB~DA~DA~DA~DA~DA~DA~DA~CA~CA~DA~DA~DA~DA~DA~DA~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~C@~B?~B?~B?~B?~B?~C@~B@~B@~B?~B?~C@~B@~B?~B?~B?~B?~B@~B@~B?~B?~B?~B?~B?~B?~B@~B?~B?~B?~B?~B?~B@~A?~B?~A?~B?~B?~B?~B?~B?~A?~B?~B?~B?~B?~B?~B?~B?~A?~A?~B?~B?~B?~A?~A?~B?~B?~B?~B?~B?~B?~A?~A?~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~C@~B?~B?~B?~B?~B?~C@~B?~B?~B?~B?~B?~B@~C@~B@~C@~C@~C@~C@~B?~B@~B@~B?~C@~C@~B@~C@~C@~C@~C@~C@~C@~C@~C@~CA~DA~CA~DA~CA~DA~C@~DA~DA~DA~DA~CA~DA~DA~C@~DA~DA~CA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~EB~DA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EC~EB~EB~FB~FC~EB~EB~EB~FC~EC~EC~EC~EC~EC~EC~EC~EC~FC~EC~EC~EB~EB~EB~FC~FC~FC~FC~FC~EC~EC~EC~FC~FC~FC~FC~FC~EC~FC~FC~EC~EB~FC~FB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~EB~FC~FC~FC~FC~FC~EC~FC~EB~FC~EC~EC~FC~FC~FC~FB~FC~FC~FC~FB~FC~FC~FC~EC~FC~EC~EB~FC~FC~EB~FC~EC~EC~FC~FC~FC~FC~FC~FC~EC~FC~EC~FC~EC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EC~EC~FC~FC~FC~EC~FC~EB~FC~FC~FC~FC~FC~EB~FC~EB~EB~EB~EB~FC~EB~EB~EB~EB~EB~FC~EB~EB~EB~EB~EB~EB~EB~DB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~C@~DA~DA~CA~DA~C@~C@~C@~C@~DA~C@~CA~CA~C@~C@~C@~C@~C@~B@~B@~B@~B@~B@~B@~B@~C@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B?~B@~B?~C@~C@~B@~B@~B@~B?~B?~B?~B?~B@~B?~B?~B?~B?~B@~A?~B@~B?~B?~B?~B?~A?~B?~B?~B?~B?~A?~A?~A?~B@~B?~A?~A?~A?~A?~B?~A?~B?~A?~B?~A?~A?~A?~B?~B?~A?~B?~A?~A?~B?~A?~A?~A?~B?~B?~B?~B?~B?~B?~B?~B?~B?~A?~B?~B@~B?~B?~B?~B@~B@~B@~B?~B?~B?~B?~B@~B?~B?~B?~B?~B?~B?~B@~B@~B@~C@~B@~C@~C@~C@~C@~C@~CA~C@~C@~C@~CA~C@~CA~CA~DA~CA~CA~CA~CA~CA~CA~CA~DA~DB~DA~DB~DB~DB~DB~EB~EB~EB~EB~EB~EC~EC~EC~EC~FC~FC~FC~EC~FC~FC~FD~EC~EC~FD~FD~FD~FD~FD~GD~GD~GD~GD~GD~GD~HE~GD~HE~HE~HF~HE~HE~HE~HE~HF~HF~IF~IF~IF~HF~IG~IG~IG~IG~JH~IG~JH~JH~JH~JH~JH~KH~KH~KH~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~LJ~MK~LJ~LJ~MJ~MK~MK~MK~MK~NK~NL~MK~MK~NL~NL~NL~NL~OL~OL~OL~PM~PM~PN~OM~PN~PN~QN~PN~PN~QN~QN~QN~QO~QO~RO~RP~QO~QO~QO~SP~RP~RP~RO~RO~SP~TQ~TQ~SQ~TQ~TQ~TR~TQ~TR~UR~UR~UR~VS~VS~VT~VT~VT~VT~WT~WT~WU~WT~XU~WT~WU~XV~XV~WU~XV~XV~YV~YV~YV~YV~YV~YV~YW~YW~YW~ZW~ZW~ZW~ZX~[X~[X~[X~[X~[X~\Y~[X~[Y~\Y~\Y~]Z~\Z~\Z~\Z~]Z~]Z~]Z~][~^[~][~^\~_\~_\~_\~_\~`]~_\~`]~`]~_\~`]~`]~`^~`]~`^~`^~`^~a^~a^~a^~a^~a^~a^~a^~a_~a^~b_~b_~a^~b_~b_~a^~a_~b_~b`~b`~b`~b`~b`~c`~c`~c`~da~c`~b`~ca~ca~da~da~da~da~db~eb~da~db~da~db~da~da~da~da~da~da~da~da~da~eb~eb~eb~da~da~eb~db~eb~db~db~da~da~da~da~da~da~da~da~da~ca~ca~db~ca~c`~c`~c`~c`~b_~b_~b_~c_~b_~b_~b_~b_~a_~a^~a^~a^~b^~a^~a^~`^~`^~`^~`^~`^~`]~`^~`^~`]~_\~_\~`]~_\~_\~_\~_\~_\~^[~_\~^[~^[~^\~^[~]Z~]Z~]Z~]Z~]Z~\Z~\Y~\Y~\Y~\Y~[X~\Y~[X~\Y~[X~[X~[X~[X~ZX~ZX~ZW~ZX~ZW~ZW~YW~ZW~ZW~ZW~ZX~ZW~YW~YW~YW~YV~YV~YV~YV~YV~YV~XV~XV~XV~XV~YV~XU~XV~XU~WU~WU~WU~WU~WU~WT~VT~WT~VS~VS~VS~US~US~US~US~US~UR~UR~TQ~TQ~SQ~SQ~SQ~SP~SP~SP~SP~SP~RP~RP~RP~QO~QO~QO~RO~QO~QO~QO~PN~PN~PN~PN~PN~PN~OM~OM~OM~OM~NL~OL~OL~NL~OL~NL~OL~NL~NL~NL~NK~MK~NL~NK~MK~MK~MK~MJ~LJ~LJ~LJ~LJ~LI~LI~LI~LI~LI~KH~KI~JH~KH~JH~JH~JH~JH~JH~JH~IG~IG~IG~IG~IG~IG~IG~IG~IF~IF~IG~HF~HF~HF~HF~HF~HE~HE~HE~GE~HE~HE~HE~GE~GE~GD~GD~GD~GD~GD~GD~FD~FD~FD~FD~FC~FC~FD~FC~FC~FC~FC~EC~FC~EB~EC~EB~EB~EB~EB~EB~EB~DB~DB~DB~DB~DB~DA~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~CA~DA~CA~C@~DA~DA~DA~C@~CA~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B?~B?~B?~C@~B?~C@~C@~B?~B?~B?~B?~B?~B?~B?~C@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~B@~B?~B?~B?~B?~B?~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~B?~B@~B@~B?~B?~B?~B?~B?~B@~B?~B@~B@~B@~B?~C@~B?~B?~B?~B?~B@~B?~B?~B?~C@~B?~B@~B?~B?~B?~B?~B@~B?~B?~B@~B?~B?~B?~B@~B@~B@~C@~B?~C@~C@~C@~C@~C@~C@~B@~C@~B@~B@~C@~B@~C@~C@~C@~DA~C@~DA~CA~C@~C@~C@~C@~DA~DA~CA~DA~DA~DA~CA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EC~EB~EB~EB~EB~EB~EB~EB~EB~FC~FC~FB~FC~EB~FB~EB~EB~EB~FC~FC~FC~FC~FC~FC~EC~EC~EC~FC~FC~FC~FC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EC~EC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EC~EC~FC~FC~EC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EC~FC~FC~FC~EB~FC~FC~FC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EC~FD~EC~FC~EC~FC~FC~FC~FC~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FB~FC~FC~FC~FC~EB~FC~EB~FC~FC~EB~FC~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DB~EB~DB~DB~DB~DB~EB~EB~EB~DA~EB~DA~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~C@~C@~C@~C@~DA~C@~C@~C@~DA~DA~C@~CA~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~B?~B?~C@~B@~B@~C@~C@~B@~B?~C@~C@~B@~B@~C@~C@~B@~C@~B?~C@~B?~C@~B@~B@~B@~B@~B?~B@~A?~A?~B?~B?~B?~B?~B?~B?~B?~B?~A?~A?~A?~B?~B?~B?~B?~A?~B?~B?~B?~B?~B@~B@~B@~B@~B@~A?~B@~A?~A?~B?~B@~B@~B@~A?~A?~A?~A?~B?~A?~B?~A?~B?~B?~B?~B@~B?~B?~B?~B?~B@~B?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~C@~B@~C@~C@~C@~C@~C@~C@~C@~CA~CA~CA~CA~CA~CA~CA~CA~DB~DB~DA~DA~DA~DA~DA~DB~EB~EB~EB~EB~EB~EB~EB~EB~EC~EB~EC~EC~EC~EC~FC~FC~FC~FC~FD~FD~FD~FD~FC~FD~GE~FD~GE~FD~GE~GD~GE~GE~GE~HE~HE~HF~HF~HF~HF~HF~HF~HF~HF~HF~IG~IG~IG~IG~JG~JH~JG~JG~JG~JH~JH~JH~JH~KH~KH~KH~KH~KH~KI~KI~KI~KI~KI~LJ~LJ~LJ~MK~MK~LJ~MK~MK~MJ~MK~MK~MK~MK~MK~NK~NK~NK~NK~OL~OM~OM~OM~OM~OM~PN~OM~PM~PN~PN~PN~QN~QN~PN~QO~QO~QO~RO~RO~RP~RP~RP~SP~RP~SP~TQ~SQ~SP~TQ~SQ~TQ~TR~TR~TR~TR~TR~UR~US~US~VS~VS~VS~WT~VS~WT~WT~WT~WT~WU~XV~XU~XU~XU~XV~XV~YV~YV~YW~ZW~ZW~ZW~ZW~YW~YW~ZX~ZX~ZW~ZW~[X~[X~[X~[X~[Y~[X~\Y~\Y~\Y~\Y~\Y~\Z~\Z~\Z~]Z~\Z~]Z~]Z~^[~^[~^[~^[~^[~^\~_]~_]~`]~`]~`]~`^~`]~a^~a^~a^~`]~`]~a^~a^~a^~a^~a^~a^~a^~a^~a^~b_~b_~b_~b`~b`~b`~b_~b_~b_~b_~c`~c`~b`~c`~c`~ca~da~da~da~da~da~eb~da~eb~da~eb~da~da~eb~da~db~eb~eb~eb~eb~db~db~eb~da~eb~ea~ea~eb~eb~fc~eb~eb~eb~eb~eb~eb~eb~eb~eb~eb~eb~eb~eb~da~eb~ea~d`~da~da~da~da~da~db~ca~c`~c`~c`~c`~da~c`~c`~c`~b_~c`~b_~b_~b_~b_~b_~a^~a^~b_~a^~a^~`^~a^~`]~`]~a^~a^~a^~`]~a^~`]~`]~`]~`]~`]~_]~_]~^\~^\~^[~^[~^[~^[~^[~^[~]Z~^[~]Z~]Z~\Y~]Z~\Y~]Z~\Y~\Y~\Y~\Y~\Y~\Y~[X~[X~\Y~[X~ZX~ZX~ZW~ZW~[X~[X~ZW~ZW~ZW~ZW~YW~ZW~YW~YV~YV~YV~YV~YV~ZW~XV~YV~YV~YV~XV~XV~XV~XU~XV~XU~WU~WU~WU~WT~WT~WT~VS~VS~VS~US~US~US~US~UR~TR~TR~TR~TR~TQ~SQ~SQ~SQ~SQ~SQ~SP~RP~RP~RP~RP~RO~RO~RO~QO~RO~QO~QO~QO~QN~QN~QN~QN~QN~PN~PM~PM~OM~OM~PM~OM~OL~OM~OL~OL~NL~NL~NK~NK~NK~NK~MJ~MJ~MJ~MJ~MJ~LI~LJ~MJ~LI~LI~LI~LI~LI~LI~KI~KI~KI~KI~JH~JH~JH~JH~JH~JH~JH~JH~IG~IG~IG~IG~JG~IF~IF~HF~HF~HF~HF~IF~HF~HF~GE~HE~HF~GE~GE~GD~GE~GD~GD~GD~GD~GD~GD~GD~GD~FD~FD~FD~FD~FD~FC~FD~FC~FD~FC~EC~EC~EC~EC~FC~FC~EB~EB~EB~EB~DB~DB~DB~DA~DB~DA~DA~DB~DB~DB~DB~DA~DA~DA~DA~DA~DA~DA~DA~CA~CA~DA~DA~DA~C@~CA~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~B@~C@~C@~C@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~B?~B?~B?~B?~B?~B?~B?~B@~B@~B?~B?~B?~A?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~C@~B?~B@~B?~A?~B@~B?~B@~B?~B?~B?~B?~B?~B?~B?~B?~B?~B@~B?~B?~C@~B?~B?~B?~B@~C@~B?~B@~B@~B@~B@~B@~B@~C@~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~CA~C@~C@~C@~C@~CA~DA~CA~DA~DA~CA~DA~DA~DA~DA~DA~DA~DA~CA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~DA~EB~DA~EB~EB~EB~EB~DA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FB~EB~FB~EB~EB~FB~FB~EC~EB~EB~FC~EB~EC~FC~FC~EC~EC~EC~FC~EC~EB~FC~FC~FC~EB~EC~EC~EC~FC~EC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~FC~FC~FC~FC~FC~FC~FD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FD~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~EB~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~DA~EB~DA~DA~EB~EB~DA~DA~DA~DA~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~C@~DA~C@~DA~DA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B?~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~B?~C@~C@~C@~C@~B@~B?~B?~B@~B?~C@~C@~C@~C@~B?~C@~B?~B?~B?~C@~B@~C@~B?~B?~B?~B?~B?~C@~B?~B?~B@~A?~B?~B?~B?~B@~B?~B@~B@~B@~B?~B?~A?~B?~B?~B?~B?~B?~B@~B?~A?~A?~B?~B@~A?~B?~B?~B@~B?~B?~A?~B?~B?~B?~B?~B@~B@~B?~B?~B?~B?~C@~A?~B?~C@~B?~B?~B@~B@~B?~B@~B@~B@~CA~B@~B@~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~DA~C@~DA~DA~DA~DA~DA~CA~DA~CA~DA~DA~DB~DB~DB~DB~DB~DB~DB~DB~EB~EB~EB~EB~EB~EC~EC~FC~EB~EC~FD~FD~FD~FD~EC~FD~FC~FC~FD~FD~FD~FD~GD~GD~GE~GE~GE~GD~GD~GE~HE~HE~HE~HF~HE~HF~HF~HF~HF~IG~HF~IG~IG~IG~IG~JG~JH~JH~JH~JH~JH~JH~JH~JH~JH~KH~KH~KH~KI~KH~KI~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~MK~MK~MK~NK~NL~NK~OL~NK~NK~OL~OL~OM~OM~OM~PM~OM~PM~PM~PN~PN~PN~QN~QO~QN~QO~QO~RO~RO~RP~QO~RP~RP~RP~RP~RP~SP~TQ~TQ~TQ~TQ~TQ~TQ~UR~US~UR~US~US~US~US~VS~VS~VS~VS~WT~WT~WT~WT~WT~WT~XV~XV~XV~XV~XV~YV~YV~YV~YW~YW~YW~ZX~ZX~[X~ZW~ZX~ZW~ZX~[X~[X~[X~[Y~[X~\Y~\Y~\Y~\Y~\Y~\Y~\Y~]Z~]Z~]Z~]Z~]Z~][~][~^\~^[~^[~^[~^\~^\~_]~_]~_]~_]~_]~`^~`^~`]~a^~a^~a^~a^~a^~a^~b_~b_~b_~a_~a_~b`~b_~b_~b_~b_~b`~c`~b_~c`~c`~b`~c`~c`~c`~c`~c`~c`~c`~da~c`~da~da~eb~da~eb~fc~eb~eb~fc~ec~ec~fc~ec~eb~db~db~eb~eb~fc~eb~eb~eb~fc~eb~fb~eb~eb~fc~fc~fc~fc~fc~fc~fc~fc~fc~fc~ec~ec~ec~fc~fc~fb~fb~eb~eb~eb~eb~eb~eb~eb~eb~db~eb~da~eb~da~ea~da~da~da~da~c`~c`~c`~c`~c`~c`~c`~b_~b_~b_~b`~b_~b_~a_~a_~a_~a_~a^~a^~b^~a^~`]~`^~a^~`]~`]~`]~`]~_\~_\~`]~_\~_\~^[~^[~^[~^[~^[~^[~^[~^[~]Z~]Z~]Z~^[~]Z~]Z~\Y~]Z~\Z~\Y~\Y~\Y~[Y~[X~[X~[Y~[X~[X~[X~ZW~ZW~[X~ZW~ZW~ZW~ZW~ZW~ZW~YV~ZW~ZW~YW~YW~YW~ZW~YV~YV~YV~XV~XV~XV~XV~XV~XU~WU~WU~WU~WT~WT~WT~VS~VS~VS~VS~UR~US~TR~TR~TR~TQ~TQ~TQ~TR~SQ~SQ~TQ~SP~RP~RP~SQ~RP~RP~RP~RP~RO~QO~QO~QO~QO~QN~QN~QN~QN~PN~PN~PN~PM~OM~PM~PM~OM~OM~OM~OM~OL~NL~NL~NL~NK~NL~NK~MK~MK~MK~MK~MK~MK~LJ~LI~LI~LI~LI~LI~LI~KI~KI~KI~KI~KI~JH~JH~JH~JH~JH~JH~JH~JG~JG~JG~JG~IG~IG~IG~IG~HF~IF~HF~HF~IG~HF~HF~HE~HF~HE~HE~HE~GE~GE~GE~GD~GD~GD~GD~GD~GD~GD~GD~FD~FD~FD~FD~FC~FC~FC~FC~FC~FC~FC~FC~EC~FC~EC~EC~EB~EB~EB~EB~EB~EB~EB~DA~DA~DA~DA~DB~DB~DB~CA~DB~DA~DA~DA~DA~DA~DA~CA~DA~CA~C@~C@~CA~DA~C@~C@~CA~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~B@~B@~B@~B@~B@~B@~B@~C@~C@~B?~B?~B?~B?~B@~B?~C@~B?~C@~B?~C@~B?~C@~B?~B?~B?~B?~C@~B?~B@~B?~B?~B?~B?~B?~B@~B?~B?~B?~B?~B@~B?~B?~B?~C@~B?~C@~B?~B?~B?~B?~B?~B?~B@~B@~B@~B?~B@~C@~B?~C@~B@~B?~B?~B@~B@~B@~C@~B@~B?~B@~B@~B@~B@~C@~B@~C@~C@~B@~B?~C@~B?~C@~C@~C@~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~CA~DA~DA~DA~DA~DA~CA~CA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~DA~EA~EA~EB~EA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EC~EC~EB~EC~EC~EC~EC~EC~EC~EC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FD~FC~FD~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~FC~FC~FC~GD~FC~FC~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~FC~FC~FC~FC~GD~FC~FC~FC~FC~GD~FC~FC~FC~GD~GD~FC~FC~FC~GD~FC~FD~FC~FD~GD~GD~GD~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~GD~FC~FC~FC~FD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EC~FC~FC~FC~FC~FC~FC~FC~FC~EB~EB~EB~FC~FC~FC~EB~EB~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~EB~DA~DB~DA~EB~EB~EB~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~CA~DA~DA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B?~B@~B@~B?~B?~C@~C@~C@~C@~C@~C@~B?~C@~B?~B?~B?~C@~B?~B?~B?~C@~C@~B@~B@~C@~C@~B?~B?~B?~B?~B?~B?~B?~B@~A?~B@~B@~C@~B?~B?~B?~B?~B?~B?~B@~B?~B?~B?~B@~B?~B?~B?~B@~B?~B@~B?~C@~B?~B@~B@~B@~B@~B@~B@~B@~B@~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~DA~C@~DA~CA~C@~C@~DA~CA~DA~DA~DA~DA~DA~DB~DA~DA~DA~DB~DA~DA~DA~DB~EB~EB~EB~EB~EB~EC~EC~EC~EC~EC~EC~EC~EC~FD~FD~FD~EC~FD~FD~FD~FD~FD~GD~GD~GD~GD~GD~HE~HE~HE~HE~HE~HE~HE~IF~HE~HF~HF~HF~IG~IG~IG~IG~IG~IG~JG~JG~JG~JG~JG~JG~JG~JH~JH~JH~JH~KH~KI~KI~KI~KI~KI~KI~LI~LJ~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~MK~MK~MK~MK~MK~NL~NL~OL~OL~NL~OM~OM~OM~OM~PM~PM~PM~OM~PM~PN~PN~QN~QO~QN~QO~QO~QO~QO~QO~RP~RP~RP~RP~RP~SP~SP~SQ~SQ~TQ~TQ~UR~TR~VS~UR~UR~US~US~VT~VS~VS~WT~WT~WT~XU~XU~XU~XU~WU~XV~XV~YV~XV~XV~YW~YV~YW~YV~ZW~ZW~ZW~ZW~ZW~ZW~[X~[X~ZX~[X~[X~[X~[Y~[Y~\Y~[Y~[Y~\Z~[Y~\Z~]Z~]Z~]Z~]Z~]Z~^[~]Z~^[~^[~^[~^\~_\~_\~_\~_]~_]~_]~_]~`]~`]~a^~`]~a^~a^~a_~b_~b_~b`~c`~b`~b`~b`~b_~b`~b_~c`~b_~b_~c`~ca~c`~c`~c`~c`~da~ca~ca~ca~da~da~db~db~ea~eb~eb~eb~eb~eb~db~fc~fc~fc~fc~ec~fc~fc~fc~fc~fd~gd~fd~fd~fd~fc~fc~gd~fd~gd~gd~gd~gd~gd~fc~fc~gd~he~gd~gd~gd~gd~fc~gd~gd~fd~fd~gd~gd~fc~fc~fd~ec~fd~fc~fc~fd~ec~fc~fc~fc~eb~ec~eb~eb~eb~eb~eb~da~d`~da~da~ea~da~da~da~c`~c`~ca~ca~ca~c`~c`~c`~b_~b_~a^~a^~a^~a^~a^~a^~a^~a^~`]~a^~`]~`^~`]~_]~_\~`]~_\~_\~_\~_\~^[~_\~^[~^[~^[~][~^\~^[~][~][~]Z~]Z~]Z~]Z~\Y~\Y~\Y~\Y~\Y~\Y~\Y~\Y~[X~[X~[X~[X~[X~\Y~[X~[X~ZX~ZX~ZW~ZX~ZX~ZX~ZW~ZW~YW~YW~YW~YV~YV~YV~YV~XV~XV~XV~XU~XU~XU~XU~VT~WT~WT~VS~WT~VS~VS~VS~UR~UR~US~UR~TR~TR~TR~TR~TR~TR~TQ~TQ~SP~SQ~SQ~SQ~SP~RO~RO~QO~QO~QO~QN~QN~QN~QO~QN~QN~QO~QO~QN~QN~PM~PM~PM~OM~OM~OM~OM~OM~NL~NL~NL~NL~NL~NK~NK~MK~NK~MK~MK~MJ~MJ~LJ~LJ~LJ~KI~LJ~KI~KI~KI~LI~KI~KI~KH~KH~JH~JH~JH~IG~JH~IG~JH~IG~IG~IG~IG~IF~IG~IG~IG~IG~HF~IF~IF~IF~HF~HF~HF~HE~HE~HE~HE~GE~GE~GE~GE~GD~GD~FD~FD~FD~FD~FD~FD~FD~FC~FC~FC~FC~FC~FC~FC~FC~EC~FC~FC~EB~EB~EB~EB~EB~EB~EB~DB~EB~EB~DB~DB~CA~DB~DB~CA~DA~DA~DA~DA~DA~DA~DA~CA~CA~CA~C@~C@~C@~C@~C@~CA~C@~CA~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B?~C@~C@~B@~B@~B@~B@~C@~B@~C@~B@~C@~B@~C@~B?~B?~B?~B?~C@~B?~C@~C@~B?~B?~B?~B?~B@~B@~B?~B@~B@~B@~B@~B@~B?~B?~B@~B@~B?~B@~B@~B@~B@~B?~B@~B@~B@~B@~B@~B@~B?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~C@~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~C@~C@~C@~CA~C@~CA~CA~C@~C@~C@~CA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~DA~EB~EB~EB~EB~EB~EB~EA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EC~EC~EC~EC~EC~EC~EB~FC~EB~EB~EC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~FC~FC~GD~FC~GD~GD~GD~FC~GD~GD~GD~GD~FC~GD~GD~GD~FD~FD~GD~FD~GD~GD~GD~FC~GD~GD~FC~GC~GD~FC~FC~GD~GD~GD~GD~GD~GD~FC~FC~GD~FC~GD~GD~FC~FC~GD~GD~GD~GD~FC~GD~GC~GD~GD~FC~GD~GD~GD~GE~GD~GD~GD~FC~FC~GD~GD~GD~GD~FC~GD~GD~GD~GD~FC~FC~GD~FC~FC~GD~GD~GD~GD~FD~GD~GD~FC~GD~GD~GD~GD~FC~FC~FC~GD~FC~FC~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FB~FC~FB~FC~FC~FB~EB~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~DA~EB~EB~EB~EB~EB~EB~EB~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~C@~DA~C@~C@~DA~C@~DA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~B?~B?~C@~B@~B@~B@~B@~C@~C@~B?~B?~C@~B?~C@~C@~C@~C@~B@~C@~C@~B?~B?~C@~B@~B@~B@~B?~B?~B@~B@~C@~B?~C@~C@~B@~C@~B?~C@~B?~B@~B?~B@~B?~B@~B@~B?~B?~B?~C@~B?~B@~B?~B?~B@~B?~B@~B@~B@~C@~C@~C@~B?~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~DA~DA~C@~C@~CA~DA~DA~DA~DA~DA~DA~DB~DB~DA~DA~DB~DB~DA~EB~EB~EB~EB~DB~DB~EC~DB~EC~EC~EC~EC~EC~FC~EC~EC~FC~EC~EC~FC~FD~FD~FD~FD~GD~GE~GE~GE~GE~HE~GD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HF~HE~HE~IF~IF~IF~JG~IF~IG~JG~JG~JG~JH~JG~JH~JG~JH~JH~JH~JH~KI~KI~KI~KI~KI~KI~KI~LJ~LJ~LJ~LJ~LJ~LJ~MJ~MJ~MK~MK~MK~MK~MK~NK~NL~NL~OL~OL~OL~OL~OM~OM~OL~OM~OM~OM~PM~PM~PM~PN~QO~QO~QN~QO~QO~QO~RO~RO~QO~RP~RP~SP~SQ~SP~SQ~SQ~SQ~TQ~TQ~UR~TR~UR~US~US~VS~VS~US~VS~VT~VS~WT~WT~XU~XU~XU~XV~XU~YW~XV~YV~YV~YV~ZW~ZW~ZW~ZW~YW~ZW~ZW~ZW~ZW~ZW~[X~[X~[Y~[Y~[X~[X~[Y~\Y~\Y~[Y~\Y~\Y~]Z~]Z~]Z~]Z~]Z~]Z~^[~^[~]Z~^[~_\~^\~^\~^\~_]~_\~_\~_]~`^~`^~`]~`]~`]~`]~a^~a^~a^~b_~a_~b`~c`~ca~c`~c`~ca~c`~c`~c`~c`~b_~b_~c`~c`~ca~c`~da~da~da~da~da~db~db~db~eb~eb~ec~ec~eb~eb~fc~fc~fb~fc~fc~fd~ge~fd~gd~gd~gd~gd~gd~gd~gd~gd~ge~gd~he~hd~hd~gd~gd~gd~hd~gd~he~he~he~hf~he~he~he~gd~he~he~he~gd~gd~hd~gd~gd~gd~gd~gd~gd~he~gd~gd~gd~gd~gd~fc~fc~fc~fc~eb~fc~eb~ec~ec~ec~eb~db~da~ea~da~da~da~da~ca~da~c`~ca~ca~ca~ca~c`~b_~c`~b`~b_~b_~b_~b_~b_~a^~a^~a^~a^~a^~a^~a^~`]~`]~`]~`]~`\~`\~_\~_\~_\~_\~^[~^[~^[~^[~^[~^[~^[~^[~^[~]Z~]Z~]Z~]Z~]Z~]Z~\Y~\Y~\Y~\Y~[X~\Y~\Y~[X~\Y~[X~[X~[X~[X~[X~[X~ZX~ZX~ZX~ZW~ZX~ZX~ZW~YW~ZW~YW~YV~YV~YV~YV~XV~XV~XV~WU~XU~XU~WU~WU~VT~VT~VS~VS~VS~VS~VS~UR~UR~UR~UR~UR~UR~TR~TR~TQ~SQ~TQ~SP~SP~SP~SP~SQ~RP~RO~RO~QO~QO~RO~QO~QO~PN~QN~PN~PN~QN~PN~PN~PM~PN~PM~OM~OM~OM~OL~OM~OL~OL~NK~NL~NL~NK~NK~NK~MK~MJ~MJ~MJ~LJ~LJ~LJ~KI~LJ~LI~LI~LI~LI~KH~KI~KH~JH~KI~JH~JH~JH~JH~IG~JH~IG~IG~IG~IG~IG~IG~HF~IG~IF~IF~IF~IF~IF~HF~HF~HF~HE~HE~HE~HE~GE~HE~GE~GE~GE~GE~GD~GD~FD~FD~FD~GD~FC~FD~FC~FC~FC~FC~EC~EC~EC~EC~FC~EC~EC~EC~EC~EC~EC~EC~EC~DB~EB~EB~DB~EB~EB~DB~DB~EB~DB~EB~DA~CA~DA~C@~DA~CA~DA~CA~CA~CA~CA~CA~CA~C@~C@~CA~CA~C@~C@~CA~C@~C@~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~B@~B@~B?~B@~C@~B?~C@~C@~C@~B@~C@~C@~C@~B@~B@~B?~B@~B@~B?~B@~B@~B?~B?~B@~B?~B?~C@~B?~C@~C@~B@~B@~B@~B@~B@~C@~C@~B?~B@~B@~B@~B@~B@~B?~B@~B?~B?~B@~B?~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~C@~C@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~CA~DA~CA~CA~CA~CA~CA~CA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~EB~DA~DB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EC~EC~EC~FC~EC~FC~FC~FC~FC~FC~FC~EC~FC~FC~FC~FC~FC~FC~FC~EB~FC~FC~FC~FC~FC~FC~FC~GD~FC~FC~GD~FC~FC~FC~FC~GD~GD~FC~GD~FC~GD~GD~GD~GD~FD~FD~FD~FD~GD~FD~GD~GD~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~GC~GD~GD~GD~GD~GD~GD~HD~GD~GD~GD~GD~GD~GD~GD~GD~GD~FC~GD~GD~GD~GD~FC~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~FC~GD~GD~FD~FD~GD~FD~FD~GD~FC~FC~FD~FD~FD~FC~FC~FD~FC~FC~FC~FC~FC~FC~FC~FC~EC~FC~EC~EC~FC~FC~FC~FC~EB~EB~FC~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DB~EB~EB~EB~EB~EB~DA~EB~DA~EB~DA~DA~DA~CA~DB~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~C@~DA~C@~C@~C@~C@~DA~CA~C@~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~B@~B?~C@~C@~C@~B@~B@~C@~B@~C@~C@~C@~C@~C@~C@~B?~C@~B@~C@~C@~C@~C@~C@~C@~C@~B?~C@~C@~C@~C@~B?~B@~B?~C@~C@~B@~B@~C@~C@~C@~C@~C@~B?~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~DA~CA~C@~DA~C@~DA~DA~DA~DA~DA~DA~DA~DB~DB~DB~DB~DB~EB~DB~DB~DB~EC~DB~DB~EC~DB~DB~DB~EC~EC~EC~EC~FC~FC~FC~FC~FD~FD~FD~FD~FD~FD~GD~GD~FD~FD~GD~GE~HE~HE~HE~HE~GE~GE~GE~GE~HE~HF~HF~HF~HF~IF~IF~IF~IF~IF~JG~JG~JG~JG~JG~JG~JH~JH~JH~JH~JH~JH~JH~KI~KI~KI~LJ~KI~KI~LI~LI~MJ~LJ~MJ~LJ~MJ~MK~MK~MK~MK~MK~MK~MK~NK~NL~NL~NL~OL~NL~NL~OM~OM~OM~PN~OM~OM~PM~PN~PM~PN~PN~QN~QO~QO~QO~RO~RO~RO~RO~RO~RP~SQ~SP~SQ~TR~TQ~TQ~TR~TQ~TR~UR~UR~UR~UR~VS~VS~VS~VS~VS~WT~WT~WU~WU~WU~WU~XV~XV~XV~XV~YV~YV~YV~YW~ZW~ZW~ZW~ZW~ZW~[X~[X~ZX~[Y~[X~[X~\Y~\Y~[Y~\Y~\Y~\Y~\Y~]Z~\Y~]Z~]Z~]Z~]Z~]Z~]Z~^[~^[~^[~_\~^\~^\~^\~^\~_\~`]~`]~`]~`]~`]~a^~a^~a^~b_~b_~b_~a^~c`~c`~ca~c`~ca~da~c`~da~da~da~da~da~eb~da~da~da~da~da~da~da~da~ea~ea~eb~eb~eb~eb~eb~fc~fc~fc~fc~fc~fc~gd~gd~gd~gd~gd~gd~gd~he~he~he~he~ge~gd~ge~ge~ge~he~ge~ge~he~he~he~ie~he~he~he~hf~hf~he~hf~hf~hf~ge~hf~hf~hf~he~he~he~he~he~he~if~hd~gd~he~hd~gd~he~gd~he~gd~ge~gd~fd~fd~gd~fd~fd~gd~gd~fc~fd~fd~fc~fc~fc~fc~eb~fc~fc~db~db~eb~db~da~da~db~ca~c`~c`~c`~c`~c`~c`~b_~b_~a^~b_~a^~b_~a^~a^~a^~a^~a^~a^~`]~`]~`]~`]~`\~`\~_\~_\~_]~_[~_\~^[~^[~^[~^[~^[~^[~^[~^[~^[~]Z~^[~]Z~]Z~]Z~\Y~\Y~\Y~\Y~\Y~[X~\Y~[X~[Y~[X~[X~[X~[X~[X~[X~ZX~[X~[X~ZW~ZX~ZW~ZW~YW~YW~YV~YV~YV~YV~XV~XV~WU~WU~WT~WT~WT~WT~VT~VT~US~US~US~UR~UR~UR~UR~UR~UR~TR~TR~TQ~TQ~SQ~SQ~SQ~SQ~SP~SP~SP~RO~RP~RP~QO~RO~RO~RO~RO~QO~QN~QO~QO~PN~PN~PN~PM~PN~PM~OM~OM~OM~NL~OM~NL~NL~NL~NK~NK~NK~NK~NK~MK~MK~NK~MJ~MJ~LJ~LJ~LJ~KI~KI~KI~KI~KI~LI~KH~KI~JH~JH~JH~JH~JH~JH~IG~JH~IG~IG~IG~IF~IF~IF~IF~IF~IF~HF~HF~IF~IF~HE~HE~HE~HE~HE~HE~HE~HE~HE~GE~GD~GD~GD~GD~GD~GD~GD~FD~GD~FC~FC~FC~FC~FC~FC~FC~FC~EC~EC~EC~EC~EC~EC~EC~EC~EC~DB~EB~EB~EB~EB~EB~EB~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~CA~DA~DA~DA~CA~DA~C@~C@~CA~C@~CA~CA~C@~C@~C@~C@~C@~C@~CA~B@~C@~B@~B@~B@~B@~C@~B@~C@~B@~B@~B@~C@~C@~C@~B@~C@~C@~C@~C@~B@~B@~B?~B@~B@~B@~B@~B@~CA~C@~C@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~B@~C@~B@~B@~B@~C@~B@~C@~C@~C@~C@~C@~B?~C@~C@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~CA~CA~C@~C@~C@~DA~DA~DA~C@~DA~C@~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~DA~DA~DA~EB~EB~DA~DA~EB~DA~EB~EB~EB~EB~EB~EB~EC~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~FC~FC~FC~FC~EB~FC~FC~FC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~FC~FC~FC~FC~GD~GD~GD~GD~GD~GD~FC~GC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~FD~GD~GD~GD~GD~GD~GD~GD~GE~GD~GE~GD~GD~GD~GD~GD~HD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HE~GD~HD~HE~HD~GD~GD~GD~GD~GD~GD~GD~GD~GE~GD~GD~GD~GD~HE~GD~HE~GD~HD~HE~HE~GD~GE~GE~GE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~FC~GD~FC~GD~GD~GD~FC~GD~GD~FD~FC~FC~FC~GD~FC~FD~FC~FC~FC~FC~FC~FC~FC~FC~EC~FC~FC~FD~FC~FC~FC~FB~FC~EB~FC~EB~FC~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~EB~DA~DA~EB~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~C@~DA~C@~DA~C@~CA~C@~C@~CA~C@~C@~C@~C@~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~B@~B@~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~DA~C@~C@~CA~CA~CA~DA~DA~DA~DA~DA~DA~CA~DB~DB~CA~DB~DA~DB~DB~EB~EB~EB~DB~DB~DB~DB~EB~EB~EB~EB~EB~EC~EC~FC~FC~FC~FC~FD~FC~FC~FC~FD~FC~FD~FD~GE~GD~GD~GD~GD~HE~GE~GE~GE~GE~GE~HF~HF~HF~HF~HF~IF~IF~IF~IF~IF~JG~IG~JG~IG~IG~JH~JG~JG~JG~JH~JH~JH~JH~JH~KI~KI~KI~KI~KI~LJ~KI~LJ~MJ~LI~LJ~MJ~LJ~MK~LJ~MK~MK~MK~MK~NL~NL~NK~NK~NK~OL~OL~OL~OL~OL~OL~OM~PM~PM~PM~PM~PM~PN~PN~PN~PN~QO~QO~RO~RP~RP~RP~RP~SP~SP~SP~SQ~TQ~TQ~TQ~TQ~TR~TR~UR~US~US~VS~US~VS~WT~VS~WT~WT~WT~WU~WT~WU~WU~XV~XV~YV~YV~YW~YW~YV~YV~ZW~ZW~ZW~ZW~ZW~[X~ZW~[X~\Y~\Y~\Y~\Y~\Y~\Y~\Y~]Z~\Y~]Z~\Z~]Z~]Z~]Z~^[~^[~^[~^[~^[~^[~_\~_\~^\~_\~_\~_\~_\~`]~`]~`]~`]~`^~a^~a^~a^~a^~a_~b_~b_~c`~c`~da~da~da~eb~eb~eb~eb~eb~eb~eb~ec~fc~fc~fb~eb~fc~fc~eb~eb~eb~ec~ec~fc~fc~fc~fc~fc~fc~fc~fc~gd~gd~gc~fd~ge~ge~he~ge~he~he~he~hf~he~hf~he~he~he~he~if~if~hf~if~if~hf~hf~if~if~if~if~ig~if~if~if~hf~ig~hf~ig~hf~hf~ig~hf~hf~hf~if~ig~if~he~he~he~he~he~hf~hf~hf~ge~he~hf~ge~ge~ge~ge~ge~he~gd~gd~gd~gd~gd~ge~gd~fc~gd~fc~fc~fc~fc~fc~fc~eb~eb~eb~eb~da~da~db~da~ca~db~ca~ca~c`~b`~b`~c`~c`~b_~b_~b^~b_~a^~b_~b_~a_~`]~`]~`]~a]~`]~a]~`]~`]~_\~_\~_\~_]~_\~_\~_\~_\~_\~^[~^[~^[~^[~]Z~]Z~]Z~]Z~]Z~]Z~]Z~\Y~\Y~\Y~\Y~\Y~\Y~\Y~[X~[X~[X~[Y~[Y~[X~[X~[X~[X~[X~ZW~YW~YW~YV~YV~YV~YV~YV~XU~XU~WU~WU~WU~WU~VT~VT~VT~VT~VT~VT~VS~VS~VS~UR~UR~UR~UR~UR~TR~TR~TR~TQ~TQ~TQ~TQ~SQ~SQ~SP~SP~RP~RP~RO~RO~QO~RO~QO~QN~PN~QO~QN~QN~PN~PN~PM~PM~PM~PM~OM~OM~OM~OL~NL~NL~OL~NL~NL~NL~NK~NK~MJ~MJ~MJ~MJ~MJ~LJ~LJ~LJ~LJ~LJ~KI~LJ~KI~KI~KI~KI~JH~KI~JH~JH~JH~JH~JH~JG~IG~IG~IG~IG~IF~IF~IF~HF~HF~IF~IF~IF~IF~HF~HF~HE~HE~HE~HE~HE~HE~HE~GE~GD~GD~GD~GD~GD~FD~FD~FD~FD~FD~FD~EC~EC~FC~FD~FC~FC~EC~EC~EC~EC~EC~EC~EB~EB~EB~EB~EB~EB~EB~DB~DB~DA~DB~EB~DB~DA~EB~DA~DA~DA~DA~DA~C@~DA~DA~DA~DA~DA~DA~DA~CA~DA~DA~C@~C@~C@~DA~DA~C@~CA~C@~CA~C@~C@~B@~B@~CA~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~B@~B@~B@~B@~B@~B@~B@~B?~C@~B@~B@~B@~B@~B?~B?~C@~B@~B@~B@~B@~B@~C@~B?~C@~B@~C@~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~C@~C@~C@~C@~C@~C@~C@~C@~CA~C@~C@~C@~C@~C@~C@~C@~CA~CA~CA~CA~CA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~CA~DA~DA~DA~DA~DA~DA~EB~DA~EB~EA~DA~EA~EB~EA~EB~DA~EB~EB~DB~DB~EB~EB~EB~EB~EC~EB~EC~EB~EB~EB~EB~EB~EC~EB~EB~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GC~FC~GC~GC~FC~GC~FC~FC~GC~GC~GD~GC~GC~GD~GD~GD~GD~GD~GD~GD~GD~FD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~GD~GD~GD~GD~GD~GD~HE~GD~HE~HD~HD~HE~GD~GD~GD~GD~HE~HE~GD~GD~HE~HE~GD~GD~GD~GD~GD~GD~HE~HE~GD~GD~HE~GD~HE~GD~GD~GD~HE~GD~GD~HE~GD~HE~GD~HE~HE~GD~GE~HD~HE~HE~HE~GD~GD~GD~GD~GD~GD~HE~HD~GD~GD~GE~HE~GE~GE~GD~GE~GE~GD~GD~GD~GD~GD~GD~GD~HE~GD~GD~GD~GD~GD~GD~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~FC~FC~GD~GD~GD~GD~GD~FC~GD~FC~FD~FC~FC~GD~FC~FC~FC~FD~FD~EC~FC~FC~FC~FC~EB~FC~FC~EC~FB~FC~FC~FC~FB~EB~FB~FB~EB~EB~EB~EB~EC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~DA~DA~DA~EB~EB~DA~DA~DB~DA~DA~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~B@~B@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~C@~B@~B@~C@~C@~C@~B?~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~CA~C@~C@~C@~C@~C@~C@~CA~CA~CA~DA~DA~DA~DA~DA~DA~CA~CA~DB~CA~DA~DB~DB~DB~DB~DB~DB~EB~EB~EB~DB~DB~EB~EC~EC~EC~EB~FC~EB~FC~FC~FC~FC~FC~GD~FD~GD~GD~FD~GD~FD~FD~GD~GD~GE~GD~GE~GE~GE~GE~GE~GE~GE~GE~HF~HF~HF~IF~HE~IF~IF~IF~IF~IG~IG~IG~IG~IG~JH~JH~JH~JH~JH~JH~JH~JH~KH~KH~KH~LI~LI~LI~LI~LJ~LJ~LJ~LJ~LJ~LJ~MK~MK~MK~MK~NK~MK~NK~MJ~NK~NK~OL~OL~OL~OL~OL~PM~OL~OM~PM~PN~PN~PN~QN~PN~QO~QO~QO~QO~QO~QO~RP~RP~RP~SP~SP~SP~SQ~TQ~TQ~TR~TQ~TR~TR~UR~UR~US~UR~US~VS~VS~VS~VS~VT~VT~WT~WT~WU~WU~XU~XU~YV~YV~ZW~YW~YW~YW~ZW~ZW~ZW~[X~[X~[X~[X~[Y~\Y~\Y~\Y~\Y~\Y~]Z~\Y~]Z~]Z~]Z~]Z~]Z~]Z~^[~^[~^[~^[~^[~^[~_\~_\~_\~_\~_\~_\~`]~`]~_\~`]~`]~`]~`^~a^~`^~a_~b_~b_~b_~b_~c`~da~da~db~da~eb~eb~eb~db~eb~ec~eb~fd~fc~fd~fd~fd~fd~fd~gd~gd~fc~fc~fc~fc~fc~gd~gd~gd~gd~gd~gd~ge~ge~ge~gd~ge~he~he~he~he~if~he~if~if~if~if~jg~if~jg~if~jg~jg~if~ig~if~jg~if~if~jg~ig~jg~jg~jg~jg~if~if~jg~ig~jg~ig~ig~ig~ig~if~jg~jg~jg~jg~if~jg~if~ig~if~hf~if~if~hf~if~if~if~hf~ig~hf~he~ie~if~hf~ge~he~ge~ge~he~ge~he~he~gd~gd~gc~gd~fc~fc~gd~fc~fc~fc~eb~fc~eb~eb~eb~eb~da~da~d`~da~da~c`~c`~c`~c_~c`~b_~c`~c`~b_~a_~a_~a^~b_~b_~a^~a_~a_~a^~`]~`]~`]~`]~`]~`]~_\~`\~_\~_\~_\~^[~^[~^[~^[~^[~^[~]Z~]Z~]Z~]Z~]Z~\Y~]Z~\Y~\Y~\Y~\Y~[X~[X~[X~[X~[X~[X~\Y~[X~[X~ZW~ZW~YW~YW~YW~YW~YV~YV~XV~XV~XV~XU~XU~XU~VS~VT~VT~VT~VT~VT~VT~VT~VT~VT~UR~US~UR~UR~UR~UR~TR~TR~TQ~TR~TQ~TQ~SP~SQ~SP~SP~SP~RO~SP~RO~RO~RO~QO~RO~QO~QO~QN~QN~QN~PN~PM~PM~PM~OM~OM~OM~PM~OM~OM~NL~NL~NL~NL~NK~MK~MK~MK~NK~MK~MJ~MJ~MJ~MJ~LJ~LJ~KI~KI~KI~KI~KI~KI~JH~KI~JH~JH~JH~JH~JH~JH~JG~JG~IG~IG~IG~IG~IG~IG~HF~IF~HF~IE~IF~HF~HF~HF~HF~HF~HE~HE~HE~HE~HE~GE~GE~GD~GD~GD~GD~GD~GD~GD~GD~FC~GD~FD~FC~FC~FC~EC~EC~EC~EC~EC~EC~EC~EC~EB~EB~FC~EB~EB~EB~EB~EB~EB~DB~EB~DB~EB~DA~DB~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~C@~DA~CA~CA~CA~CA~C@~C@~C@~CA~C@~CA~C@~C@~C@~C@~C@~B@~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~C@~C@~B@~B@~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~C@~B@~C@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~C@~CA~CA~CA~CA~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~CA~CA~CA~CA~DA~DA~DA~DA~DA~DA~C@~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~DA~EB~EA~EB~EB~EB~EA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EC~EC~EC~EB~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~FC~FC~FC~FC~FC~FC~GC~GC~GC~GC~GD~FC~GC~GD~GC~GC~GD~GD~GD~GD~GD~GD~FC~FD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GE~GD~GE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HE~HE~GD~HE~GD~HE~GD~HE~GD~HE~HE~HE~GD~HE~GD~GD~HE~HE~GD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~GD~HE~HE~GD~HD~GD~GD~HE~HE~GD~GD~GD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~GD~HE~GD~HE~GD~HE~HE~HE~GD~GD~GD~GD~GE~GD~HE~GD~GD~GE~GD~GD~GD~GE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~FC~GD~GD~GE~GD~GD~FC~GD~FD~GD~FC~FC~FD~FC~FC~FC~FD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FB~FB~FC~FB~FB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~DA~EB~DB~DA~DB~DB~DB~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~CA~DA~C@~DA~DA~DA~C@~C@~DA~C@~C@~C@~C@~DA~DA~DA~C@~C@~CA~CA~CA~CA~C@~C@~DA~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~B@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~C@~CA~C@~CA~C@~CA~CA~CA~CA~DA~DA~DA~DA~DA~DA~DA~DA~CA~DB~DA~DA~EB~DB~EB~DB~DB~EC~EB~EB~EB~EB~EC~EB~EB~EC~EC~FC~EC~FC~FC~FC~FC~FC~FC~FD~FD~GD~GD~FD~FD~GD~GD~GD~GE~GE~HE~GE~HE~GD~HE~HE~HE~HE~HE~HF~HF~IF~IF~IF~IF~IF~IG~IG~IG~IG~JG~IG~JH~JH~JH~JH~KI~JH~KI~KH~KH~LI~KH~LI~LI~LJ~LJ~LJ~LJ~LJ~LJ~MJ~MJ~MJ~MJ~MK~MK~NL~NK~NL~NL~NL~NL~NL~NL~OL~OM~OM~OM~OM~OM~PM~QN~QN~QN~QN~QN~QO~QO~QO~QO~RO~QO~RP~RP~SP~SP~SP~SP~TQ~TQ~TQ~TR~TQ~SQ~TR~UR~UR~UR~VS~US~VS~VS~VT~VT~VT~WT~VT~WT~WU~XU~XU~XU~XU~YV~YV~ZW~ZW~ZX~ZX~ZW~[X~[X~\Y~\Y~[X~\Y~\Z~\Z~][~\Z~\Z~\Z~\Z~]Z~]Z~]Z~]Z~]Z~]Z~^[~^[~^[~_\~^[~_\~_\~_\~_\~_\~_\~`]~`]~`]~`]~`^~`^~`^~a^~a^~a^~a_~b_~b`~c`~ca~c`~da~da~da~da~eb~ec~fc~fb~fc~eb~gd~gd~fc~gd~gd~gd~gd~ge~ge~he~he~ge~ge~gd~gd~gd~ge~gd~hd~he~ge~ge~he~he~if~he~he~if~if~ig~ig~if~ig~if~jg~jg~jg~jg~ig~jg~jh~jg~jg~jg~jg~jg~jg~kh~jg~jh~jh~jg~jh~jg~jg~jg~jh~jh~kh~jh~jh~jh~jg~jh~jg~jh~kh~jg~jh~jh~jg~jg~jg~jg~jg~jg~jg~jh~jh~ig~if~jg~jg~if~if~ig~if~jf~if~if~hf~if~if~if~if~if~hf~he~hf~he~hf~ge~gd~gd~gd~fc~fc~fc~fc~eb~fb~eb~eb~eb~eb~da~eb~da~da~da~c`~c`~c`~c`~c`~b_~b`~b_~c`~b^~b_~b_~b_~a^~a^~a^~a^~`^~`^~`]~`]~`]~`\~`]~`]~_]~_]~^\~_\~_\~^[~^[~^[~^[~^[~][~][~]Z~]Z~]Z~]Z~]Z~\Z~\Y~\Y~\Y~[X~[X~[X~[X~[X~[X~ZW~ZW~ZW~ZW~ZW~YV~YV~ZW~YW~XV~YV~XV~XU~XU~XU~XU~XU~XU~WT~VT~VT~VT~VT~VT~VT~US~US~UR~UR~UR~UR~TQ~TR~TR~TQ~TQ~TQ~SQ~SQ~SQ~RP~RP~RP~SP~RO~RO~QO~QO~RO~QN~QN~QN~QM~QN~PN~PN~PN~PM~PN~OM~PM~PM~PM~OM~OM~NL~NL~NL~MK~NL~MK~NK~MK~MJ~MJ~MK~MK~LJ~LJ~LJ~LJ~KI~KI~KI~KI~KI~KI~JH~KI~KH~KH~KH~KH~JH~JH~IG~IG~IG~IG~IG~IG~IF~IG~HF~IG~IF~HF~HF~HF~HF~HF~HF~HE~HE~HE~HE~GE~GE~GE~GD~HE~GD~HE~GD~GD~GD~GD~FD~FD~FD~FD~FD~FC~FD~EC~FC~EC~FC~FC~FC~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DB~EB~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DB~DA~DA~DA~DA~CA~DA~DA~C@~DA~DA~CA~CA~DA~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~DA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~C@~C@~C@~C@~DA~C@~DA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~C@~DA~C@~DA~DA~CA~C@~C@~C@~C@~DA~C@~DA~CA~CA~CA~CA~DA~DA~DA~DA~DA~DA~DA~EB~EB~DA~DA~DA~DA~DA~DA~DA~EB~DA~DA~EA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~FC~FC~FC~FC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~FC~GD~FC~GC~GC~FC~FC~GD~GD~GC~FD~GE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GE~GE~HE~GD~GD~HE~HE~GD~GD~HE~GD~GD~GD~GD~HE~GD~GD~GD~HE~GD~GD~HE~HE~GD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~GD~HE~HE~HE~HE~HE~IE~IE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HD~HE~HE~IF~HE~HE~HE~HE~HE~HE~HE~GD~HE~HE~HE~HE~HE~HE~HE~GD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~GE~GE~GD~GD~GD~GD~HE~GD~GD~HE~HE~GD~GD~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~FC~FC~GD~GD~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~FC~FC~FC~FB~EB~FC~FC~FB~FB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~DA~DA~DA~EB~EB~DB~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~CA~CA~DA~DA~DA~CA~DA~CA~DA~C@~C@~C@~C@~CA~C@~CA~C@~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~B@~B@~C@~C@~C@~C@~C@~CA~C@~C@~C@~CA~DA~CA~CA~CA~C@~C@~CA~CA~CA~CA~CA~CA~DA~DA~C@~C@~DA~DA~DA~DA~DB~DB~DA~DA~DB~DB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EC~FC~FC~FC~FC~FC~EC~FC~FC~FD~FD~EC~FD~FD~FD~FD~FD~GD~GD~GD~GE~GE~FD~GE~GE~GE~GE~GE~HE~HE~HF~HE~HE~HE~IF~HF~IF~IG~IG~IF~IF~IG~IG~JG~JH~JH~JH~JH~JH~JH~JH~JH~JH~KH~KH~KH~LI~LI~LI~LJ~LI~LI~LJ~LI~MJ~LJ~LJ~MJ~MK~NK~NK~NK~MK~NK~NL~NL~NL~OL~OL~OM~OM~OM~OM~OM~OM~PN~PN~PN~PN~QO~PN~QO~QO~QO~RO~QO~RO~RO~SP~RO~SP~SP~SP~SP~SQ~TQ~TR~TR~UR~TR~UR~VS~UR~UR~UR~VS~VS~VT~VT~VT~WT~WT~WT~XU~XU~XV~YV~YV~YV~YV~YV~YV~ZW~YW~ZX~ZX~ZX~[X~\Y~[Y~\Y~\Y~\Y~\Y~\Z~]Z~]Z~]Z~]Z~]Z~]Z~^[~^[~^[~^[~^[~^[~^\~^[~_\~_\~_\~_\~_\~`]~`]~a^~`^~`]~a^~a^~a^~b_~a^~a^~a^~b_~b_~c`~c`~c`~da~eb~fb~eb~eb~fc~fd~fd~fd~fd~fc~gd~gd~ge~gd~gd~he~he~ge~hf~hf~hf~he~he~hf~ge~ge~ge~he~if~he~he~he~if~if~ig~ig~ig~if~jg~jg~jg~jg~jg~kh~kh~kh~kh~ki~jh~jh~kh~kh~jh~kh~li~kh~li~li~kh~kh~ki~kh~kh~li~kh~ki~li~kh~ki~ki~ki~ki~ki~ki~ki~ki~kh~kh~kh~jh~kh~kh~kh~jh~jh~kh~kh~kg~jg~kh~jg~jg~jh~jg~jh~jh~jh~jh~jh~jg~jg~jg~jg~jg~ig~ig~ig~ig~ig~ig~ig~he~ie~he~he~ge~gd~gd~gd~gd~gd~gd~gd~gd~fc~fc~fc~eb~ea~ea~ea~da~db~da~db~da~da~c`~c`~c`~c_~c_~b_~c_~b_~b^~a^~b^~a^~a^~a]~a^~`]~a^~`]~`]~_]~_]~`]~`]~_\~_\~_\~^[~^[~^\~^[~^[~]Z~]Z~^[~^[~]Z~]Z~]Z~]Z~]Z~\Y~\Y~\Y~[X~[X~[X~[X~[X~ZW~ZW~ZW~ZW~ZW~ZW~YW~YW~YV~XV~YW~XV~XU~YV~XU~XU~WT~WU~WU~WU~VT~VT~VT~VS~VS~VS~VR~VR~US~UR~TR~UR~TR~TR~TQ~SQ~TQ~SQ~SP~SP~SP~SP~RP~RP~RP~RP~RO~RO~RO~QO~QO~QN~QN~QN~QO~PN~PN~PN~OM~PN~OM~OM~OL~OL~OL~OL~NL~OL~NK~NL~NK~NK~MK~MK~MK~MK~LJ~LJ~LJ~LJ~LJ~LJ~KI~KI~KI~KI~KI~KI~KI~KH~JH~JH~JH~JH~JH~JG~JG~JG~JG~IG~IG~IG~IG~HF~HF~HF~IF~HF~HF~GE~HF~GE~HF~HE~HE~HF~HE~HE~GD~HE~GD~HE~GD~GD~GD~GD~GD~GD~GD~GD~FC~FC~FC~EC~FC~FC~FC~EC~FC~EC~EC~FC~EB~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~DB~DB~DB~EB~DA~DB~DA~DA~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~DA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~C@~C@~C@~C@~CA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~CA~CA~C@~DA~C@~DA~DA~C@~C@~C@~C@~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EA~DA~EB~DB~EB~EB~EB~EB~DA~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~EB~FC~FC~EB~EB~FC~EB~FC~EB~FC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~FC~GC~GC~FC~GC~FC~GD~GD~GC~GD~GD~GD~GD~GD~FD~FD~GD~GD~FD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HE~GD~GD~GD~HE~GD~HE~HE~GD~HE~HE~HD~HD~HE~HD~HD~HE~HE~HD~HE~HE~HE~HE~HE~IF~HF~HE~IE~IE~IE~HD~HE~HE~HE~IE~IE~IE~HE~HE~IE~HE~HE~HE~IE~HE~HE~HE~HE~HE~HE~HE~HE~HF~HE~IF~HE~HE~HE~HF~HE~HE~HE~IE~IE~IE~HE~IE~IF~HE~IE~IE~IE~HE~IE~HE~HE~IF~HE~HE~HE~HE~IE~HE~IE~HD~HE~HD~HE~HE~HE~HE~HE~HE~GD~HE~HE~HE~HE~HE~HE~HE~HE~GD~HE~GD~HE~GD~HE~HE~HE~HE~HE~GD~HE~HD~HE~HD~HD~HD~GD~GD~GE~GE~HE~GD~GD~GD~GD~GD~GD~FD~FC~GD~GD~FC~GD~FC~FC~FC~GD~FC~FC~FC~FC~FC~GD~FC~FC~FC~FC~FC~FC~FC~FC~EB~EB~EC~EC~FC~EB~FC~FC~EB~FB~EB~FB~EC~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~EB~EB~EB~EB~DA~EB~EB~DA~EB~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~DA~DA~DA~DA~C@~DA~DA~C@~C@~DA~C@~DA~C@~C@~DA~C@~CA~CA~C@~CA~C@~C@~C@~CA~C@~C@~C@~C@~CA~CA~CA~C@~CA~C@~C@~C@~C@~CA~CA~C@~C@~C@~CA~C@~C@~C@~CA~C@~C@~C@~C@~C@~C@~C@~C@~CA~CA~C@~C@~C@~C@~C@~C@~C@~CA~DA~C@~CA~CA~CA~C@~CA~CA~CA~DA~CA~CA~CA~DA~DA~DA~DA~DA~DB~DA~EB~DA~DA~EB~EB~EB~EB~EB~EB~EB~EB~FC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FD~EC~FD~FC~FD~FD~FD~FD~GD~FD~GD~GD~GD~GE~GD~HE~HE~GE~HE~HE~HE~HE~HF~HF~HF~HF~HF~IF~HF~IF~IG~IG~IG~IG~IG~JG~JH~JH~JH~JH~JH~JH~JH~KI~KI~KI~KI~KI~LI~LI~LI~LJ~LI~LJ~LJ~LJ~LJ~LJ~MJ~MK~NK~NK~NK~NK~NK~NL~NK~NL~NL~NK~NL~OM~OM~OM~OM~PN~PN~PN~PM~PN~PN~QN~QN~QO~QO~QO~QO~QO~RO~SP~RO~SP~SP~SP~SQ~SQ~TQ~SQ~TQ~TQ~TR~UR~UR~UR~UR~US~VS~US~VT~VS~VT~WT~WU~WU~WU~WU~XU~XU~XU~YV~YV~YV~ZW~ZW~YW~ZW~[X~[X~[Y~[Y~[Y~\Y~\Y~\Z~\Z~]Z~\Z~]Z~]Z~]Z~]Z~]Z~^[~^[~^[~^\~^\~^\~_\~_\~^[~_\~`]~_\~`]~`]~`]~`]~a]~a]~a^~a^~a^~b_~b_~b_~b_~b_~b_~b_~c`~d`~d`~da~eb~ec~ec~eb~fc~fc~gd~gd~gd~gd~gd~he~he~he~he~he~he~he~hf~hf~hf~ig~hf~if~ig~if~if~if~if~jg~jg~if~jg~ig~jh~jh~jh~jh~kh~jh~jh~kh~kh~ki~ki~kh~jh~li~lh~kh~li~li~li~li~li~li~li~li~li~li~mj~li~lj~lj~li~li~li~mj~li~mj~mj~mj~mj~mj~mj~mj~li~li~li~lj~lj~li~li~lj~ki~ki~li~ki~li~ki~kh~li~ki~kh~kh~jh~jh~jh~jh~jh~ig~jh~jh~jh~kh~jg~jg~jg~jg~jg~jg~ig~if~if~hf~he~he~if~he~he~gd~gd~gd~gd~gd~gd~fd~fc~fc~fc~ec~eb~eb~eb~eb~eb~da~da~da~da~da~c`~c`~c`~c`~c`~c`~b_~b_~b_~a_~a_~a^~a^~a^~`]~a^~`]~`]~a^~`]~`]~`]~`]~_\~_\~`]~_\~_[~^[~^[~_\~^[~^[~]Z~^[~]Z~^[~]Z~\Y~]Z~\Y~\Y~\Y~[X~[X~[X~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~YW~YW~YV~YV~XV~XU~XU~XU~XU~XU~WU~WT~WT~WT~VT~VT~VT~VT~VS~VS~UR~US~US~UR~UR~TR~TQ~TQ~TQ~SQ~TQ~SP~SQ~SP~RP~RP~RP~SP~RO~RP~RO~RO~RO~QN~QO~QN~QO~PN~PN~PM~PM~OM~PM~PM~OM~OL~OM~OL~OL~NK~NK~MK~MK~MK~NK~MK~MK~MK~MK~LJ~LJ~LJ~LJ~LJ~LI~LJ~KI~KI~KI~KI~JH~KI~JH~JH~JH~JH~JG~JH~JG~JH~JG~JG~IG~IF~JG~IF~IF~IF~HF~IF~HF~HF~HF~GE~HF~GE~GE~GE~HE~HE~GD~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~FC~FC~FC~FC~FC~FC~FD~EC~FC~FC~EC~FC~EB~EB~EC~EB~EB~EB~EB~DA~EB~EB~EB~EB~DB~EB~DB~DB~EB~DA~DA~DA~DA~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~CA~DA~CA~DA~DA~CA~DA~DA~C@~CA~DA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~DA~DA~DA~C@~C@~C@~C@~C@~C@~C@~C@~C@~C@~DA~C@~C@~C@~C@~C@~C@~C@~CA~CA~CA~CA~DA~DA~DA~DA~DA~DA~DA~DA~CA~CA~CA~CA~DA~CA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~EB~EB~EB~EA~EB~EB~EB~EB~DB~EB~EB~EA~EB~EB~EB~EB~EB~FC~EB~EC~EB~EC~EB~EB~EC~EC~EC~EC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GC~GC~GD~GC~FC~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HE~GD~HE~HE~GD~HE~GD~GD~HE~GD~GD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IE~IE~HE~HE~HE~HE~HE~HE~HE~HE~IE~HE~HE~IF~HE~IE~IE~IE~HE~HE~HE~HE~HE~HE~HE~IE~IF~IF~HE~HE~IF~IF~IE~IF~HE~IF~HE~IF~HE~IF~IF~HE~HE~IF~IF~IF~IF~IE~IF~HE~IF~HE~HE~HE~IF~IE~IF~IF~HE~IE~IF~IF~IE~IF~IE~IE~IE~IE~IF~HF~IF~HF~HE~HE~HE~IF~HE~HE~HE~HE~HE~HE~IF~HE~HE~HE~HE~HE~HE~HE~GD~HE~HE~HE~HE~HE~GD~HE~HE~GD~GD~GD~HE~GD~GD~GD~HE~GD~GD~GD~GD~GD~GD~GD~GE~GD~GD~GD~GD~GD~GD~GD~GD~GD~FC~GD~FC~GD~GD~FC~FC~FC~FC~FC~FC~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EC~EC~FC~FB~FB~EB~FB~FB~FB~EB~EB~FB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~EB~EB~DB~DA~DA~DB~DB~DB~DB~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~DA~C@~DA~DA~DA~DA~C@~C@~CA~DA~CA~DA~C@~C@~DA~C@~DA~C@~C@~DA~C@~C@~C@~C@~DA~DA~C@~C@~CA~C@~CA~C@~C@~C@~C@~C@~C@~C@~CA~C@~CA~C@~C@~CA~DA~C@~CA~CA~CA~CA~CA~CA~C@~CA~CA~CA~DA~DA~C@~C@~CA~CA~CA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~EB~EB~DB~EB~EB~EB~EB~EB~EB~EB~EC~EB~EB~EB~EB~FC~FC~EC~EC~EC~FC~FC~FD~FD~FD~FD~FD~FD~FD~FD~GD~GD~GD~GD~GE~GD~GE~HE~HE~HE~HE~HE~HE~HF~HE~HF~HF~HF~HF~HF~HF~IG~IG~IG~IG~IG~IG~IG~IG~IG~JH~JH~JH~JH~KH~KH~KI~KI~KI~KI~KI~KI~LI~KI~LJ~LJ~LJ~MJ~MJ~MJ~MJ~MJ~NK~MK~MK~MK~NL~NL~NL~NL~NL~NL~OM~OL~OM~OM~OM~PN~OM~PM~PN~QN~QN~QN~QN~QN~QN~QO~QO~RP~RP~SP~RO~SP~SP~SP~SP~SQ~SQ~SQ~TR~TR~TR~TQ~UR~TQ~US~UR~US~VS~VT~VT~WT~WT~WT~WU~WT~WT~XU~XU~XU~XU~YV~YV~YW~YW~ZX~YW~ZX~ZX~ZX~[X~[X~[Y~[Y~[Y~\Z~]Z~]Z~]Z~]Z~]Z~]Z~^[~^[~^[~^[~_\~^\~_\~`]~_]~_]~_]~_]~`]~_]~`^~`^~`^~a^~a^~a^~b_~b_~b_~b_~b_~b_~c`~c`~c`~c`~c`~da~da~da~da~eb~fc~gd~gd~gd~ge~ge~he~hf~hf~hf~hf~hf~hf~if~ig~jg~if~if~jg~jg~if~if~jg~jg~jg~if~if~ig~jh~jg~jg~jg~kh~kh~jh~kh~ki~li~li~li~li~li~li~li~li~li~mj~li~lj~li~mj~li~mj~mj~mj~mj~nk~mj~mk~mj~nk~mj~nk~nk~mj~nk~nk~nk~nk~nk~mk~nk~nk~nk~nk~nk~nk~nk~nk~mj~mj~li~mj~mj~mj~mj~mj~lj~lj~mj~li~li~li~li~li~li~li~ki~ki~kh~kh~kh~kh~kh~kh~kh~jh~jh~jg~jg~jg~jg~jg~if~if~if~if~if~if~he~he~he~he~ge~he~gd~gd~gd~fd~fd~fc~fc~fc~ec~ec~db~ec~db~db~da~eb~da~c`~da~da~c`~c`~c`~c`~b_~b_~b_~b_~b_~a^~a^~a_~a^~a^~a^~a^~`]~`]~`]~`]~_\~_\~_\~^\~_\~^[~^[~^[~^[~^[~^[~]Z~]Z~]Z~]Z~\Y~]Z~\Y~\Y~\Y~\Y~[X~[X~ZW~[X~ZW~[X~ZW~ZW~ZW~ZW~ZW~YV~YW~YV~YV~XU~XV~XU~WT~WT~WT~WT~WT~WT~VT~VT~VT~VS~VS~US~US~US~US~UR~UR~UR~TQ~TQ~TR~SQ~SQ~SQ~SQ~SQ~SP~SP~SP~RP~RP~RO~RO~RO~RO~QN~QO~QN~QN~PN~PN~PN~PM~PM~PM~OM~OL~OL~OL~OL~NL~OL~NK~NK~NK~MK~NK~MK~MK~MJ~MJ~LJ~LJ~LJ~LJ~LI~LI~KI~LI~LI~KH~KH~KH~KH~KH~JH~JH~JH~JH~JH~JG~JG~JG~JG~JG~JG~JG~IF~IF~IF~IF~IF~IF~HE~HF~HF~HF~HE~HE~HE~HE~HE~HE~HE~GE~GD~GD~GD~GD~GD~GD~GD~GD~FC~GD~FC~FD~FD~FC~FC~FC~FC~FC~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~DB~EB~DB~DB~DB~DA~DB~DB~EB~EB~EB~DA~DA~DB~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~CA~CA~CA~CA~CA~CA~C@~CA~CA~CA~CA~DA~DA~C@~C@~C@~C@~C@~C@~C@~CA~CA~C@~C@~C@~C@~C@~C@~C@~C@~CA~CA~CA~C@~CA~CA~C@~DA~C@~DA~C@~C@~C@~DA~DA~CA~CA~DA~DA~DA~C@~C@~C@~C@~DA~DA~DA~DA~CA~CA~CA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~EB~EB~DA~EB~DA~DA~EB~EB~EB~EB~EB~EB~EB~EB~EC~EB~DB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~FC~EC~EB~EC~EC~EC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GC~GC~GD~FC~FC~GD~GD~GD~FC~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HE~GD~HE~GD~GD~GD~HE~HE~GD~GD~HE~HE~HE~HE~HE~HE~HE~HD~HD~HE~HE~HE~HE~HE~HE~IE~HE~IE~IE~IE~IE~IE~IE~IE~HE~IE~IE~IE~IE~IE~IF~IE~IE~IE~IF~IF~IE~IE~IF~IF~IF~IF~IF~HE~IF~IF~HE~IF~IF~IF~IF~HE~HE~HE~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IE~IF~IF~IF~IE~IF~IF~IF~IF~HF~HF~IF~IF~IF~IE~IF~HE~HE~HE~HF~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HD~HE~HE~HE~HE~HE~HE~GE~GE~HE~HE~GD~GD~HE~HE~HE~GE~GD~GD~GE~GE~GE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~FC~GD~GD~FC~FC~FC~GD~FC~FC~FC~FC~FC~FC~FC~EC~FC~FC~FC~EB~FC~FC~FC~FC~FC~FC~FB~FB~FB~FB~FB~EB~FB~EB~FC~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~DA~EB~EB~DA~DA~DA~EB~DB~DB~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~CA~CA~CA~DA~DA~C@~DA~DA~C@~C@~C@~CA~CA~CA~CA~DA~CA~CA~CA~CA~DA~C@~CA~CA~C@~DA~C@~C@~C@~C@~C@~C@~DA~CA~DA~CA~CA~DA~CA~CA~CA~CA~CA~CA~CA~CA~CA~CA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~EB~EB~DB~EB~EB~EB~EB~EB~EB~EC~EB~EC~EB~EB~EB~FC~FC~FC~FC~FC~FC~FD~FC~FC~FC~FD~GD~GD~GE~GE~GE~GD~GE~GE~GE~GE~HE~HE~GE~HE~HF~HF~IF~IF~IF~IF~IF~HF~HF~HF~IF~IG~IF~IG~IG~IG~IG~IG~JH~JH~JH~JH~KH~KH~KH~KH~KH~KH~KI~KI~KI~LI~LJ~LJ~LJ~LJ~MJ~MJ~MJ~MJ~MK~NK~NL~NK~NK~NL~NL~NL~NL~NL~NL~NL~OL~OM~PM~OM~PN~QN~PM~PM~QN~QN~QN~QO~QO~QO~QO~QO~RP~RO~RP~SP~SP~SP~SP~SQ~SQ~TR~TR~TR~TR~UR~UR~UR~UR~UR~VT~VT~VT~VT~VT~VT~WU~WT~WU~XU~XU~XU~XV~YV~XU~YV~YW~YW~ZW~ZX~ZX~ZX~[X~[X~[X~[X~[X~\Y~\Y~]Z~\Y~\Y~]Z~]Z~]Z~^[~^[~^[~^[~^\~_\~_]~`]~`\~`]~`]~`]~`]~a_~a^~a_~a_~a_~b_~b_~b_~a^~b_~b_~c_~c_~c_~c`~c`~c`~da~da~da~ea~eb~da~eb~fc~fc~gd~gd~he~he~he~if~if~if~if~jg~jg~jh~jh~jg~jg~jg~kh~jg~kh~kh~kh~jh~jf~jg~kh~kh~kh~kh~kh~ki~ki~li~li~kh~li~li~lj~li~mj~mj~mj~nk~nk~mk~mk~mk~lj~nk~mk~mk~nk~nk~nk~nk~nl~ok~nk~nl~ol~nk~pl~nk~ol~ol~ok~ol~ol~ol~ol~nl~ol~ol~ok~ok~ol~ok~nk~nk~nl~nk~nk~nk~nk~nk~nk~mk~nk~mk~nk~mj~mj~mk~lj~lj~lj~mj~li~mj~lj~li~kh~li~kh~ki~kh~li~jh~jh~jh~jg~kg~kh~jg~jg~jg~jg~if~if~ig~if~hf~hf~hf~hf~hf~ge~hf~he~gd~gd~fc~gd~fc~fd~fd~fc~eb~eb~eb~eb~da~da~db~db~eb~da~da~c`~da~da~c`~c`~c`~b_~b`~b_~b_~b_~a^~a^~b^~a^~a^~`]~`]~`]~`]~_\~_\~_\~_\~_[~_[~_[~^[~^[~]Z~]Z~]Z~]Z~^[~]Z~]Z~\Y~\Y~\Y~\Y~[X~[X~[X~[X~[X~ZW~ZW~[X~[X~ZW~ZW~YV~YV~YW~XV~XV~XV~XU~XU~WU~WT~WT~WT~WT~VS~VT~VT~VT~VT~US~US~US~US~TR~TR~TQ~TR~TQ~TQ~SP~TQ~TQ~SQ~TQ~SP~SP~RP~RP~RP~RO~RP~RO~RO~RO~QN~QN~QN~PN~PN~PM~PN~OM~OM~OM~OL~OL~OL~OL~OL~OL~NL~NK~NK~NK~MK~MJ~MK~MJ~MJ~MJ~LJ~LJ~LI~LI~LJ~LI~LI~KI~LI~KI~KI~KH~JH~JH~KH~JH~JH~JH~JG~IG~JG~JG~IG~IG~IG~IG~IF~IF~IF~IF~IF~IF~HE~HF~HE~GE~GE~GE~GE~GE~GE~GD~GD~GD~GD~GD~GD~GD~GD~GD~FC~FC~GD~FC~FC~FC~FC~FC~FC~FC~FC~EB~FC~FC~EB~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~DA~DA~DA~DA~DA~EB~EB~DA~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~CA~DA~DA~DA~DA~DA~C@~DA~DA~CA~C@~C@~DA~C@~DA~C@~C@~C@~C@~CA~CA~C@~CA~CA~CA~CA~DA~DA~C@~C@~DA~DA~DA~C@~DA~DA~DA~DA~DA~C@~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~DA~DA~DA~DA~EB~DA~DA~EB~EB~EB~EB~EB~EB~EA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EC~EB~EB~EB~EB~EB~FC~FC~FC~FC~FC~FC~EB~GC~FC~FC~FC~FC~FC~GC~GD~GD~GD~GD~FD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HE~HE~GD~GD~GD~HE~HE~GD~HE~GD~HD~HD~HD~HD~HD~HE~HD~HE~HE~IE~IE~IE~HE~HE~IE~IE~IE~IE~HE~HE~HE~HE~HE~HE~HE~IE~IE~HE~IE~IE~IE~HE~HE~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~JF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~JG~IF~IF~JF~IF~JF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IE~IF~IF~IF~IE~IE~IE~IF~HE~IE~IE~HE~IF~HE~HE~IE~HE~HE~HE~HE~IE~HE~HE~HE~HE~HE~HE~HE~HE~GD~GD~GD~HE~HE~HE~HE~GD~HE~HE~GD~HE~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GC~GD~GD~GD~GD~GD~GD~GD~GD~GD~FC~GD~GD~GD~GD~GD~FC~FC~FC~FC~FD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FB~FC~FB~EC~EB~EC~EC~EB~EB~EB~EB~EB~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~EB~EB~EB~EB~DA~DA~DA~DA~DA~DA~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~DA~DA~DA~CA~DA~DA~DA~DA~DA~DA~DA~CA~C@~DA~DA~CA~CA~DA~DA~DA~DA~DA~DA~C@~DA~DA~CA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~DA~EB~DA~DA~DA~EB~EB~DB~EB~EB~EB~EB~EB~EB~EC~EB~EB~EB~EB~FC~EB~EB~EC~EC~FC~FC~FC~FC~FC~FC~FC~FD~FD~FD~FD~GD~GD~GE~GE~GD~GE~HE~HE~HE~HE~HE~HE~HE~HE~HF~HF~HF~HF~IF~IF~IG~IG~IF~IG~IG~IG~IG~IG~IG~JH~JH~IG~JH~JG~KH~KH~KH~KI~KI~KI~KI~KI~LI~LI~LI~LI~LJ~LJ~LJ~LJ~MK~MK~MK~NK~NK~NL~MK~MK~NL~NL~NL~NL~OL~OM~OM~OL~PM~PM~PN~PN~PM~QN~QN~PN~QO~QN~QN~QO~QO~QO~RO~RO~SP~SP~SP~SP~SP~SQ~SQ~SQ~TQ~TR~TR~UR~TQ~UR~US~US~VS~VS~WT~VT~VT~WT~WT~WT~XU~XU~XU~XU~XU~XV~XV~YV~ZW~ZW~YW~ZW~ZX~[X~[X~[X~\Y~[X~\Y~\Y~\Y~\Y~\Y~]Z~]Z~^[~]Z~^[~^[~^\~_\~`]~_\~`]~`]~`]~`]~`]~`]~`]~a^~a^~a^~a^~a^~b_~a^~c_~b_~c`~c`~c`~c`~c`~c`~da~da~d`~d`~da~da~da~eb~fb~fc~fc~fc~gd~gd~he~jg~ig~ig~ig~ig~jh~jg~jg~jh~kh~kh~kh~kh~kh~li~kh~li~li~ki~kh~kh~kh~jg~kh~kh~li~li~li~li~li~mj~li~li~mj~mj~mj~mj~nk~mk~nk~nk~nl~nl~nk~nl~nl~om~ol~ol~ol~ol~ol~ol~ol~ol~ol~om~om~pm~pm~pm~qm~qm~pm~pl~pm~pm~pm~pm~pm~ol~pl~pl~ol~pn~om~ol~ol~ol~pl~ol~ol~ol~ok~nk~nk~nk~nk~mk~mj~mk~mj~mj~mj~mj~mj~li~lj~lj~li~lj~lj~lj~li~li~li~li~ki~ki~kh~kh~kh~jg~jg~jg~jg~jg~jg~jg~jg~ig~if~if~hf~if~if~he~ge~ge~ge~gd~gd~fc~fc~gd~fd~fc~fc~fc~fc~ec~eb~eb~eb~ea~ea~da~eb~da~da~eb~da~da~ca~c`~b_~b_~b_~b^~a_~a^~a^~a^~`]~`]~`]~_\~_\~_\~^[~_\~_\~^[~_[~^[~^[~_[~^[~^[~^[~]Z~\Y~]Z~]Z~\Y~\Y~\Y~\Y~[X~[X~[X~ZW~[X~[X~ZW~YV~YV~ZW~YV~YV~YV~XV~XV~XU~WT~XU~WU~WT~WT~WT~WT~WT~VT~VS~VS~VS~US~US~VS~US~UR~UR~TQ~UR~UR~TR~TQ~TQ~TQ~SP~SQ~SQ~SP~RO~RP~RO~RO~RO~RO~QO~QO~QO~QO~QN~PM~PM~PM~PN~OM~PM~OM~OM~OL~OL~OL~NL~NL~OL~NK~NL~MK~MK~MK~MK~MJ~MJ~MJ~LJ~LJ~MJ~MJ~LI~LI~LI~LI~LI~KI~KI~KI~KH~KH~KH~JH~JH~JG~JG~JG~IG~IG~JG~JG~IG~JG~IF~IF~IF~IF~HE~IF~HE~HE~HE~GE~HE~HF~GE~GE~GE~GE~GD~GD~GD~GD~GD~GD~GD~FD~FD~GD~GD~FC~FC~FC~GC~FB~FC~FC~FC~FC~FC~FC~FC~EB~EB~EB~EB~DB~EC~EB~EB~EB~EB~EB~EB~EB~DA~DA~DA~DB~EB~DB~DB~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~CA~DA~CA~DA~DA~C@~C@~DA~C@~C@~C@~DA~CA~DA~DA~C@~C@~DA~CA~CA~CA~CA~CA~CA~CA~CA~CA~DA~C@~DA~C@~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~DA~DA~EA~EB~EA~EB~EB~EB~EB~EB~EA~DA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~EB~FC~FC~FC~EC~EC~FC~EB~FC~EB~FC~GC~FC~FC~FB~FC~GC~FC~FC~FC~GC~FC~GD~FC~FD~FD~FC~GD~GD~FD~FD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HE~GD~GD~GD~HE~GD~GD~HD~GD~HD~HD~HE~HE~HE~HE~HD~HE~HE~HD~HD~HE~HE~HE~HE~HE~HE~IF~IE~IF~IE~IF~IF~IF~IE~IF~IE~IE~HE~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~JG~JF~JF~JF~JF~JG~JF~JF~JF~JF~JF~IF~JG~JG~JG~JG~JG~JG~JF~JG~JF~JG~JG~JF~JF~JG~JG~JF~JF~JF~JF~JG~IF~IF~JG~JG~JF~JF~JF~IF~JF~JF~JF~IF~IF~IF~IF~JG~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IE~IE~IF~IE~IF~HE~HE~IE~HE~IE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~GD~GD~GD~GD~GD~GD~GE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~FC~GD~GD~GD~GD~GD~FC~FC~FC~FC~FC~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~FC~EB~EB~EB~EB~FC~EB~EB~EB~EB~EB~EB~EB~DB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~EB~EB~EB~EB~DA~DA~DA~DA~DA~DA~DA~DB~DA~DA~DA~DA~DA~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~CA~CA~CA~DA~C@~C@~DA~DA~DA~C@~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~DA~EB~EB~DA~EB~EB~EB~EB~DB~DB~EC~EB~EB~EB~EB~EB~EB~EC~EB~EB~EC~EC~EB~EC~EC~FC~FC~FC~FC~FC~FD~FD~FD~GD~FC~GD~GD~FD~GE~GD~GD~HE~GE~HE~HE~HE~HE~HE~HE~HF~HF~HF~HF~HF~HF~HF~IG~IG~IG~IG~IG~IG~JG~JG~JG~JH~JH~JH~JG~KH~KH~KH~KH~LI~LI~LI~LI~KI~LI~LI~LJ~LJ~MJ~MJ~MK~MJ~MJ~NK~NK~NL~MK~NK~NL~NL~NL~OL~OL~OM~OM~PM~PM~PM~PM~PN~PM~PN~PN~QN~QO~QO~QO~RO~QO~RO~RO~RO~SP~SP~SP~SP~SP~TQ~TQ~TR~TQ~TQ~TR~UR~UR~UR~VS~US~VT~VT~VT~VT~VT~WT~WT~WT~WU~WU~XU~XU~XU~YV~XV~YW~YW~YW~YV~YW~ZW~[X~[X~[X~[X~[X~[X~\Y~\Y~\Y~]Z~]Z~]Z~^[~^[~^[~^[~^[~^[~^[~_\~`\~`\~`\~`]~`]~a^~a^~b_~a^~b_~b_~b_~b_~b_~c`~b`~b_~c`~ca~da~da~da~da~da~da~eb~fb~ea~ea~eb~fc~fc~fc~gc~gd~gd~ge~gd~hf~ig~jg~jg~jg~kh~kh~kh~li~ki~ki~li~li~kh~li~li~kh~li~li~lj~mj~mj~mj~li~mj~li~li~mj~li~mj~mj~mj~mj~nj~nj~nk~nk~nl~ol~ol~ol~ol~ol~ol~ol~pm~pm~pm~pm~pm~pm~pm~pn~qn~qn~pm~pn~qn~qn~qn~qn~qn~qn~ro~pn~qo~qn~qn~pm~qn~pm~qn~qn~pm~pn~pn~pn~pm~pm~pm~pm~pm~pm~ol~ol~ol~ol~pm~ol~nl~nl~nl~nk~nk~nk~nk~mj~mj~mj~mj~lj~lj~lj~lj~mj~mj~lj~mj~li~li~li~li~li~li~li~li~li~kh~kh~kh~kh~jh~jh~jg~ig~jg~jg~if~if~ie~he~he~he~he~ge~ge~gd~gd~gd~fc~fc~fc~ec~fc~fc~eb~eb~fc~eb~db~da~db~db~da~da~c`~c`~c`~c`~c`~b_~b_~b^~b^~b^~a^~a^~a]~`]~`]~`]~_\~`]~`]~_\~_\~_\~_[~_[~^[~^[~^[~]Z~]Z~]Z~\Y~\Y~\Y~\Y~\Y~\Y~[X~[X~[X~[X~[X~ZW~ZW~ZW~ZW~ZW~ZW~YV~YV~YV~XU~XU~XU~WU~XU~WU~WT~WT~WT~WT~WT~VS~VT~VS~VS~UR~VS~US~US~TR~TR~TR~TQ~TR~TQ~TQ~TQ~SP~SP~SP~SP~RP~RO~RO~RO~RO~QO~QN~QN~QN~QN~QN~QN~PN~PN~PN~OM~OM~OM~OM~OM~OM~OL~NL~NL~NL~NL~NK~NK~NK~MK~MK~MK~MJ~MJ~LI~LI~MJ~MJ~MJ~LI~LI~LI~KH~LI~JG~KH~KH~KH~JH~JH~JG~JG~JG~JG~JG~IG~JG~IG~IG~IF~IF~HE~IF~IF~HF~HE~HE~HE~HF~HF~HE~HF~GE~GE~GE~GD~GD~HD~GD~GD~GD~GD~GD~GD~GD~GD~GC~GC~GD~FC~FC~FC~FC~FC~FC~FC~FC~EB~EB~FC~EB~FC~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~DA~DA~DB~DA~DB~DB~DB~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~CA~CA~CA~CA~DA~CA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~CA~CA~DA~CA~DA~CA~CA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EA~DA~EA~EA~EA~EA~EA~EA~FB~EB~EB~EB~EB~EB~EB~EB~EC~EB~EB~EB~EB~EB~FC~EB~FC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~FC~FC~FC~FC~FC~FC~FC~GC~GC~FC~FC~GD~GD~FC~GD~FC~FC~FC~GD~GD~GD~GD~FC~GD~HE~GD~GD~GD~GD~GD~GD~GD~HE~GD~HE~HE~HE~GD~GD~HD~HD~HD~HD~HE~HE~IE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IF~IF~IF~HE~IF~HE~HE~IF~HE~IF~IF~IF~IF~IE~HE~IF~IF~IF~IF~JF~JF~IF~IF~IF~JF~JF~JF~IF~JF~JF~IF~IF~IF~JF~JF~JF~JG~KG~KG~JF~JG~JG~JF~JG~JG~JG~JG~JG~JG~JG~JG~KG~KG~KG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JF~JG~JG~JG~JG~JG~JG~JF~JG~JG~JF~JG~JF~JG~JF~JF~JF~JG~JG~JG~JG~IF~JF~JF~JF~IF~JF~JF~JF~JF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~HE~IF~HE~IE~IF~HE~HE~IF~HE~IF~IF~IE~HE~IE~IF~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~GD~HE~HE~GD~GD~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~GD~GD~GD~GD~GD~GD~GD~FD~FD~GC~GC~FC~GD~FC~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~FC~EC~EC~EC~EC~EB~EC~EC~EB~EC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~DA~DA~DA~EB~EB~EB~DA~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~C@~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~EB~EB~EB~EC~EB~EB~EB~EB~EB~DB~EB~EB~EB~EB~EC~EC~EC~EC~EB~FC~EC~EC~FC~FC~FC~FC~FC~FC~FD~FD~FD~GD~GD~GD~GD~GD~GE~GE~GE~GE~GE~HE~HE~HE~HE~HE~HE~HF~HF~HF~HF~IF~IF~HF~IG~IG~IG~IG~IG~JG~JG~JG~JG~JH~JH~JH~JH~JH~KH~KI~KI~KI~KI~KI~KI~LJ~LJ~LJ~LJ~MJ~MJ~MJ~MJ~MJ~MJ~MK~NK~NK~NK~NL~NK~NK~NL~OM~OL~NL~OM~OL~OM~PM~PM~PM~PN~PN~PN~QN~PN~QN~QO~QN~RO~RO~RO~SP~RO~RO~SP~RP~SP~SQ~TQ~TQ~TR~TR~TR~UR~UR~US~US~VS~VS~VS~US~VT~VT~VT~VT~XU~XU~XU~WU~XV~XV~XV~XV~YW~YW~ZW~ZW~ZW~ZW~ZW~ZW~[X~[X~[X~\Y~\Y~\Y~]Z~]Z~\Y~^[~^[~^[~^[~^[~_\~_\~_\~_\~_\~`]~a^~a^~a^~a^~a^~b_~b_~b_~b_~c_~c_~b_~c`~c`~c`~da~da~ca~da~da~da~eb~eb~da~ea~eb~ec~fc~fc~fd~gd~ge~ge~he~he~he~if~hf~ig~ig~jg~jg~kh~kh~ki~kh~kh~li~li~li~kh~li~li~li~li~li~li~mj~mj~nk~nk~mk~mk~mj~mj~mj~nj~nk~mk~nl~ol~ol~ok~ol~ol~pm~pm~pm~qn~qn~qn~pn~qn~pn~qo~qn~qn~qn~ro~qn~ro~ro~ro~ro~rp~rp~ro~ro~ro~ro~ro~ro~qo~ro~ro~qo~qo~qo~ro~qn~qn~qn~qn~qn~qn~qn~qn~pm~pm~qn~qn~pm~pm~pm~om~pm~pm~ol~ol~pm~ol~ol~nk~ok~nk~nk~nk~nk~mj~mj~mj~mj~nk~mj~lj~lj~mj~mj~mj~mj~li~mj~mj~li~li~li~li~li~li~li~li~kh~kh~kh~kh~jg~kg~jf~jg~if~ig~if~ie~ie~he~he~he~gd~gd~gd~gd~fc~gd~fc~fc~fc~fc~eb~eb~db~eb~ea~ea~ea~ea~d`~da~d`~c_~b_~b_~b_~b^~b^~b^~a^~a^~`]~a^~`]~`]~_\~_\~_\~_\~`]~_\~_\~_\~^[~^[~^[~]Z~]Z~]Z~]Z~\Y~\Y~\Y~\Y~[X~\Y~\Y~[X~\Y~[X~ZW~ZW~ZW~ZW~YV~YV~YV~YV~XU~XU~XV~XU~XU~XU~WU~WT~WT~WT~WT~WT~VS~VT~VS~VS~VS~UR~UR~TR~TR~TR~TR~TR~TQ~TQ~TQ~TQ~TQ~TQ~SP~SP~SP~RP~RP~RO~RO~RO~QN~QO~QN~QN~QN~QN~PN~PM~PM~PM~OM~OM~OL~OM~OL~OL~OL~NL~NK~NK~NK~NK~NK~MK~MJ~NK~MJ~MJ~LJ~LI~LI~LI~LI~LI~LI~LI~KH~KH~KH~KH~KH~KH~KH~JH~JH~JH~IG~IG~IG~IG~IG~IF~IG~IG~HF~IF~IF~IF~IF~HE~IF~HF~IF~GE~HE~GE~GE~GE~GE~GE~GE~GE~GE~GE~GD~GD~GD~GD~GD~GD~FC~GD~GC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~FC~FC~EB~EB~EB~EB~EB~EB~DA~EB~EB~EB~EB~DA~EB~EB~DB~DA~DB~DA~DA~EB~DA~DA~DA~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~CA~DA~DA~DA~DA~DA~DA~DA~DA~CA~DA~CA~DA~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~EB~DA~EB~DA~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~EA~EA~EB~EA~EA~EA~EA~EA~DA~DB~DA~DA~EB~EB~EB~EB~EB~EB~EB~EC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~FC~FC~FC~FC~FC~EB~FC~FC~FC~FC~EB~FC~FC~FC~FC~FC~FC~FC~GD~GD~FC~GC~FC~GD~GD~GD~GD~FC~GD~GD~FC~GD~GD~GD~GD~GD~GD~GD~GD~HD~HE~HE~HE~GD~HD~HE~HD~HE~HD~HD~HD~HE~HE~HE~HE~HE~HD~HE~HD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IF~IE~HE~IF~IF~IF~IE~IF~IF~IF~IF~IE~IF~JF~IF~IF~IF~IF~JG~JG~JF~IF~JF~JF~JF~JF~JF~JF~JG~JF~JF~JF~JF~JG~JF~JF~JF~JG~JG~JG~JG~KG~KG~KG~KG~JG~JG~JG~JG~JG~JG~JG~JG~KH~JG~KH~KH~JG~KH~JG~KG~KG~KG~KG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JF~JG~JG~JG~JF~JF~JG~JG~JF~JF~JG~JG~JG~IF~JG~JF~JF~JF~IF~JF~JF~JF~IF~JF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IE~IE~IE~IE~IE~IE~IE~HE~HE~HE~HE~HE~HE~IF~HE~HE~HE~HE~HE~HE~GD~HD~HD~HD~HD~GD~HE~HE~GD~HE~HE~GD~HE~HE~HE~GD~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GC~GC~GC~GC~GC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~FC~FC~FC~FC~FC~FC~EC~EC~EC~EC~FC~EC~EC~EC~EC~EB~EC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~EB~EB~EB~DA~EB~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~C@~DA~DA~DA~CA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~EB~EB~DA~EB~DA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EC~EC~EC~EB~EC~FC~EC~EB~EC~EC~EB~EC~FC~EC~FC~FD~FD~FD~FC~GD~GD~GD~GD~GD~GD~GD~GD~HE~GE~GE~GE~GE~HE~HE~HE~HE~HE~HF~HE~HF~HF~HF~HF~IF~HF~IG~IG~IG~IG~JG~JG~JG~JG~JG~JG~KH~JH~JH~KI~JH~KI~KI~KI~KI~KI~LI~LJ~LI~LI~LJ~MJ~MJ~MJ~MJ~MJ~NK~NK~MK~NK~NK~NK~NK~NL~OL~NL~OM~OM~PM~PM~PM~PM~PN~PN~PM~PN~PN~PN~QN~QO~QO~RO~RO~RP~SP~SP~RP~SQ~SP~SP~SP~SQ~TQ~SQ~UR~UR~UR~UR~UR~VS~VR~VS~VS~VS~VT~VT~WT~WT~WT~WT~XU~XU~XU~XU~YV~YW~XV~YV~YV~ZW~ZW~ZW~ZW~[X~[X~[X~\Y~\Y~\Y~\Y~\Y~]Z~]Z~]Z~]Z~^[~^[~^[~^[~_\~_\~`]~_]~_]~`]~`^~a^~a^~b_~b_~b_~b_~c`~b_~b_~c`~c`~c`~da~da~da~da~eb~eb~eb~fc~fc~eb~ec~eb~fc~fc~fc~fd~gd~ge~he~he~hf~ie~ie~if~jf~if~jg~jh~kh~kh~kh~li~ki~li~li~li~li~mj~mj~mj~mj~li~mj~mj~nj~mj~nj~nk~mj~nk~nk~nk~ol~ol~ol~ol~nk~om~pm~pm~pm~qm~qm~pm~pm~qn~qn~qn~ro~ro~ro~ro~ro~ro~ro~sp~ro~sp~sp~rp~sp~sp~sp~sp~sp~sp~sp~ro~rp~qo~ro~ro~ro~rp~sp~ro~ro~ro~sp~ro~ro~ro~ro~ro~ro~ro~ro~ro~qn~qn~qn~pm~qn~qn~qm~pn~pm~pn~om~om~om~om~ol~om~om~ol~ol~ol~ol~ol~nk~nk~nk~nk~nk~mj~nk~nk~mj~nk~mj~mj~mj~li~li~mj~li~li~lj~li~mj~mj~li~li~kh~jg~jg~kh~jh~jg~jg~if~jf~if~he~he~he~he~if~hd~gd~he~gd~gd~fc~fc~fc~fc~fc~ec~db~da~ea~da~da~da~c`~c`~c`~c`~c`~b_~b_~b_~b_~a^~a^~a^~`]~`]~a]~_\~`]~`]~`]~_\~_\~_\~_\~_[~^[~^[~^[~^[~]Z~]Z~]Z~]Z~\Y~\Y~\Y~[X~\Y~\Y~[X~[X~[X~[X~ZW~ZW~YV~ZW~ZW~ZW~YV~YV~XU~XU~XU~XU~WU~WT~WT~WT~WT~WT~VS~WT~VS~UR~UR~UR~UR~TR~UR~TR~US~UR~TQ~TQ~SP~SP~SP~TQ~SP~SP~SP~RP~RP~RP~RO~QO~QO~QO~QO~PN~PN~QN~PN~QN~PN~PM~OM~PM~OM~OM~OL~OL~OL~NL~MK~MK~NK~NK~NK~MK~NK~MK~MJ~MJ~MJ~MJ~LJ~LI~KI~LI~KI~KI~LI~LI~KI~KI~KH~KH~KH~KH~JG~JG~JG~JG~JG~IG~IF~IF~IG~IG~HF~HF~HF~HF~IF~HF~HE~HF~HF~HE~HE~HE~HE~GE~GE~GE~GD~GE~GD~GD~FD~FD~FD~FD~GD~GD~GC~FC~FC~FC~FC~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~FC~EB~EB~EB~DA~EB~EB~EB~EB~EB~EB~DA~DA~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~EB~DA~EB~EB~EB~EB~EB~EB~EB~EB~EA~EB~EA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DB~EB~EB~EB~EB~EB~EB~EB~FC~EB~EB~EB~EB~EB~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GC~GC~GC~FC~GC~GD~GC~FC~GD~GD~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~GD~HD~GD~HD~HD~HE~HE~HE~HE~HE~HE~IE~HE~HE~HE~HE~IE~IE~IF~IF~HE~HE~HE~IF~IF~IF~HE~HE~IF~HE~HE~HE~IF~IF~IF~IF~IF~IF~JF~JF~JF~JF~JF~IF~IF~IF~IG~IF~JF~IF~JG~IF~JF~IF~JG~JG~JF~JG~JG~JG~JG~JG~JG~JG~JF~JG~JG~JG~JG~JG~JG~JG~JG~KG~KH~JG~KH~KH~JG~KG~JG~JG~JG~KH~KH~KH~KH~KH~KH~KH~JG~JG~JG~JG~KH~KH~KH~JG~JG~KH~KH~JG~JG~KH~JG~KH~JG~KH~JG~JG~KH~KH~JG~KH~JG~JG~JG~JG~KH~KH~JG~JG~KG~JG~KG~KG~JG~JG~JG~JG~JG~JG~JG~JG~JF~JG~JG~JG~JF~JF~JF~JF~JF~JF~JF~JF~JF~JG~JF~JF~JF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~HE~HE~HE~HE~IF~HE~HE~IE~HE~HE~IE~IE~HE~HE~HE~HE~GD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~GD~GD~HE~GD~GD~GD~GD~GD~HE~GD~GD~GD~GD~GD~GD~GD~GD~GC~FC~GC~GD~GD~FC~FC~FC~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EC~FC~FC~EC~FC~EC~EB~EB~EB~EB~EB~FC~EB~EB~FC~FC~FB~EB~EB~EB~EB~EB~DA~EB~EB~EB~DA~DA~EB~DA~EB~EB~DA~EB~EB~EB~DA~DA~EB~DA~DA~DA~DB~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~EB~DA~DA~DA~EB~DB~DA~EB~DA~DB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EC~EC~FC~EC~EC~FC~FC~FD~FC~FC~FD~FC~GD~FC~FC~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~HE~HE~HE~GE~GE~GE~HF~HF~HF~IF~IF~IF~IF~IF~IF~IG~IG~JG~JG~JG~JG~JG~JG~JH~KH~KH~JH~KH~KH~JH~KI~KI~KI~KI~LJ~LI~LI~MJ~MJ~MJ~MJ~MJ~NK~MJ~MJ~NK~MK~MK~MK~NK~NL~NL~NL~OL~OL~OL~OM~OM~PM~PM~PN~PM~PN~PN~PN~QN~QN~QO~RO~RO~RO~RO~RP~SP~SP~SP~SQ~SQ~SP~SP~TQ~TQ~TQ~UR~UR~US~UR~VS~US~US~US~VS~VT~WT~WT~WT~XU~XU~XU~XU~XV~XV~XV~XV~YV~YW~ZW~YW~ZW~[X~[X~[X~[X~[X~\Y~\Y~\Y~]Z~\Y~]Z~^[~]Z~^[~^[~^[~^[~^[~_\~_\~_\~_\~`]~`]~a^~a^~b_~a^~b_~c`~c`~c`~ca~ca~da~d`~da~da~eb~eb~fc~fc~fc~fc~fc~fc~fc~fc~fc~gd~fc~gd~ge~he~gd~gd~he~if~hf~if~ig~ig~jg~jg~kg~lh~lh~kh~kh~li~lj~li~mj~lj~lj~nk~nj~nk~nk~nk~nk~mj~nk~ol~nk~ok~ol~ol~nl~nk~ok~pl~pm~pm~pm~pm~pn~pn~qo~pn~qn~ro~rn~ro~ro~qo~rp~sp~sp~sp~sq~sp~sp~sp~uq~sp~sp~sp~tq~sq~tq~sp~tq~sq~tr~sq~tq~sp~tq~tq~sq~tq~sp~sp~tp~tq~tp~tq~sq~tq~tq~sq~sp~rp~rp~sp~rp~ro~ro~ro~ro~ro~ro~qn~qn~ro~qn~qn~qn~qn~pn~qn~qn~om~pm~om~om~ol~ol~nl~ol~ol~ol~ol~ol~nk~nk~nk~nk~nk~nl~mj~mj~nk~nk~mj~nk~nk~mj~mj~mj~mi~mi~mi~mi~li~li~kh~kh~kh~jg~jg~jg~jg~if~if~if~he~if~he~hf~he~he~gd~ge~gd~fc~fc~fc~fb~fb~eb~eb~da~da~d`~da~da~da~da~c`~c`~c`~b_~b_~b_~a^~a^~a^~a^~a^~a]~`]~`]~`]~`]~_\~_\~_\~_\~_\~^[~_[~^[~^Z~^Z~]Z~]Z~]Z~\Y~\Y~\Y~[X~[X~[X~[X~[X~[X~[X~[X~ZW~YV~YV~YV~YV~XU~YV~XU~XU~XU~XU~WU~WU~WU~WU~WT~WT~VS~VS~VT~VS~US~US~TR~US~TR~UR~TR~TQ~TQ~TQ~TQ~TQ~SP~SP~TQ~SP~SP~SP~RP~QO~RO~RO~QO~QO~QO~QO~QN~QN~QN~PM~PM~PM~PM~PM~OM~OL~OL~OL~OL~NL~NL~NK~NK~NK~NK~NK~MJ~MJ~MJ~MK~MJ~LJ~LJ~MJ~LI~KI~LI~KI~KI~KI~KI~KI~KH~KH~JG~JH~JH~JG~JH~JG~JG~JG~IG~IF~IF~IF~IF~IF~IF~IF~HF~HE~HF~HF~HE~HF~HE~HE~HE~HE~HE~HE~GD~GD~GD~GE~GD~FD~FD~FD~FD~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~EB~EB~DA~DA~DA~EB~DA~EB~DA~EB~EB~DA~EB~EB~EB~EB~DA~EB~DA~DA~DA~EB~EB~EB~DA~DA~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~EB~DA~EA~EA~EA~DA~EA~EA~EA~EA~DA~EB~EB~EB~EB~EA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~EB~FC~FC~FC~FC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GC~FC~FD~FC~FC~GD~FC~FC~FC~FD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HE~HD~GD~GD~HE~HD~HD~HD~HD~HE~GD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IF~HE~HE~IF~IF~IF~IF~JF~IF~IE~IF~IF~IF~IE~IF~IF~IF~IF~IF~JF~JF~JF~JF~JF~JF~JF~JF~JG~JG~JG~JF~JG~JG~JG~JG~JG~JG~JG~JG~JF~KG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~KH~KH~JG~KG~KG~KG~KG~KG~KH~KH~KH~KG~KG~LH~KH~KH~KH~KH~KH~KH~KH~KH~LH~KH~KH~KH~KH~LH~KG~KG~LH~KG~KG~KH~KH~KH~KH~KH~KH~KG~KH~KH~KH~KH~KH~KH~KG~KH~KH~KH~KH~KH~KH~JG~KH~KH~KH~JG~JG~JG~KG~JG~JG~JG~JG~JG~JG~JG~JG~JG~KG~JG~JG~KG~JG~JG~JG~KG~JG~IF~JG~IF~JF~JF~JF~JF~JF~IF~IF~JG~JF~IF~JF~IF~IF~IF~IF~HE~IF~IF~HE~HE~IF~IF~IF~IE~IE~IE~IE~IE~IE~IE~HE~IE~IE~HE~HE~HE~HE~HE~IE~IE~HE~IF~HE~HE~HE~HE~HE~HE~HE~HE~HE~GD~HE~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~FC~GD~GD~FC~FC~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~FC~FC~FC~EB~EB~FC~EB~FC~FC~FC~EB~EB~EB~EB~FB~EB~EB~EB~EB~EA~EB~EA~EB~EB~EB~DA~DA~EB~DA~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DA~DB~DA~DA~DA~DA~DA~DB~DA~DA~EB~EB~EB~DA~DA~DA~DA~DA~DA~DA~EB~DA~EB~EB~EB~DA~EB~DA~EB~DA~DB~DB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~EB~EB~FC~EC~EC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~GD~GD~GD~GD~GD~GD~GE~GD~GE~GE~GE~GE~GE~GE~GE~HF~HF~HF~HF~HE~HF~IF~IF~IG~IG~IG~IG~IG~JH~JH~JG~JH~JH~JH~JH~JH~JH~JH~KI~KI~KI~KI~KI~LI~LJ~LJ~LJ~MJ~MJ~MJ~MJ~MJ~NK~NK~NK~MK~NK~MK~MK~NL~OL~NL~OL~OL~OL~OL~OL~PM~OM~OM~PN~PM~PM~PN~QN~QN~RO~QN~RO~RP~QO~RO~RO~RP~RP~SQ~SP~SQ~TQ~TQ~TQ~TR~UR~UR~UR~US~VS~US~VS~VS~VT~WT~WT~WT~XU~XU~XU~XU~XU~XV~XV~XV~YV~YV~YV~YV~ZW~ZW~ZW~ZW~ZW~[X~[X~\Y~\Y~]Z~]Z~]Z~]Z~]Z~]Z~^[~^[~_[~^[~_\~_\~_\~`\~`]~a^~`^~a^~a^~a^~b_~b_~c`~c`~c`~da~da~c`~da~eb~da~eb~eb~eb~eb~ec~fc~fc~fc~fd~fd~fd~gd~gd~he~he~he~he~ie~ie~if~if~jg~jg~kh~jg~kh~kh~kh~lh~li~mj~li~mi~mi~mj~nk~nk~nk~nk~nl~ol~ol~ok~ol~ol~ol~ol~pm~ol~ol~pl~pl~pm~pm~om~qn~qn~pm~qn~ro~qn~qo~qo~ro~rp~sp~rp~sq~sp~sp~sp~sp~sp~sp~sp~tq~tq~tq~tq~tq~tq~tq~ur~ur~tr~vr~ur~vr~tq~tq~vr~uq~uq~uq~uq~vr~tq~vr~tr~ur~tr~vr~ur~tq~vr~vr~tr~sq~tq~tq~sq~sq~rp~sp~ro~sp~ro~qn~ro~ro~qn~ro~ro~ro~qn~ro~qn~qn~pm~qn~qn~qn~pm~pm~pm~pn~om~pl~pl~pl~ol~om~ol~ol~ol~ol~ok~nk~ol~ol~nk~nk~nk~nk~mj~nk~mj~nj~mj~mj~mj~mj~li~li~li~kh~kh~kh~kh~kh~jg~jg~jg~if~if~if~he~he~he~he~gd~he~gc~gc~gc~fc~eb~eb~eb~da~eb~da~da~da~d`~da~c`~d`~c`~c`~c_~a^~a^~a^~a^~a^~`]~a]~a]~`]~`]~`]~_\~_\~_\~_\~_\~^[~^[~^[~^[~]Z~]Z~]Y~]Y~\Y~[X~\Y~\Y~\Y~\Y~[X~[X~[X~[X~ZW~ZW~ZW~YV~YV~YV~YV~YV~YV~XU~XU~XU~WU~WU~WT~WT~WT~VT~VT~WT~WT~VS~VS~VS~UR~US~US~UR~UR~TQ~TQ~TQ~TQ~TQ~SQ~SP~SP~SP~SP~RP~RO~RO~QO~RO~QO~QO~QN~QN~QN~QN~PM~PN~PM~PM~PM~OM~OL~OL~OL~NL~NL~NL~NL~NK~NK~NK~NK~NK~MK~MJ~NK~MJ~MJ~MJ~LJ~LJ~LJ~LJ~LI~LI~KI~KI~KH~KH~KH~KH~KH~KH~JH~JH~IG~JG~JG~JG~IG~IF~IF~IF~IF~IF~HF~HF~HF~IF~IF~IF~HE~HE~HE~HE~HE~HE~GE~GD~GD~GD~GD~GD~GD~GD~FD~GD~GD~GD~GD~GD~FC~FC~FC~FC~GC~FC~FC~FB~FB~FB~FC~FB~EB~FC~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EA~EB~EB~EB~DA~EB~EB~EB~EB~DA~EB~EB~EB~EB~EB~EB~DA~EB~EB~DA~EB~EB~EA~EA~DA~EB~EA~EB~EA~EB~EA~DA~EA~DA~DA~EA~EA~EA~EA~EB~EA~EB~EA~EA~EA~FB~FB~EB~EA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~FC~EB~FC~FC~EB~EB~EB~EB~EB~FB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GC~FC~GD~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~GD~HE~GD~HE~HD~GD~HE~HE~HD~HE~HE~HD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IE~IE~IE~IF~IE~IF~IF~IF~HE~IF~IF~IE~IF~IE~IF~IF~JF~JF~IF~JF~JF~JF~JG~JF~JF~JF~JF~JF~JF~JF~JF~JF~JG~JF~IF~JG~JG~JG~JG~JF~JG~KG~JG~JG~JG~KH~KG~KG~KG~JG~JG~JG~JG~JG~JG~JG~KH~KH~KG~LH~KH~KG~LH~KG~KH~KH~KH~KH~LH~LH~KH~KH~KH~KH~KH~KH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~KH~KH~KH~LH~KH~KH~KG~LH~LH~LI~LH~LH~LI~LH~LH~LH~LH~LH~LH~KH~LH~KH~KH~LH~LH~KH~KH~KH~KH~KH~LH~KH~LH~KH~KH~KH~KH~JG~JG~JG~JG~JG~JG~KG~JG~KG~JG~JG~JG~KG~JG~JG~JG~JG~JG~JG~JF~JG~JG~JF~JF~JG~JG~JG~JG~JG~JG~JF~JF~JF~IF~IF~IF~IF~IF~IE~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~HE~IF~IF~IF~IE~IE~IE~IE~IE~HE~IE~IE~IE~HE~HE~HE~HE~HE~HE~GD~HE~HE~HE~HE~HE~GD~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~FC~FC~FC~FD~FD~FC~FC~EB~FC~FC~FC~FC~EC~FC~FC~FC~FC~FC~FC~FC~EB~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~EB~EB~EB~EB~EA~DA~EB~DA~DA~DA~DA~EB~DA~EB~DA~EB~DA~DA~DA~DA~DA~DA~DA~DA~DB~DA~DA~DA~DA~DA~DA~DA~DA~EB~DA~DB~EB~EB~EB~DB~EB~EB~DA~DA~EB~EB~DB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~EB~FC~FC~FC~FC~EB~FC~FC~EB~FC~FC~FC~FC~GD~FC~FC~FC~GD~GD~GD~GD~GD~GD~GD~GD~GE~HE~HE~HD~HE~HE~HE~HE~GE~HE~HE~HF~HE~HF~IF~IF~IF~IF~IG~JG~IG~IG~JG~JG~JH~JG~JH~JH~JH~JH~KH~KH~KH~KH~KH~KI~LI~LI~LI~LI~LJ~LJ~LJ~LJ~LJ~MJ~MJ~MK~NK~MK~NK~NK~NK~OL~OL~OL~OL~OL~OL~PM~PM~PM~PM~PM~PM~PM~PM~QN~QN~QN~QN~RO~RO~RO~RO~QO~RP~RP~RP~SP~SP~TQ~SP~TQ~TQ~TR~TR~UR~UR~UR~UR~US~US~VS~VS~VT~WT~WT~WT~WU~WT~WU~XU~XU~YV~XV~YV~YV~ZW~YV~ZW~ZW~ZW~ZW~[X~[X~[X~\Y~\Y~\Y~]Z~]Z~]Z~^[~^[~^[~^[~_[~_\~_\~_\~`\~`]~`]~a^~a^~a]~a^~a^~b^~b_~b_~b_~c`~c`~da~da~da~da~eb~eb~eb~fb~fc~fc~fc~fc~gc~gd~gd~gd~he~gd~he~ie~he~ie~jf~if~jg~if~jg~kh~jg~kh~kh~kh~li~li~lh~mi~mi~mj~nj~nk~nk~nk~nk~ol~ol~ol~ol~ol~pl~pl~ol~om~pm~qm~pl~qm~qn~pm~pm~qn~qo~pn~qo~ro~ro~ro~ro~ro~sp~sp~sq~sp~tq~sp~sp~tq~tq~tq~ur~tq~tq~tq~tq~tq~ur~tq~tq~tq~ur~ur~tq~ur~vr~vr~vr~vs~uq~vr~vr~vr~vr~vr~vr~vr~vs~vr~wt~vs~vs~vs~vr~us~vr~ur~vr~uq~tq~tq~tq~tq~sp~sp~sp~sp~sp~ro~sp~sp~sp~sp~sp~ro~ro~qn~rn~rn~qm~qn~ro~qn~qn~qn~qn~qn~qn~pm~pm~pm~qn~pn~pm~pn~pm~ol~ol~pm~pm~pm~pm~nk~nl~ol~ol~nk~nk~nk~mj~mj~mj~mj~lj~mj~li~li~li~li~kh~kh~jh~jh~ig~jf~jf~if~ig~ig~hf~if~if~he~he~gd~fc~fc~ec~fb~eb~eb~eb~eb~da~ea~da~da~d`~d`~c_~c_~b_~b_~c`~b^~a^~b_~a^~a^~a^~a^~`]~a]~`]~`]~_\~_\~_\~_\~^[~^[~^[~]Z~^[~^[~^Z~]Z~]Z~\Y~\Y~\Y~\Y~[X~\Y~[X~[X~ZW~ZW~ZW~YV~YV~YV~YV~YV~XU~XU~XU~XU~XU~XU~WU~WU~WU~WU~WT~WT~WT~VS~VS~VS~VS~UR~US~UR~TR~TR~TQ~TQ~TQ~TQ~TQ~TQ~TQ~TQ~SP~SP~SP~SP~RO~RO~QO~QO~QO~QO~PN~PN~PN~PN~PN~PM~PM~PM~PM~OL~OL~PM~OL~OM~OM~OL~NL~NL~NK~NK~NK~NK~MJ~MJ~MJ~MJ~LI~LI~LI~LI~LI~LI~LI~KH~LI~KI~KH~KH~KH~KH~KH~JG~JG~JG~JG~IF~JG~IF~IF~IF~IF~HF~HF~IF~IF~IF~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~GE~GE~GE~GD~GD~GD~GD~GD~GD~FC~GD~FC~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EC~FC~FC~FC~FC~EB~EB~EB~EB~EB~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EA~EB~EA~EB~EB~EB~DB~EB~EB~EB~EB~EB~EB~EB~EA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~FC~FC~FC~EB~FC~FC~FC~FC~FC~FC~FC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~FC~FC~FC~GC~GC~GD~FC~GD~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~HD~HD~HD~HE~IE~HD~HD~HE~HE~HD~HE~HE~HE~HE~HE~IF~HE~HE~IF~IF~IF~HE~IF~IF~IE~HE~IF~HE~IF~IE~IF~IF~IF~JF~IF~JF~IF~JF~JF~JF~JF~JF~JF~JG~IF~IF~JG~JF~JF~JF~JG~JF~JG~JG~JF~JG~JG~JG~JG~JG~JG~JG~KG~KG~KG~JG~KG~KG~KG~KG~KG~KG~KH~KH~KH~KG~KG~KG~KG~KG~LH~KG~KH~KH~KG~LH~KH~KH~KH~KH~LH~LH~LH~KH~KH~LI~KH~LH~KH~KH~KH~KH~KH~KH~LH~LH~LH~LH~LH~KH~KH~KH~LH~LH~LH~LH~LI~LH~LH~LH~LH~LH~KH~LH~LI~KH~LH~KH~KH~LI~LI~LI~LH~LH~LH~LI~LH~LH~KH~KH~LH~LH~LH~LH~LH~LH~LH~KH~LH~LH~LH~KG~KG~KG~KG~KG~KH~KH~KG~JG~KH~JG~KG~KH~KH~KG~KH~JG~JG~JG~JG~JG~KG~JF~JG~JG~JG~JG~JG~KG~JG~JG~JF~JF~JF~JF~JF~JF~JF~JF~JF~JF~IF~IF~JG~IF~IF~IF~HE~IF~IF~IF~IF~IF~IF~IF~IE~IF~IF~IF~IE~IE~IE~IE~IE~HE~IE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~GD~GD~GD~GD~GD~GD~HE~HE~HE~GD~GD~HD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~FD~FD~FD~GD~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~FC~FC~FC~EB~EB~FC~EC~FC~FC~EC~EB~EB~EB~EB~EB~EB~EB~EB~EB~FB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~EB~EB~DA~EB~DA~EB~EB~DA~DA~EB~EB~EB~EB~EB~EB~EB~DA~DA~EB~DA~DA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~EB~EB~EB~FC~FC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FD~GD~FC~GD~GD~GD~GD~FC~GD~GD~FD~GD~GD~GD~GD~GD~HE~GE~GE~HF~HE~HE~HE~HE~HE~HF~IF~IF~IG~IG~IG~IG~IG~JG~JG~JG~JG~JH~JH~KH~JH~KI~JH~KI~KH~KH~LI~LI~LI~LI~LI~LI~LI~LI~LJ~MJ~MJ~MJ~MJ~MK~MK~NK~NL~NK~NK~NK~OL~OL~OL~OL~OL~OM~OM~OM~PM~PM~PM~QN~QN~QN~QN~QN~RO~RO~RO~RP~RO~RO~RO~SP~SP~SP~SP~TQ~TQ~TQ~TR~TR~UR~TR~US~US~US~US~VS~VT~WT~WT~WT~WT~XU~XU~XU~WU~XU~XU~YV~YV~YV~YV~ZW~YV~ZW~ZW~ZW~[X~[X~[X~]Z~]Z~]Z~]Z~\Y~]Z~^[~^[~]Z~^[~^[~_\~`]~`]~`]~`]~`]~`]~a^~a^~a^~a^~b^~c_~b_~b_~c`~c`~c`~da~da~da~eb~eb~fb~fc~gd~gd~gd~gc~gc~gd~he~he~he~he~he~he~he~if~if~if~ig~jg~jg~jh~kh~kh~li~li~mj~mj~mj~lj~mj~mk~ol~nk~nk~ol~ol~ol~pm~pm~pm~pm~pm~qn~pm~pm~pn~qo~qn~qn~qn~rn~ro~ro~qn~ro~ro~ro~ro~ro~ro~sp~ro~tq~tq~tq~tq~tq~tq~ur~ur~ur~ur~ur~ur~ur~vs~ur~ur~ur~ur~vs~vs~vs~vs~ur~ur~vs~vs~ur~vs~vs~vs~vs~wt~vs~wt~vs~vs~vs~vs~wt~wt~vs~vs~wt~wt~wt~vs~vs~vs~vs~ur~ur~ur~ur~tq~tq~tq~tq~sp~sp~sp~sp~sp~tq~tq~so~sp~sp~sp~ro~ro~ro~ro~sp~qo~qo~qo~qn~qn~qn~qn~ro~qn~qn~qn~qn~pm~pm~pm~pm~qn~qm~pm~pm~pm~pm~pm~ol~ol~om~nl~ol~nk~nk~nk~nk~mj~mj~li~li~li~li~li~kh~kh~kh~kg~jg~jg~jf~jf~jf~if~if~he~he~he~gc~gd~gc~gc~fc~fc~eb~fc~eb~eb~ea~da~ea~da~d`~c`~c`~c_~c_~b_~b_~b_~b_~b_~a^~a^~`]~`]~`]~`]~`\~`\~`\~_\~_[~^[~^[~^[~]Z~]Z~^[~]Z~]Z~]Z~\Y~]Y~\Y~\Y~\Y~\Y~[X~ZW~[X~ZW~ZW~ZW~YV~ZW~YV~YV~XU~YV~YV~YV~YV~WU~XU~XU~WT~WT~VT~WT~VT~VT~VS~VS~VS~UR~US~UR~UR~UR~TQ~TQ~TQ~TQ~TQ~TQ~SP~SP~SP~SP~RO~RO~SP~RO~RO~QO~QN~QN~PN~QN~QM~QM~QM~PM~PM~PM~PM~PM~PM~OL~OL~OL~OL~OL~NK~NK~NK~NK~MK~MK~MJ~MJ~MJ~LI~MJ~LI~LI~LI~LI~LI~LI~KH~LI~KH~KH~KH~JH~JH~KH~JH~JH~JG~IF~JG~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~HE~HE~HE~HE~HE~HE~HE~GE~GD~GD~GD~GD~GD~GD~GD~GD~GD~FD~GD~FC~FC~GD~GD~FC~FC~FC~FC~EC~EC~FC~FC~FC~FC~FC~FC~FC~EB~EB~FC~EB~EB~EB~FC~FC~EB~EB~EB~EB~EB~EB~EB~EB~EC~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DB~DB~DB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EC~EB~FC~FC~FC~EB~FC~EB~EB~FC~FC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GC~FC~GC~GC~GC~GD~FC~FC~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~HD~HD~HD~HD~HD~HD~HD~HD~HE~HE~HE~HE~IE~IE~HE~IF~HE~HE~HE~HE~HE~IF~IF~IF~IF~IF~IF~JF~IF~IF~IF~IF~IF~JF~IF~IF~JF~JF~JF~IF~IF~IF~IF~JG~JF~JF~JF~JF~JF~JF~JF~JF~JG~JG~IF~JG~JG~JG~KG~JG~KG~JG~JG~JG~JG~JG~JG~JG~KG~KG~KG~KG~KG~KG~KH~KG~KH~KH~KH~KG~KH~KG~KG~KG~KH~KH~KG~KG~KH~KG~LH~LH~LH~KH~KH~KH~LH~KH~KH~LH~LH~LH~LH~LH~LI~KH~KH~LH~LH~LH~LH~LH~LH~LH~LH~LI~LI~MI~LH~LH~LH~LH~MI~MI~MI~LI~LI~LI~LI~LI~LI~LH~LI~LH~LI~LH~LH~LI~LH~LH~LI~LI~LI~LI~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~KH~KH~KG~KG~KH~LH~KH~KH~KH~LH~KH~KH~KH~KH~KH~KH~KG~KG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JF~JF~JF~KG~JF~JG~JF~JG~JF~JF~JF~JF~JF~JF~JG~JG~IF~JF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~HE~IE~IE~IE~IE~IE~IE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~GD~HE~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~FC~FC~EB~EB~EC~EC~EB~EB~EB~EB~FB~EB~EB~FC~EB~FB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~EB~DA~DA~EB~EB~EB~EB~DA~EB~EB~DA~EB~EB~EB~EB~EB~DA~EB~EB~DB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EC~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~FC~FC~FC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GC~FC~FC~FC~GD~GD~GD~GD~GD~GD~GD~GE~GE~GE~GE~GE~GE~GE~GE~HE~HE~HE~IF~HE~IF~IF~IF~IF~IG~IG~IG~IG~IG~IG~JG~JG~JG~JG~JH~JH~KH~KH~KH~LI~KH~KH~LI~LI~LJ~LI~LI~LI~LI~LI~MJ~LJ~MJ~MJ~MK~MK~NK~NK~NK~NK~NL~NL~OL~NK~OL~OL~OL~OL~OM~OL~OM~PM~PM~PM~QN~QN~QN~QO~QO~QN~QO~RO~RO~RO~RP~SP~SP~SP~TQ~TQ~TQ~TQ~TQ~TR~TR~TR~US~US~US~VS~VS~WT~WT~WT~WT~WU~XU~WT~XU~XU~XU~XU~XV~YV~YV~YV~YV~YV~ZW~ZW~[X~[X~[X~\Y~\Y~\Y~\Y~\Y~]Z~]Z~]Z~^[~^[~_[~_[~_\~_\~`\~`]~`]~`]~`]~a^~a^~b^~b_~b^~b_~c_~c`~c`~c`~c`~c`~da~da~eb~eb~fc~fc~fc~gd~gd~he~gd~gd~he~he~he~if~he~if~if~he~if~jf~if~jg~jg~kh~jg~ki~kh~lh~li~lj~mk~mk~mk~nk~nk~ol~nl~ol~ol~ol~pm~qm~pm~pm~qn~qn~rn~rn~qn~qn~qn~qn~qn~qn~ro~ro~ro~ro~ro~sp~sp~sp~tq~tq~tq~tq~tq~tq~tq~tq~ur~vs~vs~vs~vs~vs~vs~vs~vs~vs~vs~vs~vs~vs~vr~ws~ur~wt~wt~wt~vs~wt~vs~wt~wt~vs~wt~xu~xu~wt~wt~wt~vs~wt~xu~wt~wt~wt~wt~wt~wt~wt~wt~wt~wt~wt~wt~wt~wt~wt~wt~ur~ur~ur~ur~ur~ur~ur~ur~ur~ur~tq~tq~tq~tq~sp~sp~sp~sp~sp~sp~sp~rp~sp~qo~ro~ro~ro~so~ro~sp~ro~sp~ro~qn~qn~ro~qn~qn~pm~pm~qn~qn~pm~pm~pl~pl~pl~ol~ol~ol~nk~mk~mk~mj~nk~mj~mj~mj~mj~li~kh~kg~kh~kh~kg~jg~jg~jg~if~if~if~if~he~gd~he~hd~gd~gc~fc~gc~fc~fb~fc~eb~fb~ea~da~da~d`~d`~c`~c`~b_~b_~b_~b_~b_~b_~b^~b_~a^~a^~a]~a]~`]~`\~`\~`\~^[~_\~^[~^[~]Z~^[~^[~]Z~]Z~]Z~]Z~]Z~\Y~[X~[X~\Y~\Y~[X~[X~ZW~ZW~YW~ZW~YV~YV~ZW~YV~XV~YV~XU~XU~XU~XU~XU~WT~WT~WT~WT~VT~VT~VT~US~VS~VS~UR~UR~TQ~UR~UR~TR~TQ~TQ~TQ~TQ~SP~SP~RP~RP~SP~RO~RO~RO~RO~RO~RO~QN~QN~QN~QN~PN~PM~PM~PM~PM~PM~PM~OL~PM~OL~OL~OK~OK~NK~NK~OK~NK~MJ~MK~MJ~MJ~MJ~MJ~MJ~MJ~LI~LI~LI~LI~LI~LI~KH~KH~KH~KH~JH~JH~JH~JG~JG~JG~JG~JG~JG~JG~IF~IF~IF~IG~HF~HF~HF~HF~HF~HF~HF~HF~HF~HE~HE~HE~HE~HE~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~FC~GD~GD~FC~GD~FC~EC~FC~FC~FC~FC~FC~FC~FC~EB~FC~EC~EC~EC~EB~FC~EC~EB~EB~EB~EB~EB~EB~EB~FB~FB~FB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~EB~FC~EB~FC~FC~FC~FC~FB~FC~FC~FC~FC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FD~FC~FD~GD~GC~GC~GD~GD~GD~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~GD~HD~HD~HD~HD~HD~HD~HE~HE~HE~HD~HE~HE~HE~HE~HE~HE~IF~IF~IF~IF~IF~IE~IE~IF~IF~IF~IF~IF~JF~JF~IF~JF~JF~JF~JF~JG~JF~JF~JF~JF~JG~JF~JG~JF~JF~JF~JF~JG~JF~JG~JG~JG~JG~JG~JG~KG~JG~KG~JG~KG~KG~JG~KG~KG~KG~KG~KG~KG~KH~KH~KG~KH~KH~KG~KG~KG~KH~KH~KH~KH~KH~KH~KH~KG~KH~KG~KH~KH~KH~KH~KH~KH~LH~LH~LH~LI~LH~LI~LI~MI~LH~MI~LH~LH~LH~LH~LH~LI~LI~LI~MI~MI~LH~LH~LH~LI~MI~MI~LI~MI~LI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~LI~LI~MI~MI~MI~MI~MI~LI~LH~LH~LI~LH~MI~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~KH~KG~KH~KH~LH~KH~KH~LH~KH~KG~KG~KH~KG~KH~KG~KH~KG~KG~KG~KG~KH~KG~KG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JF~JF~JF~JF~JF~JF~JG~JF~IF~JG~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IE~IF~IF~IF~IF~IF~IE~IF~IF~IE~IE~IF~IE~HE~HF~IF~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~GD~GD~GD~GD~GD~GD~GD~GE~GD~GE~GD~GC~GC~GC~GC~GC~GD~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~FC~FC~EB~EC~EB~EC~EB~EB~EB~EB~FB~FB~FB~FC~EB~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~DA~EB~EB~EB~EB~DA~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~FD~GD~GD~GD~GE~GD~GD~GE~GE~GE~GE~HE~HE~HE~HE~HE~HE~HE~IF~IF~IF~IF~IF~IG~IF~IG~IG~IG~JG~JG~JG~JH~JH~JH~KH~KH~KH~KH~KH~LI~LI~LI~LI~LJ~LJ~LJ~LJ~LI~LJ~LJ~LJ~MK~MK~MK~NK~NK~NK~OL~NL~NL~OL~NL~NL~OL~OL~OL~OM~PM~PM~PM~PM~PN~PN~PN~QN~QO~QO~QO~RO~QO~RP~RP~RP~SP~SP~SP~TQ~TQ~TQ~TQ~TR~UR~TR~US~US~US~VS~VT~VS~VS~VT~WT~WU~WU~WU~XU~XU~YV~YV~YV~YV~YV~ZW~YV~YV~ZW~ZW~ZW~[X~[X~[X~\Y~]Z~\Y~]Z~]Z~]Z~]Z~^Z~^[~_[~_\~_\~_\~_\~`]~`]~`]~`]~a]~a^~a^~a^~b^~c_~c`~c_~c`~c`~da~d`~d`~ea~ea~eb~fb~fb~fb~fb~fd~ge~he~he~he~he~he~he~ie~if~if~jg~jg~jg~jg~kg~jg~kh~li~li~li~li~li~mj~mj~nk~ol~nk~ol~ol~ol~ol~ol~pm~pl~qm~qn~pn~qo~pn~qo~qn~rn~qn~ro~ro~qn~rn~ro~ro~sp~sp~sp~sp~sp~tq~sp~tq~tq~ur~ur~ur~ur~ur~vs~vs~vs~vs~wt~wt~vt~wt~wt~wt~wt~wt~wt~xu~wt~wt~wt~wt~wt~wt~wt~wt~wt~wt~wt~wt~wt~wt~xt~wt~xu~yv~wt~wt~wt~xu~xu~xu~xu~xu~yu~yu~xt~wt~wt~wt~wt~wt~wt~wt~wt~wt~wt~wt~wt~wt~vt~ur~vr~ur~ur~ur~ur~ur~ur~ur~ur~ur~tr~ur~sp~sp~sp~sp~sp~sp~sp~sp~ro~sp~sp~sp~sp~ro~ro~ro~ro~ro~ro~ro~ro~ro~qn~ro~qn~qn~ol~pm~pm~qn~pm~pm~pm~pm~ol~ol~ok~nk~nj~nj~mi~mi~mi~mi~li~kh~li~li~kh~kg~kg~kg~jf~jf~ie~he~he~if~he~he~gd~gc~gc~fc~fc~gc~fc~fc~eb~eb~eb~da~da~da~da~d`~d`~d`~c_~c`~c_~b_~b_~b_~b_~a^~a^~`]~`]~`]~`\~_\~_]~`\~_[~_[~^[~^[~^[~]Z~]Z~]Z~]Y~]Y~\Y~\Y~\Y~\Y~[X~[X~ZW~[X~ZW~ZW~ZW~ZW~ZW~ZW~YV~YV~XU~XU~XU~XU~XU~XU~WT~WT~WT~VT~VT~VS~VS~VS~VS~VS~VS~UR~UR~US~UR~UR~UR~TQ~TQ~SQ~SQ~SP~SP~SQ~SP~RP~RP~RO~RO~RO~RO~QN~RO~QN~QN~PM~QN~PM~PN~PM~PM~PL~PM~PL~OL~OL~OL~OL~OK~NK~NK~NK~MK~MK~MJ~MJ~MJ~MJ~LJ~MJ~LI~MJ~LJ~LI~LI~LI~LI~KH~KH~KH~JH~KH~KG~JG~JG~JG~JG~JG~IG~JG~IF~IF~IF~IF~IF~IF~IF~IF~IF~HF~HF~HF~HF~HE~HD~HE~HE~HD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~FC~GD~GD~GD~FC~FC~FC~FC~FC~FC~FC~FC~EC~EC~EC~FC~FC~FC~FC~FC~FC~EB~EB~EB~EB~EB~EB~FC~EB~EB~EB~EB~FC~EB~EB~EB~EB~EC~EB~EB~EC~EC~EB~EC~FC~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~EB~EB~EB~EB~EB~EB~EB~FB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FB~FB~FC~FB~FC~FC~FC~GD~FC~FC~FC~GC~FC~FC~FC~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~GD~HD~HD~HD~HD~HD~HD~HD~HE~HE~IE~HE~HE~HE~HE~HE~HE~HE~HE~IF~IE~IE~IE~JF~IF~IE~IF~IF~IF~JF~IF~JF~JF~JF~JF~JF~IF~JG~JG~IF~JF~JF~JF~JF~JG~JG~KG~JG~JG~JG~KG~KG~KG~JG~KG~JG~KG~KG~JG~KG~KG~KG~KG~LH~KH~KG~LH~KG~KG~KG~KG~KH~KH~KH~KG~KG~KG~KH~KH~KH~KH~KH~KH~KH~LH~KH~KH~LH~LH~LH~LH~LH~LH~LH~LI~LI~LI~LI~LI~MI~LH~MI~LH~LI~MI~MI~MJ~MI~MI~MI~MI~MI~LH~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~LI~MJ~MJ~MJ~MJ~MJ~MI~MI~MI~MJ~MI~NJ~MI~MJ~MI~MI~MI~MJ~MI~NJ~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~LI~LI~LH~LH~LI~LH~LH~LH~LI~LI~LH~LH~LH~LH~KH~LH~LH~LH~LH~LH~KH~LH~LH~KH~KH~KG~KH~KH~KG~KG~KG~KG~KH~KG~KH~KH~KH~KG~KG~KG~KH~KG~KH~JG~KH~JG~JG~JG~JG~KG~KH~KH~JG~JG~JG~JG~JG~JG~JG~JF~JG~KG~JG~JG~JG~JF~JF~JF~JF~JF~JF~JF~IF~IF~IF~IF~IF~IF~IF~HE~IF~IF~IF~IF~IE~IE~HE~IF~IE~IE~HE~IE~IE~IE~IE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~GD~HE~GD~GD~HE~GD~GD~HD~GD~GD~GD~GD~GD~GD~GD~GD~GC~GD~GD~GC~GC~GD~GC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EC~FC~EB~FC~EB~EB~EB~EB~FC~FC~FB~FB~EB~FB~FB~FB~FB~EB~EB~FB~EB~EB~EB~FB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~EB~FC~EB~EB~EB~FC~EB~FC~EB~EB~EB~EB~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~GC~GD~FC~FC~GC~FC~FD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GE~HE~HE~HE~GD~HE~HE~HE~HE~HE~HE~IF~IF~IF~IF~IF~IF~JG~IF~JG~JG~IF~JG~JG~JG~KH~JG~KH~KH~KH~KH~KI~LI~LI~LI~LI~LI~LI~LI~LI~LJ~LJ~MJ~MJ~MJ~MJ~NK~NK~NK~NK~NK~NK~NK~OL~OL~OM~OL~OL~OL~OM~PM~OL~PM~PM~QN~QN~PN~PN~QN~QO~QN~RO~RO~RO~RO~RO~SP~SP~SP~SP~SQ~SQ~SQ~TR~TR~UR~TR~UR~UR~VS~VS~VS~VS~WT~WT~WT~WT~WU~XV~XU~XU~YV~YV~YV~YV~ZW~ZW~ZW~YV~ZW~ZW~[X~[X~\Y~\Y~\Y~\Y~]Z~\Y~]Z~]Z~]Z~^[~_[~^[~^[~_\~_\~_\~`]~a]~`]~`]~a^~a^~b^~b_~a^~b_~b_~c`~c`~c`~da~da~ea~eb~ea~ea~fb~fb~eb~fc~fc~gd~gd~he~he~he~if~if~if~if~if~jg~jg~jg~jg~kh~kh~kh~kh~ki~lj~lj~nk~mj~mj~nj~mj~nk~nk~ol~pm~pm~pm~pm~pm~qn~qn~qm~rn~ro~sp~ro~ro~ro~ro~ro~ro~ro~sp~tq~ro~sp~tq~tq~tq~ur~ur~ur~ur~ur~ur~ur~ws~ws~wt~wt~vs~ws~wt~wt~wt~vt~xv~wt~yu~yu~yu~yv~yv~xu~yv~yv~xt~xt~yv~wt~yv~xt~yu~yv~zv~zv~yu~yu~yu~yv~yv~yu~yv~yv~yv~yv~yv~zw~yv~yv~yv~yv~zv~yv~yv~xv~xv~xv~yv~yv~yv~xv~xt~xv~wt~wt~wt~xt~wt~ws~ws~wt~wt~vr~vr~vs~ur~ur~ur~ur~ur~ur~ur~ur~sp~tr~sp~uq~sp~sp~sp~sp~sp~sp~sp~sp~sp~sp~sp~sp~sp~sp~ro~ro~ro~qn~qn~ro~qn~qn~qn~qn~pm~pm~pm~pl~ol~ok~ok~nj~nj~nk~mj~mj~nj~nj~mi~li~lh~lh~kg~kh~jg~jg~if~jg~if~ie~ie~ie~he~he~gd~gd~gd~gd~fc~fc~fc~fc~eb~fc~eb~eb~eb~da~da~c`~c`~c_~c`~c_~c_~c_~c_~b^~b^~a^~`]~`]~`]~`]~`]~`]~`\~`\~_[~_[~^[~]Z~^[~]Z~]Z~]Z~]Z~\Y~]Z~]Y~\Y~\Y~\Y~\X~[X~[X~[X~ZW~ZW~ZW~ZW~ZW~YV~YV~YV~XU~XU~XU~XU~XU~XU~XU~WT~WT~WT~VS~VS~VS~VT~US~UR~UR~UR~UR~UR~UR~TQ~TQ~TQ~SQ~SQ~SQ~SP~SP~SP~SP~RO~RO~RO~RO~RO~RO~RO~QN~QN~QN~QN~QN~PM~PM~OM~PM~OM~OL~OL~OL~OL~OL~NK~OL~NK~NK~MJ~NK~NK~MK~MJ~MJ~MJ~LI~LI~LI~LI~LI~LI~LI~LI~LI~KH~KH~KG~KG~KH~JG~JG~JH~JG~JG~JG~JG~JG~IF~IF~IF~IF~IF~IF~HE~IF~IF~HE~HE~HE~HE~HE~HE~HE~HE~GE~HE~GD~GE~GE~GD~GD~GD~GD~GD~GD~GD~GD~FD~GD~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~FC~FC~FC~FC~FC~FC~EB~FC~FC~FC~FC~FC~EC~FC~EB~EB~EB~EC~FC~EB~EB~FC~FC~EB~FC~FC~FC~FC~EB~EB~EB~EB~FB~EB~EB~FC~FC~FC~EB~FC~FC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FD~FC~GD~FC~FC~FC~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~HD~GD~HD~GD~GD~GD~HD~HD~HE~HD~GD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IF~IF~IF~IE~JF~JF~IF~IF~JF~IF~IE~IF~JF~IF~JF~JF~JF~JF~JF~JF~JF~JF~JF~JG~JG~JG~JG~JG~JG~KG~KG~KG~KG~KG~KG~KG~KG~KG~KH~KG~KG~KG~KG~KH~KG~KG~KG~KH~LH~KG~KG~KH~KH~KH~KH~KH~KH~KH~KG~LH~LH~KG~LH~KH~LH~KH~LH~LH~LH~LI~LH~LH~LH~LH~LH~LH~LH~LH~LI~LI~LI~LI~LI~LI~LI~LI~MI~LI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MJ~MJ~MI~NJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~NJ~MJ~NJ~NJ~MI~MJ~MJ~MJ~MI~NJ~MI~MJ~MI~MJ~MI~MI~NJ~MJ~MJ~MJ~MI~MI~MI~MI~MI~MJ~MI~MI~MI~MI~MI~MI~MI~MI~MI~LH~LH~MI~LI~LI~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~KG~KH~KH~KH~KH~KH~LH~LH~LH~LH~LH~LH~LH~LH~KG~KG~KH~LH~KH~KH~KH~KH~KH~KG~KG~KG~KG~KG~KG~KG~JG~JG~JG~JG~JG~KG~JG~JG~JG~JG~JG~JG~JG~JF~JF~JF~JF~JG~JF~JF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~HE~IF~HE~IE~IF~IE~IE~HE~IE~IE~IE~IE~IE~IE~HE~HE~HE~HE~HD~HE~HE~HE~HE~HE~HE~GD~GD~HE~GD~GD~GD~GD~GD~GD~GD~GD~GC~GD~GD~GD~GD~GD~GC~GC~GC~GC~GC~GC~GC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~EB~FC~FC~FC~FC~EB~FC~EB~EB~FC~EB~EB~EB~EB~EB~EB~EB~EB~EC~EB~EB~EB~EB~EB~EB~FB~FB~EB~EB~EB~FC~FC~EB~EB~EB~EB~EB~EB~FB~EB~FB~FC~FC~FB~FC~FC~FB~EB~FC~FC~FB~FC~FC~FC~FC~FC~FC~FC~FC~FD~FC~FC~GD~FC~FD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GE~GD~GD~GD~HE~HE~GE~HE~HE~HE~HE~HE~IF~IF~IF~IF~IF~IF~IF~JF~IF~IF~IG~JG~JG~JG~JG~JG~KH~JH~JH~KH~KI~KI~KH~LI~LH~LI~LI~LI~LI~LJ~MJ~MJ~MJ~MJ~MJ~NK~NK~NK~NK~NK~NK~NK~OK~OK~OL~OL~OL~OL~PM~OL~PM~PM~PM~PM~PN~PM~PN~PN~PN~QN~QN~QN~RO~RO~RO~SP~SP~SP~SP~SQ~SQ~TQ~TQ~TR~TR~TR~TR~UR~UR~UR~VS~VS~VS~WT~VS~WT~WU~WU~WU~XV~XV~XV~YV~YV~YV~ZW~YV~YV~ZW~ZW~ZW~[X~\Y~[X~\Y~\Y~\Y~]Z~\Y~]Z~^Z~^Z~^[~_[~_\~_\~_\~_\~`]~_\~`\~a]~`]~a]~a^~a^~a^~b_~b_~c_~c_~d`~d`~d`~da~da~ea~eb~eb~eb~fc~fc~gd~gd~fc~gd~hd~he~he~if~ig~jg~jg~kh~kh~jg~kh~jg~kh~kh~li~li~li~li~li~mj~nk~nk~nk~nk~ol~ok~ol~pl~pm~pm~pm~qn~qn~ro~ro~qn~qn~ro~ro~ro~sp~sp~sp~sp~sp~sp~tq~tq~tq~ur~ur~ur~ur~vs~vs~vs~ur~vs~ws~wt~wt~xt~wt~wt~yu~xt~xt~xt~xt~xv~yv~yu~yv~zv~zv~zv~zv~zv~zv~zv~zv~zv~zv~zv~zv~zv~zv~zv~zv~zv~zv~zv~zv~zv~zv~zv~xv~zv~zv~zv~zx~zx~zx~zx~{x~{x~{x~{x~zx~zx~zx~zx~zv~zv~zv~zv~zv~yu~zv~yu~xt~xt~xt~xt~xt~xt~xt~wt~xt~wt~wt~wt~wt~wt~vt~wt~vt~vt~vr~tr~ur~vr~vr~vr~ur~ur~ur~tp~tp~uq~uq~tp~sp~sp~sp~so~sp~sp~sp~ro~ro~ro~ro~rn~qn~qn~qn~qm~qm~qm~qm~qm~ol~ol~ok~ok~ok~ok~ok~nj~nj~nk~mj~mj~mi~li~lh~kh~lh~kh~jg~if~jg~jf~jf~if~ie~he~he~he~gd~gd~gd~gd~gc~gc~fc~fc~eb~eb~ea~ea~da~da~da~da~c`~c`~c`~c`~b_~a^~b_~b_~a^~a^~a^~a^~`]~`]~`]~_\~_\~_\~_[~^[~_[~_[~^[~]Z~]Z~]Z~]Z~]Z~]Z~]Y~\Y~\Y~\X~[X~[X~ZW~ZW~ZW~ZW~ZW~ZW~ZW~YV~YV~XU~XU~YV~XU~XU~XU~XU~WT~WT~WT~WT~VS~VT~VS~US~VS~UR~UR~UR~UR~TQ~TQ~TQ~TQ~TQ~SQ~SP~SP~SP~SO~SP~RO~RO~RO~RO~RO~QN~QN~QN~PM~QN~QN~PM~PM~PM~OM~PM~PM~OL~OL~OL~OL~NL~NK~NK~NK~NK~NK~MJ~MJ~NJ~MJ~MJ~MI~MJ~LI~LI~LI~LI~LI~LI~LI~KH~KH~KH~KH~KH~KH~KH~JG~JG~JG~JG~JG~IF~IF~IG~IF~IF~IF~IF~IF~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HD~GD~HE~GD~GD~GD~GE~GD~GD~GD~FC~GD~GD~GD~GD~FC~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~FC~FC~FB~FC~FC~FC~FC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~FC~FC~FC~FC~EB~FB~FC~FB~FC~EB~FC~FB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GC~FC~GC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~GD~GD~FD~GD~FD~GD~FD~GD~GD~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~GD~GD~GD~GD~HD~HD~GD~GD~GD~GD~GD~HD~HD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IE~IF~IE~IF~IF~IF~IF~JF~JF~IF~JG~JF~JF~JG~JG~JF~JG~JG~JF~JG~JG~KG~KG~JG~JG~KG~JG~KG~KG~KG~KG~KG~KG~KH~KH~KH~KH~KG~KG~LH~KH~JG~KG~KH~KH~KH~KG~KH~KH~LH~LH~KG~LH~LH~KH~KH~KH~LH~LH~LH~LH~LH~LH~MI~LH~LH~LH~LH~LI~MI~LH~LH~MI~LI~LI~MI~LI~LI~LI~MI~LI~LI~MJ~MJ~LI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MJ~MJ~MJ~MI~MJ~MJ~MI~NJ~MJ~NJ~NJ~NJ~NJ~NJ~MJ~NJ~NJ~NJ~NJ~MJ~NJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~NJ~NJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~NJ~MJ~MJ~NJ~NJ~NJ~MJ~MJ~MJ~MJ~MJ~MI~MJ~MJ~MI~MI~MJ~MI~MI~MI~MI~MI~MI~MI~MJ~MI~MI~MI~MI~LI~MI~MI~LH~MI~MI~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LI~LH~LH~KH~LH~LH~LH~LH~LH~LH~LH~KG~KH~LH~KG~KH~KH~KH~KH~KG~LH~KH~JG~KG~KH~KH~KH~KH~JG~KH~KG~JG~JG~JG~KG~JG~JG~JG~JG~JG~KG~JG~KG~JF~JG~JF~JG~JF~JF~JG~IF~IF~IF~JG~IF~IF~IF~IF~IF~IF~IF~IF~HE~IE~IF~IE~IF~IE~IE~IE~IE~IE~IE~HE~IE~IE~HE~HD~HD~HE~HE~HE~GD~HE~GD~HE~GD~GD~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~GD~GD~GD~GD~GC~GC~GC~GD~GC~GC~GC~GC~FC~FC~GD~FC~GD~FC~FC~FC~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~FC~FC~FC~EC~EC~FC~FB~FC~FC~FC~FC~FB~FB~FC~FC~FC~EB~FC~FC~FB~FC~FB~FB~FC~FC~FB~FB~FC~FC~FB~FB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~GD~FD~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HE~HE~HE~HE~HE~HE~HE~HE~HE~IF~IF~IF~IF~IG~IG~IF~IF~JG~JG~JG~JG~JG~JG~KH~KH~JG~JG~KH~KH~KH~KH~LI~LI~LH~LI~LJ~LI~MJ~MJ~MJ~MJ~MJ~MJ~MJ~NJ~NJ~NK~NK~NK~NK~NK~NK~OL~OL~OL~OM~PM~PM~OM~PM~PM~PM~PM~QN~PM~PM~QN~QN~QN~QN~QN~QN~RO~SP~RO~SP~SP~TQ~TQ~TQ~TP~TR~TQ~UR~UR~UR~UR~UR~VS~VS~WT~WT~WT~WT~XU~XU~XU~XV~XU~YV~YV~YV~YV~YV~ZW~ZW~[X~[X~[X~\Y~[X~\Y~\Y~\Y~\Y~\Y~]Z~^Z~^Z~^[~^[~^[~_\~_\~_\~`]~`\~`]~a]~a]~a]~a^~a^~c_~b^~b_~c_~c_~d`~d`~d`~da~ea~da~fb~fb~fc~eb~fc~gd~gd~gd~gd~he~he~ie~if~if~if~jg~jg~kh~ki~ki~ki~kh~li~li~mj~mj~li~mj~mk~mk~nk~nk~ok~ok~ol~ol~pm~pl~qm~qn~qn~qn~qn~qn~ro~ro~ro~ro~sp~sp~sp~sp~tq~sp~ur~sp~tq~tq~ur~ur~ur~ur~vt~ur~wt~wt~wt~xt~xt~xt~xt~yu~yu~yu~zv~zv~zv~yv~zv~zv~zv~{x~zx~zv~zx~zv~zx~zx~|x~zv~zv~zx~zv~|x~|x~|x~|x~zw~zw~|x~zx~zx~zx~zx~zx~zw~zw~zw~zw~{x~|x~zx~zx~zx~|x~|x~{x~zx~|z~|x~|x~|x~zx~zx~|x~zx~zx~zv~zv~zv~zv~zv~zv~zv~zv~zv~zv~xv~xt~xv~xv~xt~xv~xt~xt~xv~xt~ws~ws~xt~xt~xt~vr~vr~vr~vr~vr~vr~vr~vr~vr~vr~uq~ur~tp~sp~tp~sp~sp~sp~sp~sp~so~so~ro~ro~qn~ro~qm~qm~qn~qn~pm~pm~pl~qm~pm~pl~ol~ol~ok~ok~nj~ok~nk~mj~mi~mi~mi~li~li~kh~kg~jg~jg~jg~jg~if~if~if~hd~he~he~hd~hd~gd~gd~fc~fc~fc~fb~ea~eb~fb~fb~ea~ea~ea~da~c`~c`~c`~b_~c`~c`~b_~b^~b^~a^~b^~a]~a]~`]~_\~`]~`\~_\~_\~_\~_[~^[~^Z~^Z~]Z~]Z~]Z~]Z~]Z~\Y~\Y~\Y~\X~[X~[X~[X~ZW~ZW~ZW~ZW~ZW~ZW~YV~YV~YV~XU~XU~XU~XU~XU~WT~WT~WT~WT~WT~WT~VS~VS~VS~US~US~US~US~UR~TR~TQ~TQ~TQ~TQ~SP~SP~SP~SP~SP~SO~RO~RO~RO~RO~RO~QO~QN~QN~QN~QN~QN~PM~PM~PM~PM~OL~OM~OM~OL~OL~OL~OL~NK~NL~NK~NK~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~LI~LI~LJ~LI~LI~LI~KH~KH~KH~KH~KH~KH~JG~JG~JG~JG~JG~JG~IF~JG~JG~IF~IF~IF~IF~IF~IF~IF~HE~IF~IF~HF~IF~HE~HE~GD~HE~HE~HE~HE~GD~GE~GE~GE~GE~GD~GD~GD~HE~GD~GD~GD~GD~GD~FC~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FB~FC~FC~FC~FC~GC~GC~FC~GC~FC~FC~FD~FC~FC~FD~FC~FD~FD~GD~GD~FC~FC~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~HD~HE~HD~HD~GD~HE~HD~HE~HE~HE~HE~HE~HE~HE~HE~HE~IE~HE~IE~IF~HE~IF~IE~JF~IE~IF~JF~JF~JF~JG~JF~JF~JF~JG~JG~JG~JG~JG~JG~JG~JG~KG~KG~JG~KG~KG~KH~KG~KG~KG~KG~JG~KH~KG~KG~KH~KH~LH~LH~KH~KH~KH~KH~KH~KH~KH~KH~KH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~MI~LH~LH~LH~LH~LI~LI~LH~LH~MI~LI~MI~LI~LI~MI~LH~LI~MI~LI~LI~LI~LI~LI~MI~MJ~MI~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MI~MJ~MJ~MJ~MJ~MJ~NK~MJ~MJ~MJ~NJ~NJ~MJ~NJ~NJ~NJ~NJ~NJ~OK~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~OK~NJ~NJ~NJ~NJ~NJ~NJ~OK~OK~NK~OK~NJ~NJ~NK~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NK~MJ~MJ~MJ~NK~MJ~MJ~MJ~NJ~NJ~NJ~MI~MI~MI~MJ~MI~MI~MI~MI~MI~MJ~MJ~MJ~MI~MI~MI~MI~MI~MJ~LI~MI~MI~MI~MI~LI~LI~LH~MI~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~KH~KH~LH~LH~LH~KH~KG~KG~KH~KH~LH~KH~KH~KH~KH~KH~KH~KG~KH~KH~KH~KH~JG~KG~KG~KG~JG~KG~JG~JG~JG~JG~JF~JG~JG~JG~JG~JG~JG~JG~JG~JG~JF~JF~JF~IF~IF~IF~IF~IF~IF~IF~HE~HE~IE~IE~IE~IE~IF~IE~IE~HE~HE~IE~HE~IE~HE~HE~HE~HE~HE~HE~HE~GD~GD~HD~GD~GD~GD~GD~HE~HE~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GC~FC~FD~GD~GD~FC~FC~GD~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~EB~FC~FC~EC~EC~EC~EC~FC~EC~FC~FC~FC~FC~FC~FC~FC~FC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GC~GC~GC~FC~FC~FC~GD~GD~GD~FC~FC~FD~FC~GD~FC~GD~GD~GD~GD~GD~GD~HE~GE~GD~HE~HE~HE~HE~HE~IF~HE~HE~IF~HF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IG~IF~JG~JG~JG~JG~KH~KH~KH~KH~JH~KH~KH~LI~KI~KI~KI~LI~LJ~LI~MJ~LI~MJ~MJ~MJ~MJ~MJ~MK~NK~NK~NK~NK~NK~NK~OL~OL~OL~OL~PM~OL~PM~OL~PM~PM~PM~PM~PN~PN~QN~QN~QN~QN~QN~RO~RN~RN~RO~RO~RP~SP~SP~SP~SP~SQ~SP~TQ~TQ~TQ~UR~UR~UR~UR~VS~VS~VS~VT~VS~WT~WT~XU~XU~XU~XU~XU~XU~YV~YV~YV~ZW~ZW~ZW~[X~[X~[X~\Y~\Y~\Y~\Y~]Z~\Y~]Z~^[~^[~^[~^[~^[~_\~_\~_\~`\~`\~`]~a]~a]~b^~a^~a^~b_~b_~c_~c_~d`~d`~d`~d`~d`~eb~eb~eb~eb~fc~fc~gc~fc~gc~hd~he~he~he~he~if~jf~if~jf~jf~jg~kg~kg~li~li~li~li~mj~li~mj~nk~mk~mk~mk~nl~ol~ol~pm~ol~pm~pm~qn~qn~qn~rn~rn~rn~ro~ro~sp~sp~tq~sp~sp~tq~tq~tq~tq~ur~ur~ur~ws~ws~ws~vr~vr~vr~xt~xt~xt~xt~xv~zv~zv~zv~zv~zv~zv~zv~{w~{w~{w~|x~{w~|x~|x~|x~|x~zx~zx~|x~|x~|x~|x~|x~|x~zx~|x~|x~|x~|z~|x~|x~}y~|x~|y~}z~|x~}z~|y~|y~|y~|y~|y~|x~|y~|y~|x~|z~}z~}z~}z~}z~~z~~z~}y~}y~|x~|x~|x~|x~|x~|x~|x~|x~zx~zx~zx~zv~zw~zw~zv~zv~xv~zv~zv~zv~yv~yv~yv~zv~zv~xv~xv~xt~xv~xv~wt~wt~wt~wt~wt~xt~vt~vt~ws~ws~vr~vr~vr~tp~tp~tp~tp~tp~tp~tp~sp~so~so~so~so~so~rn~rn~rn~qn~qn~qm~rm~pm~pl~pm~ol~ol~pl~ok~pl~ok~ok~ok~nj~nj~mi~mi~li~li~kg~kh~kg~kg~kg~jg~jg~jg~if~if~if~ie~hd~he~hd~gc~gc~hd~fc~fc~fc~fc~fb~fb~fb~eb~ea~da~da~da~da~da~c`~c_~c`~c_~b^~b_~b^~a^~b^~a^~a]~`]~`]~`]~`]~_\~_[~_\~^[~^[~^[~^Z~^Z~]Z~]Z~]Z~\Y~\Y~\Y~\X~[X~[X~[W~[W~[W~ZW~ZW~ZW~ZW~ZW~YV~YV~XU~XU~YV~WT~WT~XU~WT~WT~WT~WT~WT~VS~VS~VS~UR~UR~UR~UR~TR~TR~TR~TQ~TQ~TQ~SP~SP~SQ~SP~SP~RO~RO~RO~RO~RO~QO~QO~QO~QN~QN~QN~QN~PM~PM~PM~PM~PM~OM~OL~OL~OL~OL~OL~OK~NK~NK~NK~NK~NK~MJ~MK~MJ~MJ~MJ~LJ~LI~LI~LI~LH~LI~LH~KH~KH~KH~KH~KH~KH~KH~JG~JG~JG~JG~JG~JG~JG~JG~IF~IF~IF~IF~IF~IF~HE~IF~HE~IF~HE~IF~HE~HE~HE~HE~HE~HD~HE~HE~HD~HE~HE~GD~GD~HD~GD~GD~GD~GD~GD~GD~GD~GD~GC~GD~GD~GD~GD~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GC~GC~FC~FC~FC~GC~GC~GC~GC~FC~FC~FC~FC~FC~FC~FD~FC~FC~GD~FC~FC~FC~GD~GD~GD~GD~HE~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~HD~GD~HD~HD~HD~GD~HE~HE~HE~HE~HD~HE~HD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IE~IE~IE~IE~IF~IE~IF~JF~IF~IE~IE~JF~IF~JG~JG~JG~JG~JG~JG~JG~KG~KG~KG~KG~KG~KG~KG~KG~KG~KG~KG~KG~KG~KH~KG~KG~KH~KH~KH~KH~KH~KH~LH~LH~LH~LH~KH~LH~LH~LH~LH~LH~LH~LH~LI~LH~LH~LH~LH~LI~LH~LH~LI~LI~LH~MI~LI~LI~MI~LI~LI~MI~LI~LI~MI~MI~MI~MI~MI~MI~MI~LI~LI~MJ~MI~MI~MI~MJ~LI~MI~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~NK~NK~NJ~NJ~NJ~NJ~NJ~NJ~OK~NJ~NJ~NJ~NJ~NJ~NJ~NK~NK~NJ~NJ~NK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OL~NK~OK~NK~NK~NK~OK~OK~NJ~OK~OK~OK~NK~OK~OK~OK~OK~NK~NJ~OK~NJ~NJ~OK~NK~OK~OK~NJ~NJ~NJ~NJ~NK~NK~NK~NJ~NJ~NJ~NJ~MJ~MJ~MJ~MJ~MJ~MJ~MI~MI~MI~MI~NJ~MJ~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~LI~LI~LI~LH~LH~LH~KH~LI~LI~LH~LH~KH~LH~KH~KH~KH~LH~LH~LH~KH~LH~LH~LH~LH~LH~KH~KH~KH~LH~LH~KH~KH~KG~KH~KG~KH~KG~KH~JG~JG~KG~JG~JG~JG~JG~KG~JG~JG~JF~JF~JF~JF~JF~JF~JF~JF~JF~IF~IF~IF~IF~HE~IF~IF~IF~IE~IF~HE~HE~HE~IF~HE~IE~IE~IE~IE~HD~HE~HE~HE~HE~HE~HE~HE~GD~HD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GC~FC~FC~FC~FC~GD~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~EB~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FD~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FD~FD~FC~GD~GD~GD~GD~GD~FC~GD~GD~FC~GD~GD~GD~GD~GD~GD~GD~HE~GD~GD~HE~HE~HE~HE~HE~HE~HE~HE~HE~IF~IF~HF~IG~IG~IG~IF~IF~IF~JG~JG~JG~JG~JG~JG~KH~KH~KH~KH~KH~LI~KH~LI~LI~LI~LI~LJ~MJ~LI~LI~LI~MJ~MJ~MK~MJ~NK~MK~NK~NK~NL~NL~NK~OL~OL~OL~OL~OL~OL~OM~PM~OM~PM~PM~PN~QN~QM~QM~QN~RO~RO~RO~RO~RO~RO~RO~SO~SP~SP~TP~SP~SP~TQ~SQ~TR~TQ~TQ~UR~UR~UR~UR~UR~VS~WS~WT~WT~WT~WT~WT~WT~XU~XU~YV~XU~YV~YV~ZW~ZW~[X~[X~[X~[X~\Y~\Y~\X~\Y~\Y~]Y~]Z~^Z~^[~^[~_\~_\~_\~_\~_\~`]~`]~`]~`]~`]~a^~a^~b_~b_~b_~b_~d`~d`~c`~c`~da~da~da~ea~eb~fb~fb~fc~fd~gd~gd~gd~ge~he~he~hf~if~jf~jf~jf~jf~kg~kg~kh~kh~li~li~li~mj~mj~mj~mj~mj~mj~nj~nk~ol~pm~pm~pm~qn~qn~qn~qn~ro~ro~ro~ro~ro~sp~sp~sp~tq~tq~tq~tq~sp~ur~ur~ur~ur~ws~vr~vr~ws~wt~ws~ws~xt~xt~xt~xt~xt~xt~zv~zv~zv~{x~{x~zx~|x~|x~|x~|x~|x~|x~|x~|x~|z~|x~|z~|z~}z~~z~~z~~z~}z~|y~|y~|y~|y~}z~|y~~z~~z~~z~~{~~{~~{~|~~{~|~~{~~{~~{~~{~~{~~{~~{~~z~~{~}z~~{~~z~~{~~{~~{~~{~~{~~z~~z~|z~|z~}z~}z~|y~|y~}z~|y~|x~}z~{x~{x~zw~{x~{x~{x~zw~zw~zw~zw~zw~zw~zw~zv~zv~zw~zv~zw~yv~yv~yv~xu~xt~xt~wt~wt~wt~xt~xt~ws~ws~xt~vr~vr~vr~tr~ur~tr~tp~tp~tp~tp~tp~tp~tp~so~sp~so~so~so~so~rm~qm~ro~qm~qm~qm~ol~ol~pm~pl~ol~ol~ok~nj~nj~nj~nj~mi~mi~li~mi~lh~lh~kh~kh~jg~kg~jg~jg~ie~ie~ie~ie~he~hd~ie~he~gd~gd~gd~fc~fc~fc~fb~ea~fb~eb~da~da~d`~d`~c`~d`~c`~b_~b_~b_~b^~a^~b^~a]~`]~a]~a]~`]~_\~_\~_\~_\~_\~_[~_\~^[~^Z~^Z~]Z~]Z~]Z~\Y~\Y~\Y~\Y~\Y~[X~[W~[X~[X~ZW~ZW~ZW~ZW~ZW~YV~YV~YV~YV~XU~XU~XU~XU~WT~WT~WT~VS~WT~VS~VS~VS~UR~VS~UR~UR~TR~TQ~TQ~SP~TQ~TQ~SP~TQ~SP~RP~RP~SP~RO~QO~QO~QO~QO~QN~QN~QN~QM~QN~PN~PM~PM~PM~PM~PM~OL~OL~OL~OL~OL~NK~OL~NK~NK~NK~NK~NK~MJ~MJ~MJ~LI~MJ~LJ~LI~LI~LI~LI~KI~LH~KH~KH~KH~JG~JG~KH~JG~KH~JG~JG~JG~JG~JG~JF~JF~JF~JF~JF~IF~IF~IE~IF~IF~IF~IF~HE~HE~HE~HE~HE~HE~HE~HE~HE~HD~HE~GE~GE~GD~GD~GD~GD~GD~GD~GD~GD~GC~GD~GD~GD~GD~GD~GC~GD~GD~FD~GD~FC~FC~GC~GD~FC~GC~GC~GC~GC~FC~GC~GC~GC~FC~GC~GD~GC~GC~GC~GC~GC~GC~GC~GD~GD~GD~GC~GD~GD~FC~FC~FC~GD~FC~GD~GD~FC~FC~GD~FC~GD~GD~FC~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~GD~GD~GD~HD~HD~HD~HD~GD~HD~HD~HD~HD~HE~HE~HD~HE~HE~HE~HE~HE~IE~HE~HE~IF~IE~IE~HE~IE~IE~IE~IE~IF~JF~IE~IE~JF~IF~IF~IF~IF~IF~IF~IF~JG~JG~JG~JG~KG~KG~KG~JG~KH~KH~KG~KG~KG~KG~KG~KG~KH~KH~KG~LH~LH~LH~KH~KH~KH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~MI~LI~LI~MI~LI~LI~LI~LI~MI~LH~MI~MI~LI~LI~MI~MI~MI~MI~MI~MI~MI~MJ~MI~MI~MJ~MJ~MI~NJ~MJ~MJ~MJ~MJ~NJ~NJ~MJ~MJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NK~NJ~NJ~NK~NJ~NK~NJ~OK~OK~OK~OK~OK~OK~OK~OK~NJ~NJ~NJ~NK~OK~OK~OK~OK~OK~OK~OL~OL~OK~OK~OK~OK~OK~OL~NK~OK~OK~OK~OK~OK~OK~OK~PL~OL~OK~OL~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OL~OK~OK~OK~OK~NJ~NK~OK~OK~OK~OK~NJ~OK~NJ~NJ~NJ~NJ~NK~MJ~NJ~NJ~MJ~NJ~NJ~MJ~MJ~MJ~MJ~MJ~NJ~MI~MJ~MI~MI~MI~MI~MI~MJ~MI~MJ~MI~MI~MI~MI~MI~MI~LH~LH~MI~LI~LI~MI~LH~MI~LI~LI~MI~LH~LH~MI~LH~LI~LH~LH~KH~LH~LH~LH~LH~LI~LH~LH~LH~LH~LH~LH~LH~LH~LH~KH~KH~KH~KH~KH~KH~KH~KH~JG~KH~JG~JG~KH~KH~JG~KG~JF~JF~JG~JG~JF~JG~JG~JF~JF~JG~JF~IF~IF~IF~IF~IF~IF~IF~IF~IE~IF~IE~IE~IE~IE~HE~IE~HE~IE~IE~HE~HD~HE~HE~IE~HE~GD~HE~HE~HE~HE~GD~GD~GD~HE~HE~HE~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GC~GD~GC~GC~GD~GC~GC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~GD~FD~FD~FC~FD~FD~FD~FC~GD~FC~GD~FC~GD~GD~GD~GD~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~HE~GD~GD~GE~HE~HE~HE~HE~HE~HE~HE~HE~IE~HE~HE~IF~IF~IF~IG~IG~JG~JG~IG~JG~JG~JG~JF~JG~JG~KH~KH~JH~KH~KH~KH~KH~KH~KH~LI~KH~LI~LI~LI~LI~LI~MJ~MJ~MJ~MJ~MK~NK~NK~NK~NK~NK~OL~NL~OL~NL~OL~OL~OL~PM~PM~PM~PN~PM~PM~PM~PM~PN~PN~QM~QN~RO~RO~QO~RO~RO~RO~SP~SP~SP~SP~SP~SP~TQ~TQ~TQ~UR~TQ~UR~UR~TQ~UR~US~VS~VS~VS~WT~WT~WT~WT~XU~XU~YV~YV~YV~YV~ZW~ZW~ZW~ZW~[X~[X~\Y~[X~\Y~\X~]Y~\Y~]Z~]Z~]Z~^[~^[~^[~^[~_[~_\~_\~`]~`]~`]~`]~a]~a^~a^~b_~b_~b_~c_~c_~d`~d`~da~da~da~ea~da~eb~eb~fb~fc~fc~gd~gd~gd~gd~he~he~if~if~if~jf~jg~if~jf~kg~kh~kh~kh~li~mi~mi~mi~mi~nj~nk~nj~mj~nk~nk~ol~ol~pm~pm~qn~ro~ro~ro~ro~ro~ro~sp~ro~sp~tq~tq~tq~ur~ur~ur~ur~ur~ur~ws~vr~ws~ws~wt~xt~xt~xt~xt~xv~zv~zv~zv~zv~zv~zv~zv~|x~|x~|x~|x~|x~|x~|x~|y~}z~}z~}z~}z~}z~}z~}z~}z~}z~~|~~{~~z~~{~~z~~{~|~~{~|~~{~~{~~{~|~|~|~}~|~}~}~|~|~|~|~|~~{~~{~~{~|~}~|~|~|~|~|~|~}~|~|~~{~}z~}z~}z~|~~{~~{~}z~}z~}z~}z~|y~|y~|y~{x~{x~{x~{x~{x~{x~{x~zw~{x~{x~{x~{x~{x~{x~{x~zw~zw~zw~yv~yv~yv~yv~yv~xu~xu~xu~xu~xt~xt~xt~wt~vr~vr~vr~vs~tq~vr~tr~tr~tp~tp~uq~uq~uq~tp~tp~tp~so~so~so~so~rn~rn~rn~rn~rn~qm~pm~pm~pl~ol~ok~ok~nk~nk~nk~mj~mj~mi~mi~li~kh~li~kh~kh~jg~kg~jg~kg~jf~jf~if~if~he~he~hd~he~gc~gc~gd~gc~fc~fc~fc~fb~ea~ea~ea~da~da~ea~c`~c`~c`~b_~c`~b_~b_~b^~a]~a^~a^~b^~a]~`]~`]~`]~_\~_\~_\~_[~^[~^[~^Z~^Z~]Z~]Z~]Y~\Y~\Y~\Y~\Y~\Y~\Y~[X~[X~[W~[W~ZW~ZV~YV~YV~YV~YV~YV~YV~XU~XU~XU~XT~WT~WT~WT~WT~VS~WT~VS~VS~US~UR~UR~UR~UR~UR~UQ~TR~TQ~TQ~SQ~SP~SP~SP~RO~RP~RP~RO~RO~QO~QO~QO~QN~QN~QN~QN~PM~PM~PM~PM~PM~PM~OL~OL~OL~NK~NK~NK~NK~NK~NK~NK~NK~NK~MJ~MJ~MJ~MJ~LJ~LJ~LI~LI~LI~LI~LI~LI~KH~KH~KH~KH~KH~JG~JG~JG~JG~JG~JG~JG~JF~JG~JF~JG~IF~IF~IF~IF~IF~IF~IF~IF~IF~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~GD~HD~GD~GD~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GC~GD~GD~GD~GD~FC~GD~GC~GD~GC~GD~GD~GD~GC~GC~GD~GD~GD~FC~FC~GD~GD~GD~GD~FC~GD~GD~GD~GD~GD~FC~GD~GD~HD~GD~GD~GD~GD~GD~GC~GD~GD~GC~GD~HD~HD~HD~HD~HD~HD~GD~GD~HD~HD~GD~HE~HE~HE~GD~GD~HE~HE~HE~HD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IE~IE~IE~IE~IF~IE~IF~IE~IE~IF~IF~JF~JF~JF~IF~IF~IF~JG~JG~JG~JG~JG~JG~KG~JG~KG~KG~KG~KG~KH~KH~KH~KH~KH~KH~KG~KH~LH~KH~KH~KH~KG~LH~LH~LH~LH~LH~LI~MI~LI~LH~MI~LI~MI~LI~MI~LH~MI~MI~MI~MI~MI~LI~LI~LI~LI~LI~LI~LI~LI~MJ~LI~MI~LI~MI~MI~MJ~MI~MJ~MJ~MJ~MJ~MJ~NJ~NJ~NJ~MI~NJ~NJ~MJ~NJ~NJ~NJ~NJ~NJ~NK~NJ~NK~NK~NK~OK~NK~NK~NJ~NJ~NK~NK~NK~NK~NK~OK~OK~OK~OK~OK~OK~NK~OK~OL~OK~OK~OK~OK~PL~PL~PL~OL~OK~OK~OK~OL~PL~OK~OK~OK~OK~PL~OL~PL~PL~OL~OK~OK~OL~PL~OL~OL~PL~OL~PL~OL~OL~PL~OK~OL~OK~OK~OK~PL~OL~OK~PL~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~NK~OK~NJ~OK~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NK~NK~NK~MJ~NJ~NJ~NJ~NJ~NJ~MI~MI~MJ~MJ~MJ~MJ~NJ~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~LI~MI~MI~MI~MI~MI~MI~LH~LH~MI~MI~LI~LI~LI~KH~LI~LI~LH~LH~LH~LH~LI~LH~LH~LH~LH~LH~LH~LH~LH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~JG~KG~KG~JG~JG~KG~KG~KG~JF~JG~JG~JF~JF~JF~JG~JF~JF~JF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~HE~IE~IF~IF~HE~HE~HE~HE~HE~HD~HE~HE~HE~HE~HE~HD~HE~HE~HE~GD~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GC~GC~GD~GC~GD~GD~GD~FC~FC~GC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~FC~GD~FC~GD~FC~FC~FC~GD~FC~FC~FC~GD~GD~FC~GD~GD~FC~FC~GD~GD~GD~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~HE~HE~GE~HE~HE~HD~HD~HD~HE~HE~HE~HE~HF~HE~HE~HF~HF~IF~IF~IF~IF~IF~IF~IF~IF~IF~JG~JG~JG~JG~JG~JG~JG~KG~KH~KH~KH~KH~LH~KH~KH~LI~LI~LI~LI~LI~LI~LI~MJ~MJ~MJ~MJ~MK~NK~NJ~NJ~NK~NK~OL~OL~OL~OL~OL~OL~OL~OL~PM~PM~PM~PM~PM~PM~PM~PM~PM~QN~QN~QN~QN~RO~RO~RP~RO~RP~RP~RP~SP~SP~TQ~TQ~TQ~TQ~UR~TQ~UR~UR~UR~VS~VS~VS~VT~VT~WT~WT~WT~WT~XU~XU~XU~YV~YV~YV~YV~ZW~ZW~ZW~ZW~ZW~[X~[X~\Y~[X~]Y~]Y~]Z~]Z~]Z~]Z~]Z~^[~^[~^[~_[~_\~`\~`\~`]~`]~a]~a^~a^~b_~b_~c_~c_~c_~d`~c`~da~da~da~eb~ea~fb~fb~fc~gc~gc~gd~gd~gd~gd~he~ie~ie~jf~jf~jf~kg~kg~jg~jg~kh~lh~mi~lh~mi~mj~mj~nk~nk~nk~nk~nk~ol~ol~ol~ol~pm~qm~qm~qn~ro~ro~rn~ro~sp~ro~sp~tq~uq~tq~tq~uq~vr~vr~ws~wt~wt~wt~wt~wt~xt~xt~xt~xt~xt~xv~xv~zv~zv~zv~zv~zv~zv~|x~|x~|x~|x~|x~}z~|y~~z~~z~~{~~{~~{~~{~}z~~{~|~|~~{~|~|~|~}~|~|~|~}~}~}~|~}~}~}~~~}~~~~~~~}~}~~~~~}~}~}~}~}~}~~~~~~~~~~~~~|~}~}~|~|~|~|~|~|~|~|~|~}z~~{~~{~}z~}z~}z~}z~}z~|z~}y~}z~}z~}z~}z~}z~}z~}z~}z~}z~}z~|y~|y~|y~{x~{x~{x~zw~{x~zw~zw~zw~zw~zw~zw~zw~xu~xu~xu~xt~xt~xu~vs~vs~wt~vr~vr~ur~vr~vr~vr~vr~uq~tp~tp~tp~tp~tp~tp~so~so~so~so~rn~rn~rn~qm~pm~pm~pm~ol~pl~ol~ol~ol~nk~nk~nk~mj~nj~lh~mi~li~lh~kh~lh~kg~kg~kg~jf~jf~jf~if~if~he~he~ie~gd~gd~hd~gc~gd~fc~fc~fc~fc~eb~ea~ea~ea~da~d`~c`~da~c`~c`~b_~b_~b_~b_~a^~a^~a]~a^~a]~a]~`\~_\~_\~_\~_[~_\~^[~^[~^Z~^Z~^Z~]Y~]Y~\Y~\Y~[X~[X~\Y~[X~[X~[W~[W~ZW~YV~ZW~YV~YV~YV~XU~XU~XU~WT~XU~WT~XU~WT~WT~WT~VS~VS~VS~VS~VS~UR~UR~TR~UR~UR~TQ~TQ~TQ~SP~SP~SP~SP~RP~RP~RP~QO~QO~QO~QN~QN~QN~QN~QN~QM~QN~QN~PM~PM~OL~OL~OL~OL~OL~NL~OL~OL~NK~NL~NK~NK~NK~MJ~MJ~MJ~MJ~MJ~LI~LI~LI~LI~LH~LI~LI~LI~LI~KH~KH~KH~KH~KH~KH~JG~KH~JG~JG~JG~JG~JG~JG~IF~IF~IF~JF~JF~IF~IF~IF~IF~IF~IE~IF~HE~HE~HE~IF~HE~HE~HE~HE~IF~HE~HE~HE~HE~HE~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~GD~GD~GD~GD~GD~GD~GC~HD~HD~GD~HD~HD~GD~GD~HD~HD~HD~HD~HD~HD~HD~HE~HE~HE~GD~GD~GD~HD~HD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IE~IE~IE~IE~IF~IF~HE~IF~JF~JF~IF~JF~IF~IF~IF~IF~IF~IF~IF~JG~JG~JG~JG~JG~JG~KG~KG~KG~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KG~KH~LH~LH~LH~LH~LH~LH~LI~LH~LH~LH~LH~LH~LH~LH~LI~MI~MI~MI~LI~LI~MI~MI~MI~MI~MI~MI~MI~MI~MJ~MJ~LI~MI~MI~MJ~NJ~NJ~NJ~NJ~MI~MI~NJ~MJ~NJ~NJ~NJ~NJ~NJ~NJ~OK~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NK~NK~NK~OK~OK~NK~NK~OK~OK~NK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OL~OK~OL~NK~OK~PL~OL~PL~PL~OK~PL~PL~OK~OK~OK~PL~OK~OK~PL~PL~OK~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PM~PM~PL~PL~PL~PL~PL~OL~PL~PL~PL~OK~PL~OK~PL~PL~OL~PL~OL~OK~OK~OK~OL~OL~OK~OK~PL~OK~OK~OK~OK~OK~OK~OK~OK~OK~NJ~NK~OK~NK~NK~OK~NJ~OK~OK~NJ~NJ~NK~NK~OK~NJ~NJ~NJ~NJ~MJ~NJ~NJ~NJ~NJ~MJ~MI~MJ~MI~MI~MJ~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~LI~LI~MI~LI~MI~MI~MI~LH~LI~LH~LH~LI~LI~LH~KH~LI~LH~LH~LH~LH~LH~LH~KH~KH~KH~KH~LH~KH~KH~KG~KH~KH~JG~JG~JG~JG~KG~KG~JG~JF~JG~JF~JF~JF~JF~JF~JF~JF~IE~JF~IF~IF~IF~IF~HE~IF~IF~IF~IF~IF~IF~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HD~HD~HE~HE~HE~HE~GD~HE~HE~GD~GD~HE~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GC~GC~GD~GD~GD~GD~FC~FC~FC~FC~GD~GD~GD~GD~FC~GD~GD~GD~FC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~GD~GD~GD~GD~GD~HD~HE~HD~HE~HE~HE~HE~HE~HE~IF~IF~HF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~JG~KG~JG~KG~JG~JG~KG~JG~KH~KI~KH~KI~KI~LI~LI~KH~LI~LI~LI~LJ~LJ~MJ~MJ~MJ~MJ~NJ~NK~NK~MJ~NK~NK~NK~NK~OL~OL~OL~PM~PM~PM~PM~PM~PM~QN~PN~QN~QM~QM~QM~QN~RO~QN~RO~RO~RO~RO~RP~SP~SP~RP~SP~TQ~TQ~TQ~TQ~TQ~UR~TR~UR~UR~UR~UR~VS~VS~VS~WT~WT~WT~WT~XU~XU~XU~XU~XU~XU~YV~YV~ZW~ZW~ZW~ZW~[X~[X~[X~\X~\Y~]Y~]Y~]Z~]Z~]Z~^Z~^Z~_[~_[~_[~`\~`\~`]~`]~a^~a^~b^~b^~b_~c_~c_~c_~c_~d`~d`~da~da~eb~eb~fb~fb~fb~fb~fc~gc~gc~gd~gd~he~ie~ie~ie~ie~if~jg~jg~jg~kh~kg~lh~kh~li~li~mi~mj~nj~nj~ok~nk~ol~ol~pm~ol~ol~pm~ol~pl~qm~qm~qn~ro~ro~so~ro~sp~so~sp~sp~uq~ur~ur~vr~vr~vt~xt~xt~xt~vt~xt~xt~xt~xv~xv~yv~xt~xv~zv~zx~zx~{x~{x~{x~{x~|y~|y~|y~|y~}z~|~~{~~{~|~|~|~|~|~|~|~|~|~|~|~}~~~~~~~~~~~~~}~|~~~~~A?~~A?A?@?@?A?A?@?A?A?@?@?@?A?@?@?@?@?@?~~A?@?@?~~}~@?@?@?@?}~}~|~|~|~|~|~|~|~|~|~|~|~~{~|~}z~~{~~{~~{~~{~}z~}z~}z~}z~}z~}z~}z~}z~|y~|y~{x~{x~{x~{x~{x~|y~{x~|y~zw~zw~yv~yv~yv~yv~xu~xu~xu~xu~xu~xu~wt~wt~vs~vs~vr~vr~vr~vr~vr~vr~vr~uq~uq~tp~tp~tp~sp~tp~so~rn~so~ro~qm~qm~qm~ol~pl~ol~ol~ol~ol~nl~nk~nk~nj~nj~nj~mi~mi~mi~li~kh~kh~jg~kh~kg~kg~jg~jf~if~if~ie~ie~he~he~hd~hd~gd~gc~fc~fc~fc~fc~eb~eb~ea~da~da~da~da~d`~d`~b_~b^~b_~b_~a^~a]~a^~a]~a]~`]~`\~`]~_\~_\~_\~^[~^[~^Z~^Z~^Z~^Z~]Z~]Y~]Y~\Y~\Y~\X~\X~[X~[X~[W~ZW~ZW~YV~YV~YV~YV~YV~YV~XU~XU~XU~WT~XU~WT~WT~WT~VS~VS~VS~VS~VS~VS~UR~UR~UR~UQ~UQ~TQ~TQ~SQ~SQ~SQ~SP~SP~RP~RP~RP~RO~RO~RO~QN~QO~QO~QN~QN~QM~PM~PM~PM~PM~PM~OM~OL~OL~OL~NL~OL~NK~NK~NK~NK~MJ~MJ~NK~MJ~MJ~MJ~LI~LI~LI~LI~LI~LI~LI~LI~LI~KH~LI~KH~KH~KH~JH~KH~KH~JG~JG~JG~JG~JG~JG~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~GD~HE~GD~HE~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HE~GD~GD~GD~GD~GD~GD~GD~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~GD~GD~GD~GD~GD~HD~HD~HD~HD~HD~HD~HD~HD~HD~HD~HD~HD~GD~HD~GD~GD~HE~HD~HD~HD~HD~HD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IF~IF~IF~IF~IE~IE~IE~IE~IE~JF~IE~IF~IF~IF~IF~IF~IE~JF~JF~JF~IF~JG~IF~JG~JG~JF~JG~JG~KG~KG~KG~KG~KG~KG~KG~KG~KH~KG~KH~KH~LH~KH~KH~LH~LH~LH~LH~LH~LH~LI~LI~LH~MI~LI~MI~MI~MI~LI~LI~LI~LI~MI~MI~MJ~MI~MJ~MI~MI~MI~NJ~MI~NJ~NJ~NJ~NJ~MJ~NJ~NJ~NJ~MI~NJ~NJ~NJ~NJ~NJ~NK~NJ~NJ~NK~OK~NJ~NK~NJ~NK~NJ~OK~OK~NK~OK~NJ~NK~NK~NK~OK~OK~OK~OK~OK~OK~OL~OL~OK~OK~OK~OK~OK~OK~OK~OK~PL~PL~PL~PL~PL~PL~PL~OK~OK~PL~PL~PL~PL~PL~PL~OK~PL~PL~PL~OL~PL~PL~PL~PL~PL~PL~PM~PL~PL~QM~QM~QM~PL~QM~QM~PM~PM~PM~PM~PM~PM~QM~PM~PM~PL~PL~PL~PL~PL~PM~PL~PL~PM~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~OL~PL~PL~PL~OK~OK~OK~OK~OK~OL~PL~OK~OK~OK~OL~OK~OK~OK~OK~OK~OK~OK~OK~NK~NJ~NJ~OK~NJ~OK~NJ~OK~NJ~NJ~NJ~NJ~NJ~NJ~NJ~MJ~MJ~MI~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MI~MI~MJ~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~LI~LI~LI~LI~LI~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~KH~LH~KH~KH~KH~LH~JG~KG~KH~JG~JG~KG~JG~JG~JG~JF~JG~JF~JF~JG~JF~JF~JF~JF~JF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~HE~HE~HE~IE~HE~IE~HE~HE~IE~IE~IE~HE~HD~HE~HE~HE~HD~HD~HD~HE~HE~HE~GD~GD~GD~HE~HE~HE~GD~GD~GD~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GC~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HD~HD~GD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IF~IF~IE~IF~IF~IF~IF~IF~IF~IF~IF~JG~JG~IF~JG~JF~JG~JG~JG~KG~KG~KG~KG~KH~KH~KH~KH~KI~KH~KI~LI~LI~LI~LI~MJ~MJ~LI~MJ~MJ~MJ~MJ~MJ~MJ~NK~NK~MK~NK~NK~NK~NK~OL~OL~OL~PL~PM~PM~PM~PM~PM~PM~PM~QN~QM~QN~QN~RO~RO~RO~RO~RO~RO~RO~SP~SP~SP~SP~SP~SP~TQ~TQ~TQ~TQ~UR~UR~UR~UR~US~VS~VS~VS~WT~WT~XU~WT~XU~XT~XU~XU~XU~YV~YV~YV~ZW~ZW~ZW~[X~[X~[X~[X~\Y~\Y~]Z~]Z~]Z~]Z~^[~^[~^[~^[~_\~_\~`]~_\~`]~`]~a^~a^~a^~a^~b^~b^~b_~c_~c`~d`~d`~da~da~ea~fb~fb~fc~gc~fc~fc~gc~gd~gd~he~he~he~ie~if~if~if~jf~jg~jg~kh~kh~kh~li~li~mi~mi~mj~nj~nj~ok~ok~ol~pm~ol~pm~pm~pm~qn~pm~qm~qm~qm~qn~so~so~sp~sp~tq~uq~sp~ur~ur~ur~vr~vr~vr~xt~xt~xt~xv~xv~xt~yv~yv~xt~yv~yv~yv~zw~{x~{x~{x~{x~{x~{x~}z~}z~}z~}z~}z~~{~|~|~|~|~|~}~|~}~}~~}~~~~~@?@?@?A?@?@?@?@?@?A?A?A@A@A@A@A@A@B@A@A@B@B?A?B@B@A?A@A?A?A?A?A?B@A@A@A@A@A@A@A@A@A@A?@?@?@?@?@?@?@?@?@>@?@>|~}~|~|~|~|~|~@?|~|~|~}~|~~|~~|~~{~|~~{~}z~{~}z~}z~}z~}z~}z~}z~}z~}z~{x~{x~{x~{x~zw~zw~zw~zw~yv~yv~xu~xu~xu~xu~wt~wt~xt~ws~xt~vs~xt~xt~vs~vs~vr~vr~vr~vr~uq~tp~uq~so~so~rn~rn~rn~rn~rn~qm~qm~qm~pl~pl~qm~ok~ok~ok~ok~nj~nj~nj~mi~mj~li~li~li~li~lh~kh~kh~kg~kg~kg~if~if~if~he~he~he~he~he~hd~gc~gc~gd~fc~eb~fb~eb~eb~ea~ea~da~d`~d`~c_~c_~c_~b_~b_~b_~a^~a]~a]~a]~`\~`\~`\~`\~^[~_\~^[~^[~^[~^Z~]Z~]Y~]Y~]Y~]Y~\Y~\Y~\Y~\Y~[X~[X~[W~ZW~YV~ZV~YV~YV~YV~YV~YU~XU~XU~XU~XU~XU~WT~WT~VS~VS~VS~VS~UR~UR~VS~VS~UR~TQ~UR~UR~TQ~TQ~SQ~SQ~SP~SP~RO~SP~RO~RO~RO~QO~QN~QO~QN~QN~PN~QN~QM~PM~QM~PM~PM~PM~OL~PM~OL~OL~OL~OL~NK~NK~NK~MJ~NJ~NK~NK~MJ~MJ~MJ~MJ~MJ~LI~LI~LH~LH~LI~LI~LI~LH~LH~KH~KH~KH~KH~KH~JG~JG~JG~JG~JG~JG~JG~JG~JG~IF~IF~IF~IF~IF~IF~IF~IF~HE~IF~IE~IE~HF~IF~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~GD~HD~HD~GD~HE~HE~GD~GD~HE~GD~HD~HD~GD~GD~GD~HE~GD~GD~HD~GD~GD~HD~HD~GD~HE~GD~HD~HD~HD~GD~HD~HD~HD~HD~HD~HE~HD~HD~HD~HD~HD~HE~GD~HE~HD~HD~HE~HE~GD~GD~HE~HE~GD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IE~IE~IE~IE~IE~IE~IF~IF~IF~IF~IE~IE~IE~IE~IE~IF~IE~IF~IF~IF~IF~IF~IF~JG~JG~IF~JG~IF~JF~JG~KG~KG~KG~KG~KG~KG~KG~KG~KG~KG~KH~LI~KH~LH~LH~LH~LH~LH~LH~LH~LH~LH~MI~LH~MI~LI~MI~LH~MI~MI~MI~MI~MI~MI~MI~MI~MJ~MJ~MJ~MJ~MI~MI~MI~NJ~MI~NJ~NJ~NK~NJ~NJ~NJ~NK~NJ~NK~NJ~NK~OK~NJ~OK~NK~NK~NK~NK~NK~OK~NK~OK~OK~OK~OK~OK~NK~OK~OK~OK~OK~OK~OK~OK~OK~OK~PL~OK~OK~OK~PL~PL~PL~PL~OK~OL~PL~PL~PL~PL~PL~PL~PL~OL~PL~PM~PM~PL~PL~PL~PM~PM~PL~QM~PM~QM~PM~PL~PL~PL~PL~QM~PL~PM~QM~PM~QM~QM~QM~QM~QM~QM~QN~PM~QM~QN~QN~QM~QM~QM~QM~QM~QM~PL~PM~PL~PL~PM~PM~PM~QM~PL~QM~PL~PL~QM~QM~QM~QM~PL~PL~PL~PL~PL~QM~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~OK~PL~OK~PL~PL~PL~OL~OK~OL~OK~OL~NK~OK~OK~OK~PL~OK~NK~NJ~NJ~NK~NJ~NJ~NJ~NJ~NJ~OK~OK~NJ~NJ~NJ~NJ~NJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MI~MJ~MI~MJ~MI~NJ~MI~MI~MJ~MI~MI~MI~MI~MI~MI~LI~LI~LH~LH~KH~LI~LH~LI~LI~LI~LI~LI~LH~LH~LH~LH~LH~KH~LH~LH~KG~KG~KG~KG~KG~KG~JG~JG~JG~JG~KG~JF~JF~JF~JF~JG~JF~JF~JF~JF~IF~IF~JF~IF~IF~IF~JF~IE~HE~HE~IF~IF~IF~IF~HE~IF~IE~IE~IE~HE~HE~HE~HE~HD~IE~HD~HE~HE~HD~HD~HE~HE~GD~HE~HE~GD~GD~GD~GD~GD~GD~GD~HE~GD~GD~GD~GD~GD~GD~GD~GD~GD~GD~HE~GD~GD~GD~GD~HD~GD~GD~GD~GE~GD~GD~GD~GD~HD~HD~GD~GD~HD~GD~GD~GD~GD~GD~GD~GD~GD~GE~GD~HD~GD~HE~GE~GE~HE~HE~HE~HE~HE~HE~HE~HD~HE~HE~HE~HE~HE~HE~HE~HE~HF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~JG~JG~IF~JG~JG~JG~JG~JG~JG~KG~JG~KH~KH~KH~KH~KH~LI~KH~KH~LI~LI~LI~LI~LJ~LJ~LJ~MJ~MJ~MJ~MK~MK~MJ~NK~NK~NK~NL~NK~NK~NK~OL~OL~OM~OL~OL~PM~PN~PM~PM~PM~PM~QN~QN~RO~QN~QO~RO~QO~RO~RO~RP~SP~SP~TQ~SP~SP~TQ~TQ~TQ~TQ~TR~UR~UR~UR~UR~UR~UR~VS~VS~WS~WT~XU~WT~WT~WT~XU~YV~XU~YV~YV~YV~YV~ZW~[W~[W~[X~[X~\X~\Y~\Y~]Z~\Y~]Z~]Z~]Z~^[~^[~_\~_\~_\~_\~`]~`]~`]~`]~a^~a^~b^~b^~c_~b_~d`~c_~c`~da~c`~da~eb~eb~eb~fb~fb~fc~fc~gd~gd~hd~hd~he~he~ie~if~jf~jf~jf~jf~kg~kg~lh~lh~lh~lh~mi~mi~mj~mj~nj~nj~nj~ok~ok~pl~pl~qm~qm~qn~qn~qn~qn~qn~rn~so~rn~tp~tp~sp~tp~tp~vr~vr~vr~vr~vr~ws~xt~xt~xt~xt~xt~zv~zv~zv~zv~zw~{x~zw~{x~{x~{x~}z~|y~}z~}z~}z~}z~}z~}z~}z~~{~{~|~|~~~~~~~~~~~@?@?@?@>A?A?A?A?A?@?A@A@A?B@B@A@A@B@B@B@BAB@BABABABABACACAB@B@B@B@B@B@B@B@B@B@B@B@BAB@CACABABAB@A@A@A@BAA@A@A@A@A?A?@?A?A?A?@?@?@?@?@?A?@?@>@?@?@?@?@?@>@>|~|~@>|~|~|~{~{~|~|~~{~}z~}z~}z~}z~{x~{x~{x~{x~{x~yv~yv~yv~yv~yv~yv~yv~yv~xu~xu~xu~xu~xu~xt~xu~xt~vs~vs~vr~vr~vr~vr~vr~vr~tp~tp~tp~rn~so~rn~rn~rn~qn~qm~qm~qm~pl~pl~pl~ok~ol~ok~ok~ok~nj~nj~nj~mj~mi~li~mi~lh~lh~kg~kg~jg~jg~jg~if~if~if~ie~hd~hd~hd~hd~gd~gd~gc~fc~fc~fb~eb~ea~da~d`~d`~da~c_~c_~b_~c_~b_~b_~a^~b^~`]~a^~a]~`]~`]~`\~`\~`\~_[~_[~^[~^[~^[~]Y~^Z~]Z~]Z~]Y~\Y~\Y~[X~[X~[X~[W~ZW~ZW~ZW~ZV~YV~YV~YV~YV~YV~XU~YV~XU~XU~XU~WT~WT~VS~VS~VS~UR~UR~VS~UR~UR~UR~TQ~TR~TR~TQ~TQ~SP~SP~SP~SP~SP~SP~RO~RO~RO~RO~RO~RO~QN~QN~QN~QN~PM~QM~QM~PM~PM~PM~OM~OL~OL~OL~OL~NK~OL~NK~NJ~MJ~NK~MJ~MK~MK~MJ~MJ~MJ~MJ~LI~LI~LI~LI~KH~LI~LI~LI~KH~KH~KH~KH~KH~KH~JG~JG~JG~JG~JG~JG~IF~JG~JG~JG~IG~JF~IF~IF~IF~IF~IF~IE~IF~IF~IF~IE~IF~HE~HE~IE~IE~IF~IE~IE~HE~HE~HE~HE~HE~HE~HE~HE~HD~HE~GD~HE~GD~HD~GD~HD~GD~GD~HE~HD~HD~HD~HD~HE~HD~HD~HD~HD~GD~GD~HD~HD~HD~HD~HE~HE~HD~HD~HD~HE~HE~GD~HE~GD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~GD~HE~HE~HE~HE~HE~HE~HE~HE~IE~IE~HE~IE~IF~HE~IE~IE~JF~IE~IE~IE~JF~IF~IF~IF~IF~IF~IF~JF~IF~JF~IF~JG~JG~JF~JF~JF~JG~JF~JF~JG~KG~JG~KG~KG~KG~KG~KG~KG~KG~KG~KH~KH~LI~KH~LI~LI~LH~LH~LH~LH~LH~LH~LI~LI~LI~MI~MI~MI~MI~MI~MI~MI~MI~MJ~MI~MI~NJ~NJ~MI~NJ~NJ~NJ~NJ~NJ~OK~NJ~NJ~NK~NJ~NJ~NJ~OK~OK~OK~OK~NJ~OK~OK~NK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~PL~PL~PL~PL~PL~OK~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PM~PL~PM~PL~PL~PL~PM~PL~QM~QM~PL~PL~PM~PM~PM~QM~QM~PM~QM~QM~QM~QM~QN~QM~QM~RN~QM~PL~QM~QM~QM~QN~QN~QM~QM~QM~QM~QM~QM~QM~RN~QM~QM~QM~RN~QM~RN~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~PL~PL~PL~PL~QM~PM~QM~PM~QM~QM~QM~PL~PL~PL~PL~PM~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~OK~OK~OK~OK~OK~OK~OK~OL~OK~OK~OK~OK~OK~NK~NJ~OK~OK~NJ~NJ~NJ~NJ~OK~OK~NK~NJ~NJ~NJ~NK~NJ~NJ~NJ~MJ~NJ~MJ~MI~NJ~NJ~MI~NJ~NJ~NJ~MI~MI~MI~MI~MI~MJ~MI~MI~LI~LH~MI~MI~MI~MI~LH~LI~LI~LI~LI~LI~LI~LH~LI~KH~LI~LH~LH~LH~LH~KH~KH~KG~KG~KH~JG~KG~KG~JG~JG~JG~JG~JG~JG~JF~JF~JG~JF~JG~JF~JF~JF~JF~JF~IF~IF~IF~IF~IF~IF~IF~IF~HE~IF~IF~IF~IF~HE~HE~HE~IE~HE~HE~IE~IE~HE~HD~HE~HE~HE~HE~HE~IE~HE~HD~HE~HE~HE~HE~HE~HE~HE~GD~HE~HE~HE~GD~HE~HE~HE~GD~GD~HE~GD~HE~GD~HE~GD~GD~HE~GD~HE~GD~HD~HE~HE~HD~HD~HD~GD~HD~HD~GD~GD~GD~GD~GD~GD~GD~HE~GE~GE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IF~IF~HE~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~JF~JF~JF~JG~JG~JG~JG~KH~JG~KH~JG~KG~KH~KH~KH~KH~KH~LI~LI~LI~LI~LI~LI~LI~MJ~MJ~LI~MJ~MJ~MJ~NK~NK~NK~NK~NK~NK~OL~OL~OK~NL~OL~OL~OM~OM~PM~PM~QN~QN~PM~PM~QN~QN~QN~QO~QN~QN~RO~RO~RO~RO~SP~SP~SP~SP~SP~SP~SQ~TR~TQ~UQ~UQ~UR~UR~US~US~VS~VS~VS~WT~WT~WT~WT~XU~XU~XU~YV~YV~YV~YV~ZW~ZW~[X~[W~ZW~[W~[W~[X~\Y~\Y~\Y~\Y~^[~^[~^[~^[~_[~_[~_\~_\~_\~`]~`]~a]~a^~a^~a^~b^~b^~c_~c`~c_~c_~da~da~d`~da~eb~eb~fb~fc~fb~gc~gd~gd~gd~he~he~ie~ie~ie~if~jf~jg~jg~jg~kh~kh~lh~lh~mi~mi~mi~mi~nj~nj~nj~nj~ok~ok~pl~pm~rn~qm~qm~qm~rn~ro~ro~rn~rn~rn~tp~tp~tp~tp~tp~vr~vr~vr~vr~vr~vr~vt~xt~xt~wt~xt~yv~zw~zw~zw~zw~{x~{x~{x~}z~}z~|y~}z~}z~}z~}z~~z~|~{~|~|~|~|~@>@>@?}~@?@?A?A@A?A@A@A@A?A@B@B@B@B@B@B@B@BACAB@B@CACACACACACACACBCACACACACACACACACACACABAB@CACACACACACBCACABABABABACACACABAB@BAA@B@A@A@A@A@A?B@A@A@A@A?A?A?A?A?A?A?A?@?@?@>@>A?@?@?@?@?@>@>|~|~{~~z~~{~}z~~z~~z~~z~|x~|x~{x~{x~{x~zx~yv~zw~yv~yv~yv~yv~yv~yv~xu~xu~xu~xu~xu~xu~xu~vs~ws~vr~vr~ur~vr~vr~tp~tp~tp~tp~rn~rn~rn~rn~rn~qm~qm~qm~qm~pl~pl~ol~ok~ok~ok~ok~ok~nk~nj~mj~mi~lh~mi~li~kh~kg~kg~jg~jf~jf~jf~jf~if~ie~ie~he~gd~gd~gd~gc~gc~fc~eb~fb~eb~eb~eb~da~da~da~c`~c_~c`~c_~b_~b_~b_~a^~a^~a^~a]~a]~`\~`\~`\~_\~_\~_[~^[~^[~]Z~^[~]Z~]Z~]Y~\Y~\Y~\X~\X~[X~[W~[X~[X~ZW~ZV~ZW~ZV~YV~YV~YV~XU~XU~XU~WT~XU~WT~WT~WT~WT~WT~VS~VS~VS~UR~UR~UR~UR~UR~UR~SP~TQ~SP~SP~SP~SP~SP~SP~SP~RP~RO~RO~QN~QN~QN~QN~QN~PM~QN~PM~PM~QM~PM~OL~PM~PL~OL~OL~OK~NK~NK~NK~NK~NK~NK~MK~MK~MK~MJ~MJ~MJ~MJ~MI~LI~MI~MI~LI~LI~LH~LH~KH~KH~LH~LH~KH~KH~KG~KG~KG~KG~KG~JG~JG~JG~JG~JG~JG~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IE~HF~HE~HE~HE~IF~HE~HE~HE~HE~HE~HD~HE~HE~HE~HE~HE~HE~HE~HD~HE~HE~HD~HE~HE~HE~HD~HE~HE~HE~HD~HE~HE~HE~HE~HD~HE~HE~HD~IE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IE~IE~HE~IE~IE~IE~IE~IE~IE~IE~IE~IF~IF~IF~IF~IE~IF~IF~IF~JF~JF~JF~IF~IF~JG~JF~IF~JG~JG~JG~JG~JG~KG~JG~JG~KG~JG~JG~JG~JG~KG~KG~KG~KG~KH~KH~KG~KG~KH~KH~KH~LH~LH~LH~LH~MI~LI~MI~MI~LI~LI~LI~LI~LI~MI~MI~MI~MI~MI~MI~MI~NJ~MJ~NJ~NJ~NJ~NK~NJ~NJ~NJ~NJ~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~NJ~OK~OK~OK~OK~OK~PL~OK~OK~OK~PL~PL~OK~PL~OK~PL~PL~PL~OK~PL~PL~PL~PL~PL~PL~PL~PL~QM~PL~PL~QM~PL~QM~PL~QM~QM~PL~PL~QM~QM~QM~QM~PL~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~RN~QM~QM~QM~QM~RN~RN~QN~QM~RN~QN~QM~QN~QM~RN~QN~QN~RN~RM~RN~RN~QN~QN~QN~RN~RN~RN~RN~RN~RN~RN~QM~RN~RM~RN~RN~QN~QN~RN~QN~RN~QN~QN~QM~RM~QM~QM~RN~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~PM~QM~PM~QM~QM~QM~PL~QM~QM~PL~QM~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~OK~PL~OL~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~NJ~OK~OK~NK~NJ~NJ~NJ~OK~NK~NJ~NK~NK~NJ~NJ~NJ~NJ~NJ~MJ~MJ~MJ~MJ~MJ~MI~MJ~MJ~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~LH~LI~LH~LH~LH~KH~KH~LH~KH~KH~KH~LH~KG~KH~LH~KG~KH~KG~KG~KG~KG~KG~JG~JG~JG~JG~JG~JG~JF~JF~JG~JF~JF~JF~JF~IF~IF~JF~JF~IE~IF~JF~IF~HE~IE~HE~IF~IF~IF~IE~IF~IE~IE~IE~IE~IE~IE~IE~HE~HE~IE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~GD~HE~HE~GD~GD~HE~HE~GD~GD~GD~GD~GD~HE~HE~HE~GD~GD~HE~HE~GD~HE~GD~HE~GD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IF~HE~IF~HE~HE~IF~HE~IF~IF~IF~IF~IF~IF~IG~IF~JG~JG~JG~JF~JG~JG~JG~KG~KH~JG~JG~KH~KH~KH~KH~KH~KH~LI~LI~LI~LI~LI~LI~LI~LI~MJ~LI~MJ~MJ~MJ~NK~NJ~NK~NK~NK~NK~OL~NK~OL~OL~OL~OL~OL~PM~PM~PM~PM~PM~PM~PM~QN~QN~QN~QN~QO~RO~RO~RN~SO~RO~RO~RO~RO~SP~SP~SQ~SQ~TP~TQ~TQ~TQ~TQ~UR~UR~UR~VS~VS~VS~VS~VS~VS~WT~WT~WT~XU~XU~YV~YV~YV~YV~ZW~[X~ZW~[W~[X~[X~[X~[X~\Y~\Y~\Y~]Z~^Z~]Z~^Z~^[~^[~_[~_\~_\~_\~`\~`]~a]~a^~b^~a^~b_~b_~c_~c`~d`~c`~d`~d`~da~ea~ea~ea~fb~fb~fc~gc~gd~gd~gd~he~ie~he~ie~jf~jf~jg~kg~kg~kh~kg~lh~lh~lh~lh~mi~mi~nj~nk~nk~ok~nk~ol~ol~om~pm~qm~qn~rn~ro~ro~ro~so~so~so~sp~tp~tp~tp~tp~tp~tp~vr~vr~vr~ws~ws~ws~wt~wt~yv~xv~xv~zv~zw~{x~{x~{x~{x~{x~}z~}z~}z~}z~}z~~{~~{~|~@>@>|~|~@>@>@?@?@>A?@?A@A@A@A@A?B@B@B@B@B@B@B@B@B@CACACACACBCACACADBDBDBCBCBCADBCBDBDBDBDBDBDBDBDBDBCBCBCBCBCBDBDBDBDBCBCBCBCACBCBCBCBCBCACBCACACABABAB@B@B@BAB@BAA@A@A@B@A?A@A@A@A@A@A@A?A@A?A?A@A@A@A?A?@?@?@>@>@>@>@>@>@>?=?=~z~|z~}z~|z~|x~|x~|x~{x~{x~{x~zw~{x~zw~yv~zw~zw~yv~xu~xu~xu~xu~ws~ws~vs~vs~vr~vs~ur~tr~tp~tp~tp~tp~tp~so~so~so~so~qm~rn~qm~qm~qm~qm~qm~pl~ok~ol~nk~nj~nj~nj~nj~mj~mi~li~lh~lh~lh~kg~kg~kg~jg~jf~if~jf~ie~he~he~he~gd~gc~gc~gc~fb~fb~fb~fb~eb~ea~da~c`~da~d`~d`~c`~c_~c_~b_~b^~b^~b^~a^~`]~a]~`\~`\~`\~_[~_[~_[~_[~^Z~]Z~^Z~]Z~]Z~]Y~]Y~]Y~\X~[X~[X~[X~[X~ZW~ZW~ZW~ZV~YV~YV~YV~YU~XU~XU~XU~WT~WT~WT~WT~WT~WT~VS~VS~VS~UR~UR~VR~UR~TQ~TQ~TQ~TQ~TQ~SP~TQ~TP~SQ~SP~RP~SP~RO~RO~QN~QN~QN~RO~QN~QN~QN~PM~PM~PM~PM~PM~PM~PL~OL~OL~OL~OL~OL~NK~OL~NK~NK~NJ~NK~NK~NK~MJ~MJ~MJ~MI~LI~LI~LI~LI~LI~LH~LH~LH~LI~KH~KH~KI~KH~KH~KG~KH~KG~KG~KG~JG~JG~JG~JG~JG~JF~JF~JG~JG~IF~JG~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~HE~HE~HF~HF~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HF~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IE~HE~IE~IE~IE~IE~IE~IE~IE~IF~IE~IE~IE~IE~IF~JF~IE~IF~JF~IF~IF~IF~IF~IF~IF~JG~IF~IF~JF~JG~JG~IF~JF~JG~KG~KG~JF~JG~KG~JG~JG~KG~KG~KG~KG~KG~KH~KG~KG~KG~KH~KH~LH~KH~LH~LH~LH~KH~LI~LH~LH~LH~LH~LI~LI~LI~LI~LI~MI~MI~MI~MJ~MI~MI~MI~NJ~MI~NJ~NJ~NJ~NK~NK~NJ~NJ~NJ~OK~NK~NK~NK~NK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~PL~OK~OK~OK~PL~PL~PL~PL~PL~PL~PL~PL~PL~PM~PL~PL~PL~PL~PL~PL~PL~QM~PL~QM~PM~PM~QM~QN~QM~PL~QM~QM~QM~QM~QM~QM~QM~QM~QM~QN~QM~QN~QM~RM~RM~RN~RN~RN~RN~QM~QM~QM~RN~QM~RM~RN~QM~RN~RN~RN~RN~RN~RN~RN~RN~RN~RO~RN~RN~RO~RN~RN~RN~RN~RN~RN~RN~RO~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RO~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~QM~RM~RN~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~PM~QM~PM~PM~QM~PL~PL~PL~QM~PL~PL~QM~PL~PL~PL~OK~OK~PL~OK~PL~PL~PL~PL~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~NK~OK~NK~NJ~NJ~NK~OK~OK~NJ~NK~NK~NK~NK~MJ~NJ~NJ~MJ~MJ~NJ~MJ~MJ~MJ~MJ~MI~MI~MI~MI~MI~MI~MI~MI~MI~LH~LH~MI~LI~LH~LH~LH~LH~LH~LH~LH~LH~KH~KH~KH~KG~KG~KG~KG~KG~KH~KG~KH~KG~JG~JG~KG~JF~JG~JG~JF~JF~JF~JF~JF~JF~JF~JF~IF~JF~JF~JF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~HE~IF~IF~IF~HE~IF~IF~HE~HE~HE~IE~IE~HE~IE~HE~HE~HE~IE~IE~IE~HE~IE~IE~HE~HE~IE~HE~HE~HE~HE~HE~HE~HE~HE~GD~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IF~HE~IF~HE~HE~HE~HE~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~JG~IF~IG~IG~JG~JG~JG~JG~JG~KH~JG~JG~JG~JG~JG~KH~KH~KH~KH~KH~LI~LI~LI~LI~LI~LI~LI~LJ~LJ~MJ~LI~MJ~MJ~MJ~MJ~NK~NK~NK~NK~NK~NK~NK~OK~NK~OK~OL~OL~PL~PM~PM~PM~PM~PM~PM~PM~QN~QN~QN~QN~RN~RN~RO~RO~RO~RP~RO~SP~SP~SP~SP~TQ~TQ~TQ~TQ~TQ~TQ~UR~UR~VS~UR~VS~VS~VS~VS~VS~WT~WT~WT~XU~XU~XU~XU~YV~YV~ZW~ZW~[W~ZW~[W~[X~[X~\Y~\Y~\Y~\Y~]Y~]Z~^Z~^Z~^[~^[~^[~_\~`\~`\~`\~`]~a]~b^~a^~b^~b^~b_~c_~c`~c`~c`~d`~d`~da~da~da~fb~eb~fc~gc~gc~gc~hd~hd~hd~hd~ie~if~ie~jf~jf~jg~jg~lh~lh~kh~li~li~mj~mj~mj~nj~nj~nk~ok~ok~ok~ok~ol~pm~qn~pm~qm~qn~ro~so~so~so~tp~tp~tp~tp~tr~tr~tp~ur~ur~vr~wt~vr~wt~wt~xu~xu~yv~yv~yv~zw~zw~{x~|y~}z~{x~|y~}z~}z~}z~~{~}z~{~{~|~|~}~@?@?@?@?@?A?A?A@B@B@B@B@B@B@B@B@B@B@BABACACACACACACBCADBCBCBCBDBDBDBECECDCDCDCDCECDCECECECECDBDBDCDBDCDCDBDCDBCBDCDBDCDCDCDBDCDBDCDCDCDCDBDBCBCBCBCACBCBCBBABABABABABABAB@A@B@B@B@BABABAA@A@A@A@A@BAA@A@A@A@A?A?@?@?@>@?@>@>@>@>?=?=?=?=~z~~z~|x~|x~{x~{x~{x~|x~{x~{x~{w~{x~zw~zw~yv~yv~yv~yv~yv~xu~wt~xu~wt~vs~vs~vs~uq~ur~tp~tp~tp~tp~uq~tp~so~sp~so~so~qm~qm~qm~qm~qm~pk~ok~ok~ok~ok~nk~nk~mi~nj~mi~li~li~lh~lh~kg~kg~kg~kg~kg~jf~ie~if~if~he~he~hd~hd~gc~gc~gc~fc~fb~fb~fb~eb~da~da~da~c`~d`~c`~c`~c_~c_~b^~a^~a]~a]~a^~`]~`]~_\~_\~_\~_\~^[~_[~^[~]Z~\Y~]Z~]Z~\Y~\Y~\Y~\X~[X~[X~ZW~[X~ZW~ZW~ZW~ZW~ZW~YV~YV~YV~YU~XU~XU~WT~XU~WT~WT~VS~WT~WT~VS~VS~VR~VR~UR~VR~UQ~TQ~TQ~TQ~SP~TP~TQ~SQ~SP~RP~SO~RO~RO~RO~RO~QN~QN~QN~QN~QN~QN~PM~PM~PN~PM~PL~PM~PL~PL~OL~OL~OL~OL~NK~OK~MJ~NK~NK~NK~MJ~MJ~MJ~MJ~MI~MJ~MJ~LI~LI~LI~LI~LI~KH~KH~KH~LI~KH~LI~KH~KH~KH~KH~KH~KH~JG~KG~JG~JG~JG~JG~JG~JG~JG~JG~JG~IF~IF~JF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~HE~HE~HE~HE~HE~IF~HE~IF~IF~HE~IF~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IE~HE~IE~IE~IE~IE~IE~HE~HE~IF~IE~IE~HE~IE~IE~IE~IE~IE~IE~IE~IE~IE~IF~IF~IE~IE~IE~IE~IE~IE~IF~IE~IE~IE~IF~JF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~JF~JF~JF~JG~JG~JF~KG~KG~JG~JG~JG~JF~KG~KG~KG~KG~KG~KG~KG~KG~KH~KH~KH~KG~KG~LH~LH~LH~KH~LI~LI~LI~MI~LH~LI~LI~LH~LI~LI~MJ~MJ~MI~MI~MI~NJ~MI~NJ~NJ~NJ~NK~NJ~NK~NK~NK~NK~NJ~OK~OK~OK~NK~NK~NJ~OK~OK~OL~PL~OK~OK~OK~PL~PL~OK~PL~PL~PL~PL~PL~PL~PL~PL~QM~PL~PL~PM~PL~QM~PL~QM~PL~PL~PL~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~RN~RN~QM~QM~RN~RN~RN~QN~QN~RN~RN~RN~QM~RN~RN~RN~RN~RN~RO~RN~RN~RN~RN~RN~RN~SO~RN~SO~RO~SO~RO~SO~RN~RN~RN~SO~SO~SO~SO~RN~SO~SO~RN~RN~SO~SO~SO~SO~SO~SO~SO~SO~SO~RN~SO~SO~SO~SO~SO~RN~SO~RN~SO~RN~SO~SO~RN~RN~RN~RN~SO~SO~RN~RN~RN~RN~RN~SN~RN~RN~RN~RN~RM~RM~RM~RN~RN~RN~RN~QM~RM~QM~RN~QM~RN~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~PM~QM~QM~QM~QM~QM~PM~QM~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~OK~PL~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~NJ~NJ~NK~NJ~NJ~NJ~NJ~NJ~NJ~MI~MJ~MJ~MJ~MI~MJ~MJ~MI~MI~MI~MI~MI~MI~MI~MI~MI~LI~LI~MI~LH~KH~KH~KH~KH~KH~KH~KH~KH~LH~KG~LH~KH~KH~KH~KG~KG~KG~KG~KG~JG~JG~JG~JF~JG~JG~JG~JF~JG~JF~JF~JF~JF~JF~JF~JF~IF~IF~JF~JF~IF~IF~IF~IF~IF~IE~IE~IE~IF~IF~HE~HE~IF~IF~IF~HE~HE~IF~IF~IE~IE~IE~IE~IF~IE~IE~IE~IE~IE~IE~HE~HE~HE~HE~HE~HE~IF~HE~HE~IE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~HE~IF~HE~HE~IE~IE~IE~IF~IE~HE~IF~IF~IF~HF~IF~IF~IF~IG~IF~IF~IG~IG~IF~IF~JG~JG~JG~JG~JG~JG~JG~KH~KH~KG~KH~KG~KH~KH~KH~KH~KH~KH~KH~LI~MI~LI~MI~MJ~LI~MJ~MJ~MJ~MJ~MJ~MJ~NJ~MJ~MJ~NK~NK~NK~NK~NK~NK~OL~NK~OL~OL~PM~PM~OL~PM~PM~PM~QN~QN~QN~QN~QN~QO~QN~QO~RO~RO~RP~SP~RO~SP~SP~SP~SP~TQ~TQ~SQ~TQ~UR~UR~UR~UR~UR~UR~VS~VS~VS~VS~VS~WS~XT~WT~XU~XU~XU~XU~YV~YV~YV~YV~ZW~[W~ZW~[X~[X~\Y~\Y~]Z~]Y~]Z~]Y~^Z~^[~^[~^[~_\~_[~_\~_[~`\~`\~`\~a^~a^~a^~b_~b_~b_~c`~c_~d`~da~da~da~ea~eb~fb~fb~gc~fc~gd~gd~hd~hd~hd~hd~hd~ie~jf~jf~kg~jg~kg~kh~kh~lh~mi~mj~mj~mi~mj~mj~nk~nk~ok~ol~ol~ok~pl~qm~qm~qm~qn~ro~ro~so~so~sp~so~tp~tp~tp~tp~tr~tr~ur~vr~vr~wt~wt~wt~wt~xu~xu~yv~yv~yv~yv~zw~{x~{x~{x~}z~}z~}z~}z~~{~|~|~|~|~@?@?@?@?A?A?A?A@A@A@A@A@B@B@CACACABABACACACACBCACBCBCBCBCBDCDBDCDCDCDCECDCECECEDEDEDECFCECECECECECECECECECECECDCDCDCDCDCDCECDCDCDCDCDCDCECDCECEDDCDCDCDCDBDCDBCBCBCBCBDBCBCBCACACACACACACACABABABABABABABABABAB@B@A@A@A@A@A@A@A@A?@?A?A?@?@>?>?>~|~?>~z~?=?=}z~|z~}z~|z~~z~~z~}y~}y~{x~{x~zx~yv~yv~yv~yv~xu~xu~xu~xu~xu~vs~vs~vs~vs~vs~vs~vr~vr~uq~uq~uq~tp~tp~so~so~so~so~qm~qm~qm~qm~qm~pl~ok~nj~ok~nj~nj~nj~nj~mi~mi~mi~li~lh~kh~kh~kg~jf~jf~ie~jf~if~he~ie~ie~he~he~gd~gd~gc~fb~fc~fc~eb~eb~ea~eb~da~da~d`~c_~c_~c_~c_~b^~b^~b^~a^~a^~`]~`]~_\~_\~_\~^[~_\~^[~^[~^Z~]Z~]Z~]Z~\Y~\Y~\X~\Y~\X~[X~[X~[X~[W~ZW~ZW~YV~YV~YV~YV~YV~XU~XT~XT~XU~WT~WT~WT~WT~VS~VS~VS~VR~VR~UR~VR~UR~UR~UR~UQ~TQ~TQ~TQ~TQ~TQ~SP~SP~SP~SO~RO~RO~RN~QN~QN~QN~QN~QN~QN~PM~PM~PM~PM~PM~PM~PL~PL~OL~OK~OK~OK~NK~NK~NK~NJ~NJ~NK~MJ~MJ~MJ~MJ~MI~MJ~LI~LI~LI~LI~LI~LI~LI~LH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KG~KG~KG~JG~JG~JG~JG~JG~JG~JF~JG~JF~IF~IG~IG~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IE~IE~IF~HE~IF~IE~IE~IF~IF~HE~IF~IE~IE~HE~IF~IE~IE~IE~IF~IE~IE~IE~IE~IE~IE~IE~HE~IE~IE~IE~IE~JF~IE~IE~IE~IE~IE~IE~IF~IF~IF~JF~JF~JF~IE~IF~IF~IF~IF~JF~IF~JF~IF~JF~JF~JG~JG~JG~JF~JG~JG~JF~JG~JG~KG~JG~KG~KG~KG~KG~KG~KG~JG~KG~KH~KG~KG~KH~KH~KH~KH~LH~LH~LH~LH~LH~LH~LH~LH~LI~LI~LH~LI~LI~MI~MI~LI~LI~MI~LI~MI~MJ~MJ~MJ~MJ~NJ~NK~NK~NK~NK~NJ~NK~NJ~NK~NK~OK~OK~OK~OK~OK~OK~PL~PL~PL~OK~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~QM~PL~PL~QM~QM~QM~QM~QM~QM~QM~QM~QM~PL~QM~QM~QM~RN~QM~QN~QM~QN~QN~QN~RN~RN~QN~QM~RN~RN~QN~RN~SN~RN~RN~RN~RN~RN~RO~RN~RN~RN~RO~RO~SO~SN~RN~RN~RN~SO~SO~RN~RN~RN~SN~SN~SN~SO~SO~SO~SO~SN~SO~TO~SO~SO~SN~SO~SO~SO~SO~SO~SO~TO~TO~SO~SO~TP~SP~SP~SP~SO~SP~SO~SO~TP~SO~TP~SO~SO~TP~SP~SO~SO~SO~SO~SO~TO~SO~SO~SO~SO~SO~SO~SO~SO~SO~RN~SO~RN~SO~RN~RN~RN~SO~SO~RN~RN~RN~RN~RN~SO~RO~RO~RN~RN~RN~RN~SN~RN~SN~SN~RN~RM~RM~QM~RN~QM~QM~QM~QM~QM~QM~PL~QM~QM~QM~QM~PL~PM~QM~PM~PL~PL~PM~PL~PL~PL~PL~PL~PL~PL~PL~OL~OL~PL~PL~PL~OK~OK~OK~OK~OK~OK~OL~OK~OK~OK~OK~NK~OK~OK~NJ~NK~NK~OK~NJ~NJ~NJ~NK~MJ~NJ~NJ~MJ~MJ~MJ~MJ~MJ~MI~MJ~MJ~MI~MI~LI~MI~MI~LI~LH~LI~LH~LI~LI~KH~LH~LH~LH~KH~LH~LH~LH~KG~KG~LH~KG~KG~KH~KG~KG~KG~JG~JG~JF~JG~JG~JG~JG~JG~JG~JF~JF~JF~JF~JF~JF~JF~IF~IF~JF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~HE~IF~IE~IF~IF~IE~IF~IE~IE~IE~IE~IE~IE~IE~HE~IE~IE~HE~HE~IF~IE~IE~IE~IE~IF~HE~IF~IF~IE~IE~IE~IE~IE~IE~IE~IE~IF~IF~IF~IF~IF~IF~IF~JF~IF~IF~IF~IF~IF~IE~IF~IG~IG~IF~IG~IG~JG~IF~IF~JG~JG~JG~JG~JG~JG~JG~JG~JG~KH~KH~KH~KH~KH~KH~KH~KH~LH~LI~LH~MI~LI~MI~MI~MJ~MJ~MI~MJ~MJ~MJ~MJ~MJ~MJ~NK~NK~NK~NK~NK~NK~NK~OK~OL~OL~OL~OL~OL~PM~OL~OL~PM~PM~QM~QN~QN~QN~QN~RO~QO~RO~RO~RO~RO~SP~SP~SP~SP~SP~TQ~TQ~TQ~TQ~TQ~TQ~UR~UR~UR~UR~VS~VS~VS~VS~WT~WT~WT~WT~XU~XU~XU~XU~XU~XU~ZV~ZV~ZV~ZW~[W~[X~[X~\Y~\Y~\X~\Y~]Y~]Z~]Z~^[~^[~_[~_\~_\~`\~`\~`\~a]~a]~a^~b^~a^~b_~b_~b_~c_~d`~d`~d`~d`~da~ea~fb~fb~fb~fb~gd~gc~gd~hd~hd~hd~hd~ie~ie~ie~jf~jf~kg~kg~lh~lh~li~li~mi~mi~mj~nk~ok~nj~ok~ok~ol~pl~pm~pm~qn~qn~qn~qn~qn~so~tp~sp~sp~tp~tp~uq~uq~uq~uq~uq~vr~vt~vt~wt~wt~wt~wt~xu~xu~yv~yv~yv~yv~{x~{x~{x~{x~{x~|z~}z~}z~~z~?>|~@>@?@?@?A@A@A?A@B@B@B@B@B@B@BABACACACBCBCBCBCBCBCBCBCBCBDBDCDCDCDCDCEDDCEDECECFDFDEDEDEDFEFEFDFEFEFDFDFDFDFDEDEDFDEDECECECEDFDEDEDEDECECEDEDEDEDEDEDEDEDEDEDEDEDDCDCECDBDBECECDCDCDBDBDBDBDBDBDBCBCACBCBCBCACACBCBCACABABABABAB@B@B@A@A@A@A@A@A?A?@?@?@>@>@>@>?>?=?=~z~{~@>~z~?=~z~~z~~z~}z~|x~{x~{w~|x~zw~zw~yv~yv~yv~xu~xu~wt~xu~xu~xu~xs~vs~vs~vs~vr~vr~uq~uq~tq~tp~tp~so~so~so~rn~qm~qm~pl~pl~pl~pl~ol~ok~ok~nj~nj~mj~mi~mi~mj~li~mi~lh~lh~kg~kg~jg~jg~jf~jf~ie~he~he~ie~hd~hd~gd~gc~fc~fc~fb~fb~fb~ea~ea~ea~da~d`~d`~c_~c_~b_~c_~b^~a^~a^~a^~`]~`]~_\~_\~_\~_[~_[~^Z~^Z~_[~^Z~]Z~]Z~]Z~]Y~\Y~\Y~[X~\Y~[X~[X~[W~ZW~ZW~YV~ZV~YV~YV~XU~XU~XU~XU~XT~XT~XT~WT~VS~WT~VS~VS~VS~VS~UR~UR~UQ~UQ~UQ~UR~TQ~TQ~TP~SP~SP~SP~SP~SO~RO~RO~RO~RO~RO~QN~QN~QN~QN~QM~PM~PM~PM~PM~PM~PL~PL~OL~OK~OL~OL~OK~NK~NK~NK~NK~NK~NK~MJ~MJ~MJ~MJ~MJ~MJ~MI~MI~MI~MI~MI~LI~LI~LI~LI~LH~KH~KH~KH~KH~KH~KG~KH~KH~JG~KG~KG~JG~JG~JG~JG~JF~JF~JG~JG~IG~JG~JG~JF~IF~JG~JF~JF~IF~JF~IF~IF~JF~IE~IF~IE~IE~JF~IE~JF~IE~IE~IF~IF~IF~IE~IF~IF~IF~IF~IF~IE~IE~IE~IF~IE~IF~IE~IE~IF~IE~IE~IF~IE~IE~IE~IE~IE~IF~IF~IF~IF~IF~IG~IF~IF~IF~JF~JF~JF~JF~JF~JF~JG~JF~IF~IF~JF~JG~JG~JG~JF~JG~JF~JF~KG~JG~KG~KG~JG~JG~KG~KG~KG~JG~KG~KG~KH~KG~KG~KG~KG~LH~KG~KH~KH~LH~LH~LH~LH~LH~LH~LH~LH~KH~LH~LH~LH~LH~LI~MI~MI~MI~LI~MI~MI~MI~MI~MI~MI~MJ~MI~NJ~NJ~NJ~NK~NJ~NK~NK~OK~OK~OK~OK~OK~OK~OK~OK~PL~OK~PL~OK~PL~PL~OL~OL~OL~PL~PL~PM~PL~PL~QM~PL~PL~PL~QM~QM~QM~QM~QM~PL~QM~QM~QM~QM~QM~RN~RN~RN~QM~QM~RN~QN~QM~RN~RN~RN~SN~RN~RN~RN~RN~RN~RN~RN~RN~SO~SO~RN~SO~RN~SO~RN~RN~SO~SO~SO~SO~SO~RO~SO~RO~SO~SO~SO~SO~SO~SP~SO~SO~TO~TO~SO~SO~SO~SO~SO~SO~SO~TP~TP~TP~SO~SO~TP~TP~TP~SO~TP~TO~TP~TP~TP~SO~TP~TP~SO~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~SO~TP~SP~SO~TP~SO~SO~TO~TO~TO~TO~SP~TO~SO~SO~SO~SO~SO~TO~SO~SO~SO~SO~SO~SO~SN~TO~SO~SO~RN~SO~RN~RN~SO~RN~SO~SO~SO~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~QM~RN~QM~QM~QM~QM~QM~QM~QM~QM~PL~PL~QM~PL~PL~PL~OL~PL~PM~PL~PL~PL~PL~PL~PL~PL~PL~OK~PL~PL~PL~OL~OL~OK~OK~OK~NK~OK~OK~OK~OK~NJ~NJ~NJ~OK~NK~OK~OK~NJ~NK~MJ~NJ~NJ~MJ~MJ~NJ~NJ~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~LH~MI~LH~LH~KG~LH~LH~LH~LH~KH~KH~LH~LH~KG~KH~KH~KG~KG~KG~KG~KH~KG~JG~JG~JG~JF~JG~JG~JG~JF~JF~JF~JF~JF~JF~JF~JF~JF~JF~JF~JF~JF~IF~JF~IF~JF~JF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~HE~IE~IE~IF~IE~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IG~IG~JF~IF~IF~IF~IF~IF~JG~JG~IF~IF~JG~JG~JG~JG~JG~JG~JG~KH~KH~KH~KH~KH~KH~LH~LH~LH~LH~LH~LI~LI~LI~LI~MI~LI~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~NJ~NJ~NJ~OK~OK~OK~OK~OL~OL~OK~OL~OL~PL~PL~PL~PL~QM~QN~QN~QN~QN~QN~RO~RO~RO~RO~RO~RO~RO~RO~SP~SP~SP~SP~TQ~TQ~TQ~TQ~TQ~UR~UR~VS~VS~VR~VS~VS~WT~WT~WT~WT~XU~WT~WT~XU~YU~XU~YU~YV~YV~ZW~ZV~ZW~[X~[X~[X~\X~\X~\Y~]Y~]Y~]Z~]Z~]Z~^[~_\~_\~_\~`\~`\~a]~a]~b^~a^~a^~b_~b_~b_~c_~c`~c`~d`~ea~ea~fb~fb~fb~gc~gc~gc~gc~gc~gd~he~hd~ie~if~jf~jf~jf~jg~kg~kg~lh~li~mi~lh~mi~mj~nj~nj~nj~ok~ol~ok~pl~pl~pl~pm~qm~qm~qn~rn~rn~rn~so~tp~tp~tp~tp~tp~tq~ur~ur~vr~vr~vr~vs~wt~wt~xu~wt~xu~yv~yv~zw~zw~zw~{x~{x~{x~{x~|x~}z~~z~~z~~z~~|~?>@>@?@?@?@?@?A@A@B@BABABABACACABABACBCBDCDCCBDBDBDBDCDCDCDBECDBDCECECFDEDFDEDFEFDFEFEFEGEGEGEGFGEGEGFGEGEGEGEGEGEGEFEFEGEFDFEGEGEFDGEGEGEFEFEFEFDFEFEFDFDFDFEFDFDGEFDFDFDFDECECECECECECECECECECDCECECDCDCDBDBDBDBCADBDBDBCACACBCACABABABAB@B@A@A@A@B@A@A?@?A@A?@?@??>@?@>@>@>?>?>?=?=?=~z~~z~~z~~z~|x~|x~{x~{x~{x~{x~zv~zv~yu~xu~xu~xu~xu~xu~xu~wt~wt~wt~vs~vs~vr~vr~uq~tq~tp~tp~so~ro~rn~rn~rn~rn~qm~pl~pl~pl~pl~pl~ol~nk~nk~nj~nj~mi~mi~mi~li~lh~lh~kh~kg~kg~jf~jf~jf~ie~jf~ie~if~he~hd~gc~gc~fc~fc~fb~eb~eb~eb~ea~ea~da~c`~c`~c`~c`~b_~b^~b_~b_~a^~a]~a]~`]~_\~`]~_\~_[~_\~_[~_[~]Z~]Z~]Z~]Z~]Z~\Y~\Y~\Y~\Y~\X~[X~[W~[X~ZW~ZW~ZV~ZV~YV~YV~YU~XU~XU~XU~XU~WT~WT~WT~WT~VS~VS~VS~VS~VS~UR~UR~UR~UR~UR~TQ~SP~TQ~SP~SP~SP~SP~SP~SO~SO~RN~RO~RO~QN~QN~QN~QN~PM~QN~PM~PM~PM~PM~PL~OL~OL~PL~OK~OK~OK~OL~NK~OL~NK~NK~NJ~MJ~MJ~MI~MI~MI~MI~MJ~MJ~LI~LI~MJ~MI~LI~LI~LI~LI~LH~KH~KH~KH~KH~KH~KH~KH~KG~KH~KH~KH~JG~JG~JG~JG~JG~JG~JG~JG~JG~JF~JG~JG~JG~JG~IF~JF~IF~IF~JG~JG~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~JF~IF~IE~IF~JF~IF~IF~IF~IF~IE~JF~IF~IF~IF~IF~JF~JF~IF~IF~IF~JF~IE~IF~IF~JF~JG~JF~JF~JF~JF~JF~JG~JG~JG~KG~JG~JG~KG~JG~KG~KG~JG~KG~KG~KG~JG~JG~JG~JG~JG~KG~KG~KH~LH~LH~LH~KH~KG~KH~LH~LH~KH~LH~LH~LH~LH~LH~LH~LH~LI~LI~LI~LI~MI~MI~MI~MI~MI~LI~MJ~MJ~MJ~MI~MI~MI~MJ~MJ~NJ~NJ~NK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~PL~PL~PL~PL~PL~PM~PM~PL~QM~PL~PL~QM~QM~QM~QM~QM~QM~QM~RM~QM~QM~QN~RN~RN~RM~RN~RN~RN~SO~RN~RN~RN~RN~RN~RN~RN~SO~SO~RN~RN~SO~SO~SO~SO~SO~SO~SO~RN~RO~SO~SO~SO~SO~SO~SO~SN~SO~SO~SO~SO~SO~TO~TO~SO~SP~TP~TO~SO~TO~TP~SO~SO~TP~TP~TP~TO~TO~TP~SO~TO~TP~TP~SO~TP~TP~TP~TP~TP~UQ~TP~TP~TP~TP~TP~TP~TP~UP~TP~TP~TP~TP~TP~UQ~TP~TP~UQ~TQ~UQ~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TO~TO~TO~TP~TP~SO~TP~TP~TP~TO~TO~TO~SO~SO~SO~SO~SO~SO~SO~SO~TO~SO~SO~SO~SO~SO~SO~SO~RN~SO~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~QN~RN~RN~RN~RN~QM~QM~QM~QM~QM~QM~QM~QM~PM~PM~QM~QM~QM~PM~PM~PM~QM~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~OL~OL~OL~OK~OK~OL~OL~NK~OK~NK~OK~OK~OK~OK~NK~NK~NK~NK~NK~MJ~NJ~MJ~MJ~MI~MJ~MJ~MI~MI~NJ~MI~MI~MI~LI~LI~LH~MI~LH~LH~LH~LH~KH~KH~LH~LH~KG~KG~KH~KG~KG~KH~KH~KH~LH~LH~KG~KH~KG~KG~KH~JG~KH~KH~KG~JG~JG~JG~KH~JF~JF~JG~JG~JF~IF~JG~JG~IF~IF~JF~JG~JF~JF~JF~JF~JF~IF~JF~IF~JF~IF~IF~IF~IE~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~IF~JG~IF~IF~IF~JG~IF~IF~IF~IF~IF~IF~IF~IF~IF~JG~IF~JF~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~KH~KH~KH~KH~KH~KH~KH~KH~LI~KH~LH~LH~LH~LI~LI~LI~LI~LI~MI~MI~MJ~MJ~MI~MJ~MJ~MJ~NJ~NK~NK~OK~NJ~NK~OK~OK~OK~OK~OL~OL~OL~OL~PM~PL~PM~QM~QM~QM~QM~QM~QN~QN~QN~RO~RO~RO~RO~RO~RO~SO~SP~SP~SQ~TQ~SQ~TQ~TQ~TQ~TQ~TQ~UR~UR~UR~VS~VS~VS~VS~WT~WT~WT~WT~XU~XU~XU~YU~YV~YU~YU~ZV~ZW~ZV~ZW~[X~ZW~[X~[X~\X~\Y~\Y~]Z~]Y~^Z~^[~^[~^[~_[~`\~`\~`\~`]~a]~a^~b^~a^~b^~b_~b^~c_~c_~c_~c`~da~da~ea~eb~fb~fb~fc~gc~gd~gd~hd~hd~hd~he~if~jf~jg~jf~jf~kg~lh~lh~lh~mi~mj~mi~nj~nj~nk~ok~nk~ok~ok~ol~pl~qm~qm~qm~qn~qn~qn~rn~so~tp~tp~tp~tp~uq~tr~tr~ur~vr~vr~vr~xt~wt~xu~xu~xu~xu~xu~yv~yv~yv~yv~{x~{x~{x~|x~|z~|z~}z~~z~~|~~|~|~@>@?@?@?A?A@A@A?A@A@B@BABABABABACACBCBCBCBDCDCDCECECECECECECECECECECEDFDFDGEGEGEGEGEGEGEGEGFGFHFHFHFHFHFHFHFHFHFHFHFGEGEGFGFHFGEGFGEGFGEGEGEGFGEGEGEGEGEGFGFGFFEGEGFGFGEGEGEGEFEFEFEFEFEFEFEFDEDFDFDECECECECDCDCDCDCDCDCECECDCDBDBDBDBCACACACACACACABAB@BAB@B@A?A@A@@?@?@?@?@>@>@>@>@>@>@>@>?>~|~?=~z~?=?=}y~}y~}y~}y~|x~{w~{w~zu~zw~zw~zw~zu~zu~xu~xu~xu~wt~xs~vs~vs~vs~tq~tq~tq~tq~tp~so~sp~ro~ro~rn~qm~qm~qm~pl~pl~ok~pl~ok~nj~ok~ok~nj~mi~mi~mj~li~li~kh~kg~kh~kg~kg~jf~jf~ie~ie~ie~he~he~he~hd~gc~fc~fc~fc~fb~fb~ea~ea~ea~d`~d`~da~d`~c_~b_~b_~a^~a^~a]~a]~`\~`\~`\~_\~`\~_\~^[~_\~^[~^Z~^Z~^Z~]Y~]Z~]Y~]Y~\Y~[X~[X~[X~[W~[W~[W~ZW~ZW~YV~YV~YV~YV~XU~XU~XU~XU~WT~WT~WT~WT~WT~VS~VS~UR~VS~UR~UR~UR~UR~TQ~TQ~TQ~TQ~TQ~SP~SP~SP~SP~RO~RO~RO~RN~RN~RN~QN~QN~QN~QN~QN~PM~PM~PM~PM~OL~OL~OL~OL~OL~OL~NK~NK~NK~NK~NK~NJ~NK~NJ~NJ~NJ~NJ~MI~MI~MJ~MJ~MI~MI~MI~LI~LI~LI~LI~LH~LI~LI~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~JG~JG~JG~JG~KH~JG~JG~JG~JG~JG~JG~JF~JG~JG~JG~IF~JG~IF~JG~IF~IF~IF~IF~IF~JF~IF~IF~JF~IF~IF~IF~IF~IF~JF~IF~IF~IF~IF~JF~JF~IF~JF~JF~JF~JF~JF~JF~IF~IF~JF~IF~JG~JG~JF~JG~JG~JG~JF~JG~JG~JG~JG~JG~JG~JG~JG~JG~KG~JF~JG~JG~KG~KG~KG~KG~KG~KG~KG~KG~KG~KG~KG~KH~KH~KH~KG~KG~KG~KG~LH~LH~LH~LH~LH~LH~LI~LH~LI~LH~LH~LI~LI~LI~LI~MJ~MI~MI~LI~MI~LI~MI~MI~MI~MJ~MI~MI~NJ~MI~NJ~NJ~NJ~NJ~NJ~NJ~NK~OK~NK~OK~OK~OK~OK~OK~OK~OK~PL~PL~PL~PL~OK~PL~PL~PL~PL~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~RN~RN~RM~RN~RM~RM~RN~RN~RN~RN~RN~RN~RN~RN~RN~SO~RN~RN~RN~RN~RN~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~TO~SO~SO~TP~SO~TO~TO~TO~SO~SP~SP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~SO~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~UP~UP~UP~UP~UP~UQ~UQ~UQ~TQ~TP~TQ~UQ~UQ~UQ~UQ~UQ~UP~UP~UP~UP~VQ~UP~UQ~UQ~UQ~UP~UQ~UQ~TQ~TQ~UQ~UQ~UP~UQ~UP~UP~TP~UQ~TQ~TQ~UQ~UP~UQ~TQ~UQ~UP~UP~TP~TP~UP~TP~UQ~UQ~TP~TP~TP~UQ~TP~TP~TP~TP~TP~TP~SO~TP~SO~TP~TO~TP~TP~TP~TP~TP~TO~TO~SO~TO~SO~SO~SO~SO~SO~RN~SO~SO~SO~SO~SO~RN~RN~RN~RN~RN~RN~RN~RN~QM~RN~RN~RN~RN~QM~QM~QM~RN~RN~QM~QM~QM~RN~QM~QM~QM~QM~QM~PM~PM~PL~PL~QM~QM~PM~PL~PL~PL~PL~PL~OL~PL~OL~OL~OK~OK~OK~OK~OK~OK~NK~OK~OK~NJ~OK~NK~NK~NK~NJ~NJ~MJ~MJ~MJ~MJ~MI~MI~MJ~LI~MJ~MJ~MI~MI~MI~LI~LI~LH~LI~LH~LH~LH~LH~LH~LH~LH~LH~LH~KH~KG~LH~LH~KH~KH~KH~KH~LH~KH~KH~KH~KH~KH~KG~KG~KH~KG~JG~JG~JG~KG~JF~JF~JF~JG~JF~JF~JF~JF~JF~JF~JF~JF~JF~JF~JF~JF~JF~JF~JG~JF~JF~JF~JF~IF~IF~JF~IF~IF~IF~IF~IF~JF~JF~IF~JF~IF~IF~IF~JG~IF~IF~IF~JG~JG~IF~IF~IF~JG~IF~JG~JG~JG~IF~IF~IF~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~KG~KH~KH~KH~KH~KH~KH~KH~KH~LH~LH~LH~LH~LH~LH~LI~MI~LI~LI~MJ~MJ~MJ~MJ~MJ~MJ~NJ~NK~NJ~NJ~NK~NK~NK~OK~OK~OK~OL~OL~OL~OL~OL~PL~OL~PL~PL~PL~PM~PM~QM~QM~QN~QN~QN~QN~RN~RO~RO~RO~RO~SP~RP~SP~SP~SP~SP~SP~TQ~TQ~TQ~UR~TQ~TQ~TQ~UR~VS~VS~VS~VS~WT~VS~WT~XT~XT~XT~XU~YU~YV~YV~YV~ZV~ZW~[X~ZW~[W~[X~[X~[X~\X~\X~]Y~\Y~]Z~]Z~^[~^[~^[~_[~_\~`\~`\~`]~a]~`]~a^~a^~b^~b^~b^~b_~c_~c_~d`~da~ea~ea~fb~eb~fc~gc~gc~gc~gc~gd~hd~he~ie~if~jf~jf~jf~jg~kg~lh~kh~lh~mi~mi~nj~nj~nk~nk~nk~nk~ok~pl~pl~pm~qn~qm~qm~so~so~so~rn~tp~so~tp~tp~tp~vr~vr~vr~vr~vr~vs~vs~wt~xu~xu~wt~wt~xu~xu~yv~yv~yv~zv~{w~|x~|x~|x~|x~~z~~z~~z~~z~@>@>?>@?@?@?@?A@A@A@BABAB@BABABACBCACBCBDBCBDBDBDCDCDCECEDEDEDEDFDFDFDFDFDFEGEFEGEGEHFHFHFHFHFHFHFIGHGIGIGHFIGIGIGIGHGHFHFHFHFHFHGHGHGHGHGHFHFHFHFGFHFHFHFHFHFHFHFHFGFGFHGHGGFGFHFHFHFGFGFGEGEGEGEFEFDFDFEFEFEFDFDEDFDEDEDEDEDECEDDCDCECECDCDCDCDCDBDBDBDBCBCACACAB@B@B@B@B@A@A@A@A@A?A?@?@>@>@>@>@??>?>?=@>?=?=?=?=~z~}y~}y~}y~}y~}y~}y~|x~{w~{w~yu~zw~yu~xu~xu~ws~ws~vs~vs~vs~vs~vs~ur~uq~tq~tq~tq~so~ro~rn~rn~rn~rn~rn~pl~pl~pl~pl~pl~pl~ok~nk~nj~nj~nj~mi~mi~li~lh~kh~kh~kg~kg~kg~jf~if~jf~ie~ie~he~he~hd~hd~gc~fc~fc~fc~eb~eb~eb~fc~da~ea~ea~c_~c_~c_~b^~b^~b^~a^~a^~`]~a]~`]~`\~_[~_\~_\~_[~^Z~^Z~]Z~]Z~]Z~]Z~\Y~\Y~\Y~[X~\X~[X~[X~ZW~ZW~ZW~ZW~ZV~YV~YV~XU~XU~XU~XT~XT~WT~WT~WT~WT~VS~VS~VS~VS~UR~UR~VS~UR~UR~TQ~TQ~TP~TP~SP~SP~SO~SP~RO~SP~RO~RO~RO~QO~QN~RO~QN~QN~PM~PM~PM~PM~PM~PL~PL~PL~OL~OL~OL~OL~OL~OK~NK~OK~NK~NK~NK~NK~NK~NJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~LI~MI~MI~LI~MI~LI~LH~LH~LH~LH~LH~KH~KH~KH~KH~KH~JG~JG~KH~KH~KH~KH~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JF~JG~JG~JG~JG~JG~KG~JG~IF~JG~JG~JF~JF~JG~IF~JG~JF~JG~JG~JG~JG~JG~JF~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~KG~JG~JF~KG~JG~KG~JF~JG~KG~KG~KG~JG~KG~JG~JG~KG~KG~KG~KG~KG~KG~KG~KG~KH~KH~KH~KH~KG~KG~KH~LH~LH~LH~LH~LH~LH~LI~LH~LH~LI~LH~LI~LH~MI~MI~LI~MI~MI~MI~MI~LI~LI~MJ~MI~MI~MJ~NJ~NJ~MI~NJ~NJ~NJ~NJ~NJ~NJ~NK~NJ~NJ~NK~NK~OK~OK~OK~OK~OK~OK~PL~PL~PL~PL~PM~PL~PL~PL~PL~PL~PL~QM~QM~QM~QL~QL~RM~QM~QM~QM~QN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~TO~TO~SO~SO~SO~TO~TO~TO~TP~SO~SO~TP~TP~TP~TP~TP~TP~TP~TP~UQ~UQ~TP~TP~TP~TP~TP~TP~TP~TQ~TQ~TP~TP~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UP~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~VR~UQ~UQ~UQ~VQ~VQ~VQ~VR~UQ~VQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~VQ~VQ~VQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UP~UQ~UQ~TQ~UQ~UP~UP~UP~UP~UQ~TP~TP~TP~UP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TO~TO~TO~SO~SO~TO~TO~SO~SO~SO~SO~SO~SO~RN~RN~RN~RN~SO~RN~RN~RN~RN~RO~RN~RN~RN~RN~RN~RN~RN~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~PL~QM~PM~PL~PM~PM~PM~PL~PL~PL~PL~PL~PL~OL~OL~OK~OK~OK~OK~OK~OK~OK~OK~OK~NK~NK~NK~NJ~NJ~NJ~NK~NJ~NJ~MI~NJ~MJ~MI~MJ~MI~MI~MI~MI~MI~MI~MI~MI~MI~LI~LI~LI~LH~MI~LI~LH~LH~LH~KH~LH~KH~LH~LH~LH~LH~KH~LH~LH~KG~KG~KG~KH~KH~KG~KH~KG~KH~KH~KG~KH~JG~JG~JG~JG~KG~JF~JG~JG~JG~JG~JF~IF~IF~JG~JF~JF~JF~JF~JF~JG~JF~JF~JF~JF~JG~JG~JG~JF~JF~JG~IF~JF~IF~JG~JG~JF~IF~JG~JF~JG~JG~IF~JG~JG~JF~JF~IF~JG~JG~JG~JG~IF~JG~JG~JG~JG~JG~JG~JG~JG~KG~JG~JG~JG~KG~KG~KG~KG~KG~KG~KH~KH~KH~LI~KH~LI~LH~LH~LH~LH~LH~LI~LI~LI~LI~LI~MJ~LI~MJ~MJ~MJ~NJ~MJ~NJ~NJ~NJ~NK~NK~NK~NK~NK~OL~OL~OL~OL~OL~OL~PL~PL~QM~PL~QM~PL~QM~PM~QN~QN~QN~QN~QN~RO~QN~RN~RO~RO~SO~SO~SP~SP~SQ~TQ~TQ~TQ~TQ~TQ~UR~UR~UR~UR~VS~VS~VS~VS~VS~WS~WS~WT~WT~XU~XU~XU~XU~XU~YV~YV~YV~ZW~ZW~[W~[W~[X~[X~[X~\Y~\Y~\Y~]Y~]Z~]Z~]Z~^Z~^[~_[~_\~_\~_\~_\~`]~a]~`]~a^~b^~b^~b_~b_~c`~c`~d`~d`~da~eb~fc~eb~fb~fc~fc~gc~hd~hd~hd~hd~he~ie~ie~jf~kg~jf~kg~kg~lh~lh~li~mi~nj~nj~nj~ok~ok~ok~ol~qm~qm~qm~qm~qm~rn~so~so~so~so~tp~tp~tp~tp~tr~tq~vr~vr~vt~wt~wt~wt~xu~xu~xu~xu~yv~yv~yv~yv~zv~zx~zx~zx~{x~{x~|x~|x~}z~?=~z~?=?>?>@>@>@?@?@?A@A@A@A@B@BABABACACACBCBDBDBDCDCDBECECECECEDEDFDFDFDFEFDFDGEGEGEGEGFGFGFHFHFHFHFHFIGIGIGIGIGIGIGIGIGIGIGIHHGIGIGIGIGIGIGIHIHIHIGHGIGHGHGHGIGIGIGIGIHIHIGIGHGHGHGHGHGHGHGHGHGHGHGGFGFGFHFGFGFGEGEGFGFFEFEFEFEFEFEFEFDFDFDEDEDEDECECEDECECECECDCDCDBDBCBCACACACAB@B@B@B@B@B@B@A?A?A?@?@?@>@>@>@>@>@>@>@>?=?=?=?=?=~z~}y~}y~}y~|x~|x~{w~{w~{w~{w~zv~yu~yu~yu~xu~ws~wt~xs~xs~xs~vs~vs~ur~tq~tq~tq~to~so~so~ro~ro~rn~pm~qm~qm~pl~pl~pl~ok~ok~nj~nj~nj~nj~mj~li~li~li~li~kg~kg~kg~kg~jf~jf~ie~ie~ie~hd~hd~gc~hd~gd~fc~gc~gc~fb~fb~ea~da~da~d`~c_~c_~c_~c_~c_~b^~b^~a^~a]~a^~`\~`]~`\~_\~`\~_\~^[~^[~^[~^Z~^Z~]Y~]Z~\Y~]Y~\Y~\Y~[X~[X~[X~ZW~ZW~ZW~ZV~ZV~YV~YV~YV~XU~XU~XU~XU~XT~WT~WS~VS~VS~VS~VS~VS~VS~UR~TQ~UR~UQ~TP~UQ~TQ~TP~TP~SP~SP~SP~RO~RO~RO~RO~RN~RN~RN~RN~QM~QM~PM~PM~PM~PL~QM~PL~PL~OL~OL~OL~OL~OK~OL~NK~OL~NK~NK~NK~NK~NK~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MI~MI~MI~LI~MI~LI~LH~LH~LH~LH~LI~LI~LI~KH~LI~KH~LH~LH~KH~KH~KH~KH~KG~KH~JG~JG~JG~KG~KG~KG~KG~KG~JG~JG~JG~KG~KG~KG~KG~KG~KG~KG~KG~JG~JG~JG~JG~JF~JG~KG~KG~KG~JF~JG~JG~JG~KG~JG~KG~JG~KG~JG~KG~KG~KG~KG~KG~KG~KG~KG~JG~KG~JG~KH~KG~KG~JG~KG~JG~KG~KG~KG~KG~KG~KH~KG~KG~KG~KG~KG~KH~KH~KH~KH~KH~KH~LH~KH~KH~LH~LI~LH~LH~LH~LI~LI~LI~LI~LI~LI~LI~LI~LI~MI~MI~MI~MI~MI~MI~MI~MI~NJ~NJ~MJ~NJ~NJ~MJ~NJ~NJ~NJ~NJ~NJ~OK~NK~NJ~OK~NJ~OK~OK~OK~OK~PL~PL~PL~PL~PL~PL~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~RN~RN~QM~RN~RN~RN~QM~RN~RN~RN~RO~RN~RN~RN~SO~SO~SO~RN~SO~SO~SO~SO~SO~SO~SO~TO~SO~TP~TP~SO~SO~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~UP~UP~UP~UP~UQ~TP~UP~UP~UP~UP~TP~TQ~UQ~TQ~TP~UQ~UQ~UQ~VQ~TQ~UQ~UQ~UQ~UQ~UQ~UQ~VR~UQ~UQ~UQ~UQ~UQ~VQ~VR~VQ~UQ~UQ~VR~VR~UQ~VR~VR~VR~VQ~UQ~UQ~VR~VR~VR~VR~VR~VR~VR~VR~UQ~VR~VR~VR~VR~VR~VR~VQ~VR~VR~VR~VR~VR~VQ~VR~VR~VQ~VQ~WR~WR~VR~WR~VQ~VR~VR~VR~VQ~VR~UQ~VR~VR~UQ~VQ~VQ~UQ~VQ~VQ~VQ~VQ~VQ~UQ~VR~UQ~UP~VQ~UQ~UQ~UP~UQ~UQ~UQ~UQ~TP~UQ~UP~UQ~TP~TP~TP~TP~TP~TP~TP~TO~TP~TP~TO~TO~TP~TP~SP~SO~SP~TP~SO~SO~SO~SO~SO~SO~SO~RN~SO~SO~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~QN~QM~RN~QM~QM~QM~QM~QM~QM~QM~QM~PM~PM~PM~PM~QM~PL~PM~PL~PM~PL~PL~PL~PL~OK~PL~OK~OK~OK~OK~OK~OK~NK~OK~OK~NK~NK~NK~NK~MJ~NJ~MJ~MJ~MJ~MJ~MJ~MI~MI~MI~MI~MJ~MI~LI~LI~MI~MI~MI~MI~LH~LH~MI~LH~LH~LI~LH~MI~LH~LH~LH~LH~KH~LH~LH~LH~LH~KH~KH~KH~KH~KG~KH~KG~KH~KG~KG~KG~JG~JG~JG~JG~JG~JG~JG~JG~JF~JF~JG~JG~JF~JF~JF~JG~JF~JG~JG~JG~JF~KG~KG~JF~JF~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~KG~JF~JF~JG~JG~JF~JG~KG~JG~JG~JG~JG~JG~JG~JG~JG~JG~JG~KG~KH~KG~KG~KG~KH~KG~KG~KG~LH~LH~KH~KH~LI~LI~LI~LI~LI~LH~LI~LI~LI~LI~LI~MJ~MI~MI~MJ~MJ~MJ~NJ~MJ~NJ~NJ~NK~NJ~NJ~OK~OK~NK~NK~NK~OL~OL~OL~OL~PM~PL~PM~PM~PM~PM~PM~QM~QM~PM~QN~QO~RO~QN~QN~RN~RO~RO~SO~RO~SO~SP~SP~TP~TQ~TQ~TQ~TQ~UR~UR~UR~UR~UR~UR~UR~VS~WT~WT~WT~WT~WT~XT~XU~XU~YV~YU~YV~YU~YV~ZW~ZW~ZW~ZW~[X~[X~[W~[X~\Y~\Y~]Y~]Z~^Z~^Z~^[~^[~^[~_[~`\~`]~`]~`]~`]~`]~a]~b^~b^~b^~b^~b_~c`~c`~da~d`~ea~ea~ea~fb~fc~gc~gc~gc~hd~hd~he~he~jf~jf~jf~jg~kh~kg~kg~lh~lh~mi~mi~nj~mj~nj~nk~nk~ol~ol~pl~qm~qm~qm~rn~qn~rn~rn~so~so~so~tp~tp~uq~uq~uq~ur~ur~vs~wt~wt~xu~xu~yv~yv~xu~yv~yv~yv~yv~zv~zv~{x~|x~|x~|x~|x~}z~}z~?=?=@>@>@>@?@?@?@?@?@?A?A@B@B@BACACACACACACBCBDBECECECECEDEDEDFDFDFDFDFDFDFEGEGEGEFEGFGFGFHFHFHFHFHGHGIHIGIGJHIHIHIGIHJHJHJHJHJHIHIHIHIHJHJHIHIHJHIHIHIHIHIHIHIHIHIHIHIHIHIHIHIHIHIHIHIGIGJHIHIHIHJHIHIHHGHGHGHFHGHFHFGFHFGFGFGFGFFEGEGEGEFEFEFEFEFDFDEDEDEDFDECECECECDCDCDCDBDBCBCBCBCBCACACACACAB@BAB@B@A@A?A?A?A?A?@?@>@>@??>?>?>?=?=?=?=?=?=?=?=}y~}y~{w~{w~zv~zv~zv~zv~yu~yu~yu~xu~xu~xu~vs~vs~vs~ur~tq~tq~tq~tq~sp~to~so~ro~rn~rn~rn~rn~pl~qm~pl~pl~ok~nj~nj~nj~nj~nj~mj~mi~li~lh~lh~kh~kh~kg~kg~jf~jg~if~jf~ie~hd~hd~hd~hd~gc~gc~fc~gc~fc~ea~eb~ea~d`~d`~d`~c_~b_~c_~b^~b^~b^~a^~`]~a^~`\~`\~`\~_[~_[~^[~^[~^Z~^Z~^Z~]Z~]Y~]Y~\Y~\Y~\X~[X~[X~[X~[X~[W~ZV~ZV~ZV~ZV~YV~YV~YV~YV~XU~XU~WT~XU~WT~VS~VS~VS~VS~VS~UR~UR~UR~UQ~UQ~UQ~TQ~TQ~TQ~TQ~SP~TP~SP~RO~RO~SO~RN~RN~RN~RN~RN~QN~QN~PM~PM~PM~PM~OL~PL~OL~PL~OL~OL~OL~OL~OL~OL~OK~OK~OK~OK~NK~NK~NJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MI~LI~LI~LI~LI~LI~LH~LI~LI~LI~LI~LI~LH~LH~LH~LH~LH~LH~LH~KG~KG~KH~KG~KG~KG~LH~KH~KH~KH~KH~KG~KG~KG~KG~KG~KG~KG~JG~KG~KG~KG~KG~KG~JG~KG~KG~KG~JG~JG~KG~KG~KG~KG~KG~KG~JG~JG~KG~KG~KG~JG~JG~KG~KG~KG~KG~KG~KH~KH~KH~KH~JG~JG~KH~KG~KG~KG~KH~KG~KH~LH~LH~KH~LH~LH~LH~KH~LH~LH~LH~LH~LH~LI~LH~LH~LH~LH~LH~LH~LH~LH~LI~LI~LI~LI~LI~MI~MI~MI~MI~MI~NJ~MI~NJ~MI~MI~NJ~NJ~NJ~NJ~NK~NK~NK~NJ~NJ~NJ~OK~OK~NK~NK~OK~OK~OK~OK~OK~OL~PL~PL~PL~PL~PL~PL~PL~PL~QM~QM~QM~QM~QM~QM~QN~QN~QM~RN~RN~RN~RN~RN~RN~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SP~SO~TP~TP~TO~TP~TP~SO~TP~TP~TP~TP~TP~TP~UP~UP~UP~TP~UQ~UP~UQ~UQ~VQ~UQ~UQ~UQ~UQ~UQ~UQ~VQ~UQ~UQ~UQ~UQ~UQ~UQ~VQ~UQ~VR~VR~VR~VR~UQ~VR~VR~UQ~VR~VR~VR~VR~UQ~VR~VR~VR~VQ~WR~VR~VR~VR~VR~WS~VR~VR~WR~WR~VR~WR~WR~VR~VR~WR~WR~WR~WR~WS~VR~WR~WR~WR~VR~VR~WS~WS~WS~WR~WR~WR~WR~WS~WS~WS~WR~WR~WS~VR~VR~VR~WR~WR~WR~WR~VR~WR~VR~VR~WS~VR~WR~WR~WR~VR~VR~VR~VR~VR~VR~VQ~VR~VR~UQ~VQ~VR~VR~VR~VQ~VQ~UQ~VQ~VQ~UQ~VQ~VQ~VQ~UQ~UQ~UP~VQ~UP~UQ~UQ~UQ~TP~UP~TP~TP~UQ~TP~TP~TP~TP~TP~TP~TP~SP~SO~TP~TP~SO~SP~SO~SO~SO~SO~SP~SO~SO~SO~SO~SO~SO~SN~RN~RO~RO~RN~RM~QM~QM~RN~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~PL~PL~PL~PM~PL~PL~PL~OK~PL~PL~PL~OL~OK~OK~OK~OK~OK~OK~OK~OK~NJ~OK~NJ~NJ~NJ~NJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~LH~MI~MI~LH~LH~LH~MI~LI~LH~LH~LH~LH~LH~KH~KH~KH~KG~KH~KG~LH~KH~KH~KH~KH~KH~KG~KG~KH~KG~KG~JG~JG~JG~KG~JG~JG~JG~KG~JG~JG~JF~JG~JG~KG~JG~JG~JG~JF~JF~JF~KG~JG~JG~JG~JG~JG~JG~KG~JG~JG~JG~JG~KG~KG~JG~KG~KG~KG~KG~KG~KG~KG~KH~JG~KH~JG~KH~KH~KH~KG~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~LI~LI~LI~LI~MI~LI~MI~LI~MI~MJ~MI~NJ~NJ~MJ~NJ~NJ~NK~NK~NJ~NK~NK~OK~OK~OL~OL~OL~OL~OL~OL~PL~PL~PL~PL~QM~PM~PM~PM~QM~QM~QM~QN~QN~QN~RO~RO~RO~RO~SO~RO~RO~RP~SP~SP~SP~TQ~TP~TQ~TQ~UQ~UQ~UR~UR~UR~UR~VS~VS~VS~WT~WT~XU~XT~XU~XU~XU~XU~XU~YV~YV~ZW~ZW~ZV~ZW~ZW~ZW~[W~[X~\Y~\Y~\Y~]Z~]Z~]Z~^Z~^Z~^[~_[~_[~_[~_\~_\~_\~`]~a]~a]~a]~a^~b^~b_~b_~c_~c`~c`~d`~da~ea~fb~fb~fb~fc~gd~gd~hd~hd~hd~ie~ie~jf~jf~jf~kg~kg~kh~lh~lh~mi~mi~mi~nj~nj~nj~ok~ok~pl~qm~qm~qm~qm~rn~rn~rn~so~rn~rp~sp~tp~tp~tq~tq~ur~vs~vs~vs~vs~wt~xu~yv~yv~yv~yv~yv~zw~zw~zv~zv~{x~|x~|x~|x~|x~}z~}z~?=?=?=?>?>@>@>@?@?@?@?A@A@A@A@B@CABACACACBCBCBDBDCDCDCECEDEDEDEDFEFEFEFEGEFEGEGFGFGFGFGFHFHFHGHGHGIGHGHGIGIHIGIHIHIHJHJIJIJHJHJIJIJIJHJHJHJHJIJIJIJIKIKIJIJIKIJIJHKIJIJIJIJIJIJIJIJHJIJIJHJHJHJHJIJIJIJIJHJHIHIHIHIHIHHGIGIGIGHFHGHFHGGFGFGFGFGFGFGFGFGFFEGEFEFDFDEDEDEDEDEDEDDCECECDCDCDCDCCBDBDBDBCADBCABABABAB@B@B@B@B@B@B@A?A?@?@>@>@>?>@>@>?=?=?>?=?=?=|x~|x~|x~{w~{w~{w~zv~{w~{w~{w~zw~yv~yu~xt~xt~ws~ws~ws~vs~tq~ur~ur~tq~so~to~tq~ro~ro~rn~rn~rn~pm~qm~pl~pl~ok~ok~nj~nj~nj~nj~mi~mi~lh~mi~mi~lh~kh~jg~kg~kg~jf~jf~ie~if~ie~he~he~hd~gd~gc~gc~fc~fb~fb~eb~ea~da~d`~c`~c`~c_~c_~b_~b^~a^~a^~a^~a]~`]~`\~`\~_\~_\~_[~_\~^[~]Z~^[~]Z~]Z~]Y~\Y~\Y~\Y~[X~[X~[X~[W~ZW~ZW~ZW~YV~ZV~YV~YU~YU~XT~XU~XU~WT~WT~VS~WT~VS~VS~VS~VS~UR~UQ~UR~UR~UR~TQ~TQ~TQ~SP~SP~SP~SP~SP~SP~RO~RO~RO~RN~RN~RN~RN~QN~QM~QM~PM~PM~PM~PM~PL~PM~PM~OL~OL~OL~OL~OL~OK~OK~OK~OK~OK~OK~OK~MJ~NK~NK~NK~MJ~MJ~MJ~MJ~MJ~MJ~MI~LI~LI~LI~MJ~LJ~LI~LI~LI~LH~LH~LH~LH~LH~LH~LH~LI~LH~LH~KG~LH~KH~LH~LH~KH~KH~KH~KH~KH~KH~KG~KG~KG~KG~KH~KG~KG~KG~KG~KG~KH~KG~KG~KG~KG~JG~KG~KG~KG~KG~JG~KG~KG~KG~KH~KG~KG~KG~KG~KH~KG~KH~KH~KH~KG~KG~KH~KH~KG~KH~KH~KG~LH~LH~LH~LH~KH~LH~LH~LH~LH~LH~LI~LH~LH~LI~LH~LI~LI~LI~LI~LI~LI~LI~LI~MI~MI~LI~MI~LI~MI~LI~MI~MI~MI~MI~NJ~MJ~MJ~NJ~NJ~NJ~NJ~NJ~NJ~MJ~NJ~OK~NK~NK~NK~OK~NJ~OK~OK~OK~OK~PL~PL~OK~OK~PL~OL~PL~PL~PL~PM~PL~QM~QM~QM~QM~QM~QM~QM~QM~RN~RN~RN~RN~RN~RN~RN~SN~RN~SO~SO~SO~SN~SO~SP~SP~SO~SO~SO~TO~TP~SO~SO~TO~TP~TP~TP~TP~TP~TP~UP~UP~UP~UP~UP~UQ~UP~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~VR~VR~VR~VR~VR~VR~VR~VR~VR~VR~VR~VR~VR~VR~VR~VR~WS~VR~VR~VR~VR~WR~VR~WR~WS~VS~VS~WS~VS~WS~WS~WR~WR~VR~WS~VR~WR~WS~WS~WS~WS~WS~WR~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WR~WS~WS~WS~WS~WS~WS~WS~WR~WR~WR~XS~WS~WS~WR~WR~WS~WR~WS~WS~WS~WS~WS~WR~WS~WR~WS~WR~WR~WR~WS~VR~VR~VR~VR~VR~VR~VR~VR~VR~VR~VR~VQ~VR~VQ~UQ~UQ~VR~UQ~UP~VQ~VQ~VQ~VQ~UQ~UQ~UQ~UQ~UQ~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~SO~SO~TP~SO~SO~SO~SO~RO~SO~RO~RN~RN~SN~RN~RN~RN~RN~RN~QM~QM~QM~RN~QM~QM~RN~QM~QM~PL~QM~QM~PL~QM~PL~PL~PL~OK~PL~PM~PM~PL~OK~OK~OK~OK~OK~OL~OK~NK~OK~OK~NJ~NK~NJ~NK~NJ~NJ~NJ~NJ~MI~MJ~NJ~MJ~MJ~MI~MI~MJ~MI~MJ~MI~MJ~MJ~MI~MI~MI~MI~MI~LH~LI~LI~LI~LH~LI~KH~LH~LH~LH~KG~KH~LH~KH~LH~KH~KH~KH~KH~LH~LH~KH~KG~KG~KG~KH~KH~KG~JG~KG~KH~JG~KG~KG~JG~KH~JG~JG~JG~KG~KG~KG~KG~KG~KG~JG~JG~JG~JG~JG~KG~JG~JG~JG~JG~KG~JG~KH~KH~KG~KG~KG~KG~KH~KH~KH~KH~KH~KH~KG~KH~KH~KG~KH~KH~KH~KH~KH~KH~LI~LI~LI~LI~LI~MI~LI~LI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MJ~MJ~NJ~MJ~NK~NK~NJ~NJ~NK~NJ~NK~NK~NK~OK~OK~OL~OL~PL~PL~PL~PL~QM~PL~PM~PM~PM~QM~QN~QM~QN~QN~QN~QN~RO~RO~RO~RN~SO~SO~SP~SP~SP~SP~TQ~TQ~TQ~UR~TQ~UR~VS~VR~UR~UR~VS~VS~WT~WT~WT~WS~XT~XT~XU~XU~YU~YU~YV~YV~YV~ZW~ZW~ZW~[W~[W~[X~[X~[X~\Y~]Y~\Y~]Z~]Z~^Z~^Z~^Z~^[~_[~_\~`\~_\~_\~`\~`]~a]~a^~a^~b_~b_~c`~c`~d`~d`~da~da~ea~ea~fb~fb~fb~gc~gc~hd~hd~hd~ie~if~ie~jf~jf~jf~kg~lh~lh~lh~mi~mi~mi~nj~nj~ok~ok~pk~pk~qm~qm~qn~so~rn~qm~rn~rn~so~so~so~tp~tq~tq~tq~vs~vs~vs~vs~vs~wt~wt~wt~yv~yv~yv~yv~{w~{x~{x~{x~{x~|x~|x~~z~~z~~z~~z~?=?>?>?>@>@>@>@>@?@?A?A@A@A@B@B@CABACACBDBDBDBDBDCDCDCECECEDEDFDFEFEFEFEGEGFFEGEGFGFHFHFHFIGIGIGIHJHIHIHJHJHJHJHJHJHJIKIJIJIJILJKIKIKIKIKIKIKIKIKIKIKJKILJLJLJKJLJLJLJKIKIKJLJKJKJKJKJKJLJKIKJKIKJKJKIKIJIKIKIJIJIKIJHJHJHJHJHJHIGIGIGIGHGIGIGIGHGHGHFHFHFHFHFGEGEGEGEFEFEFEFDFDFDEDEDFDEDEDECDCDCDCDCDBDBDBDBCACABACACAB@B@B@B@A?B@A?A?B@A?A?@>@>@>?>?>@>?=?=?=?=>=><><|x~|x~|x~{w~{w~|x~{w~zv~{w~yu~yu~yu~xu~ws~wt~vs~vs~vs~vq~tq~tq~tq~tq~tq~ro~ro~qm~ro~qm~qm~pm~pm~ok~ok~ok~nj~nj~nj~nj~mi~mi~mi~lh~lh~lh~kg~kg~kg~jf~jf~jf~jf~ie~he~hd~hd~hd~gc~hd~gc~fc~fb~fc~ea~eb~ea~da~d`~c_~c_~c_~b^~b_~b^~a^~a^~a]~`]~`]~_[~_\~_[~_[~^[~^[~]Z~]Z~]Z~]Y~]Y~\Y~\Y~\Y~\X~[X~[X~[W~[W~ZV~YV~ZV~YV~YV~YU~YU~XU~XU~WT~XT~WT~WT~WS~WS~WS~VR~UR~VS~VS~UR~UR~TQ~TQ~TQ~TP~TP~SP~SP~SP~SP~RO~RO~RO~RO~RO~RO~RO~QN~QN~QN~QN~QM~PM~PM~PM~OL~OL~OL~PM~OM~OL~OL~OL~NK~NK~NK~NK~NK~NK~NK~NK~NJ~NJ~NJ~NK~NK~NJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~LI~MI~LI~LI~LI~LI~LI~LI~LI~LI~LI~LI~LH~LH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KG~KG~KG~KH~KH~KG~KG~KG~KG~KH~KG~KH~KG~KG~KG~KH~KH~KG~LH~KH~LH~KG~KG~KH~KG~KH~KH~KH~KH~LH~LH~KH~LH~LH~LH~LH~LH~MI~LH~LH~LH~LI~LH~LH~LH~LH~LH~MI~LI~LI~LI~LH~LI~LI~MI~LI~LI~MI~LI~LI~MI~MI~MI~MJ~MI~NJ~MJ~MJ~MI~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NK~NJ~NJ~OK~OK~OK~OK~OK~OK~OK~OK~OK~PL~OK~OL~OL~PL~PL~PL~PL~PL~PL~QM~QM~QM~QM~QN~QM~RN~QM~RN~RN~RN~RN~RN~SN~RN~SO~SO~SO~SN~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~TP~TP~TP~UP~UP~TP~UQ~UP~UP~UP~UP~UQ~UP~UP~UQ~UQ~UQ~UP~UQ~UQ~UQ~UQ~VR~VR~VR~VR~VR~VR~VR~VR~VR~VR~VR~VR~WS~VR~VR~VR~VR~VS~VS~VS~VR~VS~WS~WS~WR~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~XT~WS~WS~WS~WS~WS~WS~XT~XT~XT~XS~XS~WS~WS~WS~WS~XT~WS~WS~WS~XT~XT~XT~WS~WS~XT~XT~XT~WS~XT~XT~XT~XT~XT~WS~XS~XS~XS~XT~XT~WS~WS~XT~WS~WS~XT~XT~XT~WS~XT~WS~WS~WS~WS~WS~WS~WS~XS~WR~WS~XS~WS~WR~WS~WS~WS~WS~WS~WR~WS~WR~WR~WR~WR~WR~VR~VR~VR~VR~VR~VR~VR~VR~VQ~VQ~VQ~VQ~VQ~VQ~VQ~VQ~UQ~VQ~UP~UQ~UQ~UQ~UQ~TP~UQ~TP~TP~TP~TP~TP~TP~TP~TP~TQ~SP~TP~TP~TP~SO~TP~TO~TP~SO~SO~SO~SO~SO~SO~SO~SO~SO~RN~RN~RN~RN~RN~RN~RN~QM~RN~RN~QM~QM~QM~QM~PM~PM~QM~PL~QM~QM~PL~PM~PL~PL~PL~OK~PL~PL~PL~OK~OK~OK~OK~OK~OK~OK~NK~NK~OK~NK~NJ~NJ~NK~NK~NJ~MJ~NJ~MJ~MJ~NJ~MJ~MI~MJ~MJ~NJ~MI~MJ~MI~MI~MI~MI~MI~MI~MI~MI~LH~LH~LI~LH~LI~KH~KH~LH~LH~LH~LH~LH~LH~LH~LH~LH~KH~KH~KH~KH~KH~KH~KH~KH~KG~LH~LH~KH~KH~KH~JG~KH~KH~KH~KG~KH~JG~KG~KG~KH~KG~KH~KG~KG~KG~KH~JG~KH~KH~KG~KG~KG~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~LI~LI~LI~LH~LH~LI~LI~LI~LI~LI~MI~MI~MI~MI~MI~MI~MI~MI~MJ~MI~MI~MJ~MJ~MJ~MJ~NJ~MJ~NK~NK~NK~NK~OL~NK~NK~OK~OK~OK~PL~OL~PL~PL~PL~PL~PM~QM~PM~PM~PM~PM~QN~QN~QN~QN~QN~RO~RO~RN~RO~SO~SP~SP~SO~SP~SP~SP~TQ~SP~SP~TQ~UR~UR~UR~UR~UR~VR~VS~VS~WT~WT~WT~WT~XT~XU~XT~XU~YU~YU~YV~ZV~YV~ZW~ZW~ZW~ZW~[W~[W~[X~[X~\Y~]Z~\Y~]Y~]Z~]Z~]Z~^Z~_[~_[~_\~_\~_\~`]~`]~a]~a]~a]~a^~b_~b_~c_~c_~c`~d`~ea~ea~ea~fb~fb~fb~gc~gc~gc~hd~hd~hd~ie~ie~ie~jf~jg~jf~kg~kh~lh~mi~mi~mi~mj~mj~nj~nj~ok~qm~qm~qm~qm~qm~rn~rn~so~so~so~so~tp~tq~tq~tq~tq~vr~vr~vs~vs~vs~wt~wt~wt~xu~yv~yv~zw~zw~zw~{x~{x~|x~|x~|x~|x~~z~?=?=?=@>@>@>?>?>@?@?@?A?A?A@B@A@B@B@BACACACBCBDBDBDBDCECECEDEDEDEDEDFDFDFEFEGEGFGFHFHFGFHFHFHFIGIGIGIGJHJHJHJHJHJHKIJHKIKIKIKIKIKIKIKIKIKJKIKJKJKILJLJKILJLJLJLJLJLJLJLJMKMKMKMKLJLJLJLJLJLJMKMKMKMKMKMKLJLJMKLJLJLJLJLJLJKIKIKIKIKIKIKIJIJHJHJHJHJHJHJHJHIGIGIGIGIGIGIGHFGFGEHFGEGEGEGEGEGEGEFDFDFDEDEDEDEDECECDCECDCDBDCCBCBCBCACACACABACAB@B@B@B@B@A?A?A?A?@?@?@>?>@>?>?>?=?=?=><?=><><}y~}y~|x~|x~zv~{w~yu~yu~yv~yu~yu~yu~xu~xs~vs~vs~vs~tq~tq~tq~tq~tq~ro~ro~ro~ro~qn~pm~pm~pm~pm~pl~pl~ok~nj~nj~nj~nj~mi~mi~mi~li~lh~lh~lh~kg~jf~jf~jf~ie~jf~ie~ie~he~hd~gc~gc~fb~gc~gc~fb~eb~ea~d`~d`~c`~c`~d`~c_~b^~b^~a^~a^~a]~`]~`]~`]~`\~_\~_[~_[~_[~^[~^[~]Z~]Z~]Y~\Y~\X~\X~[X~[X~[X~[X~[X~ZW~YV~ZV~ZW~YV~YU~YV~XU~XU~XU~XU~WT~WT~VS~VS~VS~VS~VS~UR~UR~UR~UR~UR~TQ~TQ~TQ~SP~TQ~SP~SP~SP~SP~SP~RO~RO~QN~RO~RO~QN~QN~QN~QN~QN~PM~PM~PM~PM~PM~PM~OL~OL~OL~OL~OL~OL~OL~NL~NL~NL~NK~NK~NK~NK~NK~NK~NJ~NJ~MJ~MJ~MJ~NJ~MJ~NJ~MI~MI~MJ~MI~MI~LI~LI~MI~LI~LI~LI~LI~LI~LI~LI~LI~LH~LI~LI~LH~KH~LH~LH~LH~LH~LI~LH~LH~LH~LH~LH~LH~LH~KH~KH~LH~LH~KH~KH~KH~KH~LH~LH~KH~LH~KH~KH~KH~LH~LI~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LI~LH~LI~LH~MI~MI~MI~MI~LI~LH~LH~LI~LH~LH~MI~MI~MI~MI~MI~MI~MI~MI~MJ~LI~LI~LI~MJ~MI~NJ~MI~MJ~NJ~NJ~NJ~NJ~MJ~NJ~NJ~NJ~NJ~NJ~NJ~NK~OL~OK~OK~OK~OK~OK~OK~OK~OK~OK~OL~OL~OL~PL~PL~PL~PL~PL~PL~PL~PL~PL~QM~QM~RN~QM~QM~RN~RN~RN~RN~RN~RN~RN~RN~RN~SO~SN~SO~SO~SO~SO~SO~TP~SO~SO~TP~SO~SO~TP~TP~TP~TP~UP~UQ~UP~TP~UP~UQ~UQ~UP~UQ~UQ~UP~VQ~UQ~VR~VR~VQ~VQ~VQ~VQ~VR~WR~VR~WR~VR~VR~VR~VR~VS~WR~WR~WR~WR~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~WS~XT~XT~WS~WS~WS~WS~XT~XT~XT~WS~WS~XS~XT~XS~XT~XT~WT~XT~XT~XT~XT~XT~XT~YT~XT~XT~XT~XS~XS~XT~XT~XT~XT~YT~XT~XT~XT~YT~XT~XT~XT~XT~YT~YT~YT~XT~YT~XT~XT~YU~XT~YT~XS~XT~YT~XT~XU~YU~YU~YT~XT~XT~XT~XT~XT~XT~XS~XS~XT~XT~XT~XS~XS~XS~XT~XT~XT~XS~XT~WS~XT~WS~WS~WS~WS~XS~WS~WS~WS~WS~WR~WR~WS~WS~WR~WR~WS~WR~WS~WR~WR~VR~VR~VR~VR~WS~VR~VR~VQ~VQ~VR~VR~VQ~VQ~UQ~UQ~UR~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~TP~TP~TP~TP~TP~TP~TO~TP~TP~TP~SO~TP~SO~SO~SO~SO~SO~SO~RO~SO~SO~RN~RN~RN~RN~QM~RM~QM~RN~QM~QM~QM~QM~QM~QM~QM~QM~QM~PL~QM~PL~PL~PL~PM~PL~PL~OL~OL~OK~PL~OK~OK~OK~OK~OK~OK~OK~OK~NJ~NJ~OK~NJ~NJ~NJ~NJ~NJ~MJ~MJ~MJ~NJ~NJ~NJ~MI~MI~MI~NJ~MJ~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~LI~LI~LH~LH~LH~LH~LH~LH~LH~LH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KH~KG~KG~KH~LH~LH~KG~KH~LH~LH~KH~LH~KH~LH~KH~KH~LH~JG~KH~KH~KH~LI~KH~KH~KH~LH~LH~KH~KH~LH~LH~LH~LI~LI~LH~LI~LI~LH~LI~LI~LI~LI~LI~LI~LI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MJ~MJ~MJ~MJ~MJ~MJ~MJ~NJ~NK~NK~NK~NK~NK~NK~OL~OL~OL~OL~OL~OL~OL~PL~PL~PM~PM~PM~PM~PM~PM~QN~QN~QN~QN~QN~QN~RN~RO~RO~RN~SO~SO~SP~SP~SP~SP~TQ~SP~TQ~SP~TQ~TQ~TQ~UR~UR~UR~VR~VS~VS~VS~VS~WT~XU~XU~XT~XU~XU~YV~ZW~YV~YV~ZW~ZW~ZW~ZW~[X~ZW~[X~[X~[X~\X~\Y~]Y~]Z~^Z~]Z~^Z~_[~_[~_[~`\~_\~`\~`\~`]~a^~a^~b^~b^~b_~c_~c_~d`~d`~da~d`~ea~ea~eb~fb~fc~gc~gd~gd~ie~ie~hd~ie~jf~jf~jf~jf~kg~kg~lh~lh~mi~mi~mi~nj~ok~ok~ok~ok~pl~pl~qm~qm~qm~so~so~so~to~to~to~tq~tq~tq~tq~vs~vs~vs~vs~xu~xu~xu~yv~yu~yv~yv~{w~{w~{x~|y~|y~|x~|z~~z~~z~~z~?>?>?>?>@>@>@>@?@?@?A?A?A@A@A@B@B@B@CACACACBDBDBDBECDCEDEDDCEDEDFDFDFDFEFDGEGEGFGFGFHFHFIGHFHFIGHGIHIHIHJHJHJHJIJIKIKIKJKIKJKJKJKJKJKILJKJLKKJLKLJLKLKLJLKLKMKMKMKMKMKMKMKMKMKMKMLNLMKMKMKMKMKMKNLNLNLNLMKNLMKMKMKMKMKMKMKLJLKLJLJLJKJKJKJKJKJKIKIKIKIJHJIJIJIJHJHJHJHIHIGIGHGHGHGHFHFHFHFHFHFHFGEGEGEGEGEFEFDGEFDFDEDEDDCDCECDCDBCBDBDBDBDBCBDBCBCBCACACACAB@B@B@B@A?A?A?@>@>?>@>?>?=?=?=?=?=><><><|x~|x~zx~{w~{w~{w~yu~yu~yu~yu~yu~ws~ws~ws~ws~vs~vs~tq~tq~tq~sp~ro~sp~ro~ro~rm~rm~pm~pm~pl~pl~pl~nk~nj~nj~nj~nj~nj~mi~mi~mi~lh~lh~kg~kg~kg~jf~jf~jf~ie~ie~hd~ie~hd~hd~gc~fc~fb~fb~fb~ea~ea~da~ea~c`~c`~b_~b_~b_~b^~b^~a^~a^~a^~`]~`]~_\~_[~_[~^[~^[~^[~^[~^Z~]Z~]Z~]Y~\Y~\X~\X~[X~[X~[X~[X~ZW~ZW~ZV~YV~YV~YV~YU~XU~XU~XU~XT~XT~WS~WT~VS~VS~VS~VS~VR~UR~UR~UR~UR~TQ~TQ~TQ~TQ~TQ~TQ~SP~SP~SP~RO~SP~RO~RO~RO~RO~RO~QN~QN~QN~QN~PM~PM~PM~PM~PL~OL~OL~PL~OK~OL~OL~OL~OL~OK~OK~NK~NK~NK~NK~NK~NJ~NK~NJ~NJ~MJ~NK~NK~NJ~NJ~MI~MI~MJ~MJ~MJ~MJ~MI~MI~MI~MJ~MI~LI~LI~MI~MI~LI~LI~LI~LI~LI~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LI~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LI~LI~LI~LI~LI~LI~LI~LI~LI~MI~MI~MI~LI~LI~LI~LI~LI~LI~LI~LI~MI~MI~MJ~NJ~MJ~NJ~MJ~NJ~NJ~NJ~NJ~NJ~NJ~NK~NJ~NK~NK~NK~NK~NK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OL~OK~OL~PL~OK~PL~PL~PL~PL~PL~PL~PL~QM~QM~PL~PL~QM~QM~QM~QM~QM~RN~RN~RN~RN~RN~RN~RN~RN~SO~SO~SO~RN~SO~TO~SO~SO~TP~TP~TP~TP~TP~TP~TP~TP~TP~UP~UQ~UQ~UP~UP~UQ~UQ~VQ~VQ~VR~UQ~VR~VR~VR~VR~VR~VR~VR~VS~VR~VR~VR~VS~VS~VS~WS~WS~WS~WS~WS~WR~WS~WS~WS~WS~WS~WT~WS~WS~WS~WS~XT~WS~XT~XT~XT~XT~XT~XT~XT~XT~XT~XS~XT~XT~XT~XS~XU~YT~YT~YT~XT~YT~YU~YU~XT~XT~XT~XT~YU~XT~XT~XT~XT~YU~YU~YU~YU~YU~YU~YU~YU~YU~YT~YT~YT~YT~YT~XT~YU~XT~YU~YU~YU~YT~YT~YT~YU~YT~YT~YU~YU~YU~YU~YU~YU~YU~YU~YU~YU~YU~XT~YU~YU~YU~YU~YU~YT~YT~YT~YT~YT~YT~XT~XT~XT~YT~YT~XT~XT~XT~XT~XS~XT~XT~XT~XT~XT~XT~WS~XT~WS~XT~WS~WS~WT~WS~WT~WS~WS~WS~WS~WS~WS~WS~WS~VR~WR~WR~VR~VR~VR~VR~VR~VR~UQ~VR~UR~VR~UR~UR~UR~UR~UR~UQ~UQ~UQ~UQ~TP~TP~UQ~UQ~TP~TP~TP~TP~TP~TP~TP~TP~TO~TO~SP~SO~SP~SP~TP~SO~SO~SO~RO~SO~RO~RO~RN~RN~RN~QN~RN~QM~QM~RM~QM~QM~QM~QM~QM~QM~PM~QM~QM~PL~PM~PL~PL~PL~PL~PL~OK~OK~OL~OK~OK~OK~OK~OK~OK~OK~OK~OK~NJ~NK~NJ~NJ~NJ~NJ~MJ~NJ~NJ~MJ~MI~NJ~NJ~MJ~MI~MJ~MI~MI~MI~MI~MI~MI~MI~LI~MI~MI~MI~LI~LI~LI~LH~LH~LH~LI~LH~LI~KH~KH~LI~LH~LH~LH~LH~LH~KH~LH~KH~LH~KH~KH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LH~LI~LI~LH~LH~LH~LH~LI~LH~LI~LH~LI~LH~LH~LI~LH~MI~LI~LI~LH~LI~LI~LI~LI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MJ~MJ~MJ~MI~MJ~MI~MJ~MJ~MJ~MI~MJ~MJ~MJ~MJ~NK~NK~NK~NK~NK~NK~OK~OK~OL~OK~OL~OL~PL~PL~PL~PL~PM~PM~PM~PM~PM~PM~QN~QN~QN~QN~RN~RN~RN~SO~RO~RO~RO~SO~SP~SP~SP~SP~SP~TP~TP~TQ~TQ~UQ~TQ~UR~UR~VS~VS~VS~VS~WT~WT~WT~WT~XT~XU~XU~XU~YV~YV~YV~ZW~ZW~ZW~ZW~[W~[W~[X~[W~\Y~\X~\X~\X~\Y~]Z~]Y~]Y~^Z~^Z~^[~_\~_\~`\~_\~`\~`]~a]~a^~a^~b^~b^~b_~c_~d`~d`~d`~ea~ea~fb~fb~fc~fb~gc~gc~gc~hd~hd~hd~ie~if~if~jg~jf~kg~lh~kh~lh~mi~mi~mi~nj~ok~ok~pl~pl~pl~pl~pm~qm~rn~rn~ro~so~so~to~to~tq~tq~tq~vs~vs~vs~vs~vs~wt~wt~yv~yv~yv~zw~{w~{w~{w~|x~}y~|x~|x~~z~~z~~z~~z~@>?=@>@>@>@>A?A?A?A?A?A@A@B@B@B@CACACACBCBCBDBDCDCDCECEDEDEDEDEDFDFDFDFDGEGEGEGEHFGEHFHFHFIGIGIGIGIHIHIHIHJHJIJIKIKILJLJKJLJLKLKLKLKLKLKLKLKLKLKLKMKMKLKMLMKMLMLMLNMMLMLMLMLNLMLNLNLNMNMNLNMMLMLNLNMNMNMNMNLNMNLNMNLNMNLNLNLNLNLMKMLMKMKMKLKMKLKMKLKLJKJLJKJKJLJKJJIKIKIJIJHJHJHIHIHIHIGIHHGIGIGIGHGHFHFHFHFHFGEGEGEGEGEGEFDFDFDECECECECDCECDCDBDBDCDBDBDBDBCACACACAB@B@B@B@B@A?A?A?@>@>?>@>?=?=?=?=?=?=><><|x~|x~zw~zw~zv~zv~zv~yu~yu~yu~yu~xt~wt~ws~ws~vr~uq~ur~tq~tq~tq~ro~ro~ro~ro~qn~qn~qn~pm~pm~pm~pl~pl~ok~nj~nk~nk~mi~mi~mi~mi~lh~li~kh~kg~kg~kg~jf~jf~ie~ie~ie~ie~gc~gc~gc~gc~fc~fb~fb~ea~ea~ea~da~d`~c`~b_~c_~b^~b^~a^~a^~a^~a^~`]~`\~`\~_\~_[~^[~^[~^Z~]Z~]Z~]Z~\Y~\Y~\Y~\X~[X~[X~[W~[W~[W~[W~ZW~ZV~YV~YU~YV~YU~XU~XU~XU~XU~WT~WT~WT~VS~VS~VR~VR~VR~VR~UR~UR~TQ~TQ~TQ~TQ~TQ~SP~TP~SP~SP~SP~SP~RO~RO~RO~RO~QN~QN~QN~QN~QM~QN~PM~PM~QM~PM~PM~PM~PL~PL~PL~OL~OL~OL~OK~OK~OK~OK~NK~NK~NK~NK~NK~NK~NJ~NJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MI~MI~MI~MI~LI~MJ~LI~LI~LI~LI~LI~LI~MI~MI~MI~MI~MI~LH~LH~LI~LH~LI~LH~LH~LH~LH~LH~LI~LI~LH~LH~LI~LH~LH~LH~LI~MI~LH~MI~LH~LI~MI~LI~LI~LI~LI~LI~LI~LI~LI~LI~LI~LH~LI~LI~LI~LI~MI~MI~MI~MJ~MJ~MI~MJ~MI~MJ~MJ~MI~NJ~NJ~MI~NJ~MJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NK~NK~NK~NK~NK~NK~NK~OL~OK~OK~OL~OL~OK~OK~OK~PL~PL~PL~PL~PM~PM~PL~PL~PL~PL~PM~PL~PM~PM~QM~QM~QM~QM~QM~RN~QN~RN~RO~RO~RO~RO~SO~SO~SO~SO~SO~TP~TP~TP~TP~TP~TP~TP~TP~TP~UP~UP~UQ~UP~UP~UQ~UQ~UR~UQ~UQ~VR~VR~UQ~UQ~VR~VR~VR~VR~WR~VR~VR~WR~WS~WS~WS~WR~WS~WS~WS~WS~WS~WT~WS~WS~WS~WS~WS~WS~XS~XT~XT~XT~XT~YU~XT~YT~YT~XS~YT~XT~XT~XT~YU~YU~XT~XU~XT~YU~YU~YU~XU~YU~XT~YU~YU~YU~YU~YU~YT~YU~YU~YU~YU~ZV~ZU~YU~YU~YU~YU~ZV~YU~YU~YU~ZU~ZU~ZU~ZV~ZU~YU~YU~ZU~ZU~ZU~YU~ZU~YU~ZV~YU~YU~YU~ZU~ZU~ZU~ZV~ZU~YU~ZU~ZU~ZU~ZU~ZU~ZU~ZU~ZU~ZU~ZV~ZV~YU~YU~YU~YU~ZV~ZV~ZV~YU~YT~YU~YU~YU~YU~YU~YU~YU~YU~YU~YU~YU~YU~YU~YU~YU~YT~XT~YU~YT~YT~YU~YT~XU~XU~XT~XT~XT~XT~XT~XT~XT~XS~WS~XT~WS~XT~XS~XS~WR~WS~WR~WR~WR~VR~WS~WS~VR~VR~VR~VR~VR~VR~VR~UQ~VR~VR~VR~UQ~VQ~VQ~VQ~UQ~UQ~UQ~UQ~UQ~UP~UQ~TP~TP~TP~TP~TP~TP~TP~SO~TP~TP~TP~SO~SO~SO~SO~SO~SO~SO~SO~RO~RN~RN~RN~RM~RN~QM~QM~RN~QM~QM~QM~QM~QM~QM~QM~PM~PL~PM~PL~PL~QM~PL~PL~PL~PL~PL~OK~OK~OL~OK~OK~OK~OK~OK~OK~OK~OK~NJ~NJ~NJ~NK~NK~NJ~NJ~NJ~NJ~MJ~NJ~MJ~MJ~MJ~MI~MI~MI~MJ~MJ~MJ~MI~MI~MI~MI~MI~MI~LH~MI~MI~MI~LI~LH~MI~MI~LI~LI~LI~LH~LH~LI~LH~LI~LI~LH~LH~LH~LH~LH~KH~KH~LI~LH~LH~LH~LI~LH~LH~LI~LH~LH~LH~LH~LH~MI~LH~LI~LI~LI~MI~MI~LH~LI~LI~LI~LI~LH~MI~LI~LI~LI~MI~MI~MI~MI~MJ~MI~MI~MI~MJ~LI~MJ~MJ~MJ~MJ~MJ~NJ~NJ~MJ~NK~NJ~NJ~NK~OK~NJ~OK~OK~NK~OK~OK~OK~OK~OL~OL~PL~PL~PL~OL~PM~PM~PM~PM~PM~QN~PM~QN~QN~RN~RN~RO~RO~RO~RO~RO~SO~RO~SO~SP~SP~SP~TQ~TQ~TQ~TQ~UQ~TQ~UQ~UR~UR~UR~UR~VS~VS~VS~WT~WT~WT~WT~XT~XU~XU~YU~YV~YV~YV~ZV~YV~ZV~ZW~[W~[X~\X~\X~\Y~\Y~\X~]Y~]Y~^Z~^Z~^Z~^[~_[~_\~_\~_\~`\~`\~a]~a]~a]~b_~b_~b_~b^~c_~c_~c`~c`~da~ea~ea~eb~fc~fc~gc~gc~gc~gc~he~hd~if~if~jf~kg~kg~kg~lh~lh~lh~lh~mi~mi~mi~mi~ok~ok~pl~pl~pl~qm~qm~qm~ro~ro~ro~to~so~tq~tq~vq~vq~vs~vs~vs~vs~wt~wt~wt~yu~yv~zw~{w~{w~{x~{x~|x~|x~}y~~z~~z~~z~?=@>?>@>@>@>@?@>A?A?A?A?A@B@B@B@BACACACACBCBCBDBDBDCDCDCDCECEDEDEDEDFDFDFDFDGEGEGEGFHFHFHFHGIGIGIGIGJHJHJHJIJIJIKIKIKJLJLKLKLKMKMKMKMLMLMLMLMLMLMLMLMLMLMLMLNLNLMLMLNLNLNLNMNMOMOMNMNMNMNMNMNMNMNMOMNMONONONPNOMONONOMOMONONOMNMNMOMNMNMNMNMMLMLNMMLNLMLMLMLMKMLMKLJLJLJLJKJKJLJKIKIJIJIJIJIJIJHIHIHIHJHIHIGIGHGHGHFHGGFHFGFGFGEGEGEGEFDFDFDFDFDEDFDEDEDECECDCDCDCECDBDBDBCBCACACAB@B@B@A?A?A?A?@?A?@>@>?>?>?>?=?=?=?=?=><|x~|x~{w~{w~{w~zv~yu~zv~yu~yu~xt~xt~wt~ws~ws~vs~vs~ur~tq~sp~tq~tq~ro~ro~ro~rm~pm~pm~pm~pm~ol~ol~nk~nk~nk~nj~mi~mi~li~mi~kh~lh~lh~kg~kg~kg~jf~ie~ie~ie~ie~he~hd~gd~gc~fb~fb~fb~fb~ea~d`~da~c`~d`~c`~c`~b_~b^~b^~b^~a^~a^~`]~`\~`]~`\~_[~_[~_[~^[~^[~]Z~]Z~]Z~]Y~\X~\Y~\X~[X~[X~[X~[W~ZW~ZW~ZW~ZV~YV~YV~YU~YU~XU~XU~XU~XU~XT~WS~WT~VS~VS~VS~VS~VR~VR~VR~UQ~UQ~TQ~TQ~TQ~SP~SP~SP~SP~SP~SP~SP~RO~RO~RO~RO~QN~RN~QM~QM~QM~QN~QN~QM~PL~QM~QM~PM~PM~PL~PL~OL~OL~OL~OL~OL~NK~NK~NK~OL~NK~NK~OK~NK~NK~NK~MJ~MJ~NJ~MJ~MJ~MJ~NJ~NJ~MJ~NJ~MI~MI~MI~MJ~MJ~MJ~MJ~MI~MI~MI~MI~MI~LH~LI~MI~LI~LI~LI~LI~LI~MI~LI~LI~MI~LI~LI~LI~LH~LI~LH~LI~LI~LI~LI~LI~LI~LI~LI~LI~LH~MI~LI~LI~LI~LI~LI~LI~MI~LI~LI~MI~MI~LI~LI~MJ~LI~MI~MI~MI~MI~NJ~MI~MI~NJ~MI~MI~MJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NK~NK~NJ~NK~OK~NJ~OK~OK~OK~OL~OL~OL~OK~OL~OL~PL~OL~PL~PL~PL~PL~PL~PL~PL~PL~QM~QM~QM~QM~QM~QM~QN~QM~QM~QM~QM~RN~RN~RN~RN~QM~RN~RN~RN~SO~SO~SP~SO~SO~TP~TP~TP~SO~TP~TP~TP~TP~TQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~VQ~VQ~VR~VR~VR~VR~VR~VR~VR~WS~WS~VR~WS~WR~WR~WS~WR~WS~WS~WS~WS~XT~XT~XT~XT~XT~WS~XT~XT~XT~WS~XT~XT~XT~XT~XT~XT~XT~XT~YU~YU~YT~YT~YT~XT~YU~YU~YU~YU~YU~YU~YU~YU~YU~YU~ZV~ZV~ZV~ZV~ZV~ZV~YU~ZV~ZV~[V~ZU~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZU~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~YU~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZU~[V~[V~ZV~ZU~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZU~ZV~ZV~ZV~ZV~ZU~ZU~ZU~ZU~YU~ZU~YU~ZV~ZV~ZV~ZV~YU~YU~YU~YU~YU~YU~YU~YU~YU~YU~YU~YU~YU~YU~XT~YU~YT~YT~XT~XT~XT~XT~XT~XT~XT~XT~WS~WS~WT~WT~WS~WS~WS~WS~VS~WS~WS~WR~VR~VS~VR~VR~VR~VR~VR~VR~VR~VQ~UQ~VR~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~TP~TP~UP~TP~TP~TP~TP~SP~TP~SO~SO~SO~SO~SO~SO~SO~SO~RN~SO~SO~RN~RN~SO~SN~RN~QM~RN~QM~RN~QM~QM~QM~QM~PL~QM~PM~PM~PM~PM~PM~PL~PL~PL~OK~PL~PL~PL~OL~OL~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~NK~NK~NK~NK~NJ~NJ~NJ~NJ~NJ~NJ~MJ~NJ~MJ~MJ~MI~MJ~MJ~MI~MI~MI~MJ~MI~MI~MI~MI~MI~MI~MI~MI~LI~LI~LI~LI~LI~LI~LI~LI~LI~LH~LH~LI~LI~LI~LI~LI~LH~LI~LI~LH~LI~LI~LI~LI~LI~LI~LI~LI~LI~LI~LI~LI~LI~MI~MI~MI~LI~LI~LI~LI~MI~MI~MI~LI~LI~MJ~MJ~MJ~MI~MJ~MJ~MJ~MI~MJ~MJ~NJ~NJ~NJ~NJ~NJ~NK~NK~NJ~NK~OK~NK~NJ~OK~OK~NK~OK~OK~OK~OL~OL~OK~PL~PL~PL~PL~PM~PM~PM~PM~PM~QN~QN~QN~QN~RN~RN~RO~RO~SO~SO~SO~SO~SO~SP~SP~SP~SP~TP~SP~TQ~TQ~TQ~UQ~UQ~UR~UR~UR~UR~VS~UR~VS~WS~WT~WT~XU~XU~XU~XU~XU~YV~YV~YV~YV~ZW~ZW~ZW~ZW~[X~[X~\Y~\Y~\Y~\Y~]Y~]Y~]Y~^Z~^Z~^[~^[~^[~_\~`\~`\~`\~`\~a]~a^~a^~b^~b_~c_~c_~c_~d`~ea~d`~ea~ea~eb~fc~fb~fb~gc~hd~hd~hd~hd~ie~if~jf~jf~kg~kg~kg~lh~lh~lh~mi~mi~mi~nj~ok~ok~ok~pl~pm~qm~rm~rm~ro~ro~ro~sp~tq~tq~tq~tq~vq~vq~vs~vs~xs~xu~wt~wt~yv~yv~yv~{w~{w~{w~|x~|x~|z~~z~~z~?=?=?>@>@>@?@?@>@>A>A?A?A?A@B@B@B@CACACACACADBDBDBDBDCDCDCDCECECEDFDFDGEGEGEGEGEGEGEGEHFHFHFHGHGIGIHIHIHJIJIJIKIKIKJKJKJLJLJLJMKMKMLMLMLMLNMNMNMMLNMNMNMNMNMNMNMNMNMOMONOMOMNMOMOMOMPNPNOMPNPNPNPNONPNPNOMPNPNPNPNQOPOPNPNPNPNPNPNPNONONONPNPNPNONOMOMNMOMNLNMNLNMNLNLMKMKMKMLMLMKMKMKMKLJLJLJLJKJKJJIJIJIKIJHJIIHIHIHIGIGIGHGHGHGHGHFHFHFHFGFGEGEGEFDGEGEGEFDFDECECECECECECECECDBDCDBCACACACACAB@B@B@B@A?A?A?A?A?@>@>@>@>?=?=?=?=?=>=|x~|x~|x~{w~zv~zv~zv~zv~yu~xv~yu~xt~wt~wt~ur~vs~ur~ur~uq~tq~uq~so~so~so~ro~rm~qm~qm~pm~pm~pm~nk~pk~ok~nk~nk~nj~mi~mi~li~li~lh~lh~kg~kg~kg~jf~ie~ie~hd~ie~hd~hd~gc~gc~fc~fc~fb~fb~ea~ea~d`~d`~d`~d`~c_~c_~b^~b^~b^~a]~a]~`]~`\~`]~_\~_[~_[~^[~^Z~]Z~]Z~]Z~]Y~]Y~\Y~\Y~\X~\X~\Y~[X~[W~[W~[W~ZW~ZV~YV~YV~YV~XU~YU~XU~XU~WT~WT~WT~VS~VS~VR~VR~VR~UR~UR~UR~UQ~TQ~TQ~UR~TQ~TQ~SP~SP~SP~SP~SP~SP~RO~RO~RO~RN~QN~QN~QN~QN~QN~QN~QM~PM~PM~PM~PM~PL~PM~PM~PL~OL~OL~OL~OK~PL~PL~OK~OL~NK~OK~OK~NK~NK~NK~NJ~NJ~NK~NK~NK~NJ~NJ~NJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MI~MJ~MI~LI~LI~MJ~LI~MJ~MI~MI~LH~MI~MI~LI~LI~LI~MI~LI~LI~LI~LI~LI~LI~MI~MI~LI~MI~MI~MI~MJ~MJ~MI~LI~LI~MI~MI~MJ~MI~MI~MI~MJ~MJ~NJ~MJ~MI~MI~NJ~NJ~MI~NJ~NJ~MJ~NJ~NJ~NJ~NK~NJ~NJ~OK~NJ~NK~OK~NK~NJ~NK~NK~OK~OK~OK~OK~OL~OL~OK~OL~OL~PL~PL~PL~OL~PL~PL~PM~QM~PL~PL~PL~PL~QM~QM~QM~QM~QM~QN~RN~QM~QN~RN~RN~QM~QM~RN~RN~RN~SN~SN~RN~RN~SO~TP~TP~TP~TO~TP~TP~TP~TP~TP~UQ~UQ~UQ~UQ~UQ~UQ~UP~UQ~UQ~VR~VR~UQ~VR~VR~VR~VR~VR~VR~VR~WR~WR~VR~WS~WS~WS~WS~WS~WS~XT~WS~XS~XT~WS~XT~XT~XT~XT~XT~YT~XT~YT~YT~XT~YU~YU~YU~YU~YU~YT~XT~YU~YT~YT~YU~YU~YU~ZV~YU~YU~ZU~ZV~ZU~ZU~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZU~ZV~ZV~[V~[V~[V~[V~[W~[W~[W~[W~ZV~ZV~[W~[V~ZV~[W~ZV~ZV~[V~[V~[W~ZV~[W~[V~[W~\W~ZV~[W~[W~[W~[W~ZV~[V~ZV~ZV~[W~[V~[W~[V~[W~[V~[V~[V~[W~[W~[W~[V~[V~\W~[W~[W~[W~[W~ZV~[V~[V~[V~[V~[V~[V~[V~[V~[V~ZV~[V~[V~[V~[V~[V~[V~ZV~ZV~ZV~[V~ZV~ZV~[V~ZU~ZV~ZV~ZV~ZU~YU~ZU~ZV~ZV~ZU~YU~ZV~YU~YU~YU~YU~YU~XU~YU~YU~YT~XT~XT~XT~XT~XT~XT~XT~XT~XT~XT~XT~WS~WS~WS~WS~WS~VS~WT~VS~WS~WS~WS~WS~WS~VR~VR~VR~VQ~VR~VR~VR~UR~VR~UR~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~TP~TP~TP~TP~TP~SO~SO~TO~SO~SO~SO~SO~RN~RN~RN~SO~RO~RN~RN~RN~RN~RN~RN~RN~RN~QM~QM~QM~QM~QM~QM~PM~PL~PM~QM~PL~PL~PL~PM~PL~PL~PL~PL~PL~PL~OK~OL~OK~OL~OK~OK~OK~OK~OK~OK~OK~NK~OK~OK~NJ~NJ~NJ~NJ~NJ~NJ~MJ~NJ~MJ~MJ~MJ~NJ~NJ~MJ~MJ~NJ~MI~MI~MI~MI~MJ~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~MI~LI~MI~MI~LI~MI~MI~LI~MI~MI~LI~MJ~MI~LI~MI~MI~MI~MJ~MI~MI~MI~MI~MI~NJ~MJ~MJ~MJ~MJ~MJ~NJ~NJ~MJ~NJ~NJ~OK~NJ~NJ~NK~NK~NK~NJ~NJ~NK~NJ~NJ~OK~OK~OK~OK~OK~OK~OL~OK~OL~OL~OL~PL~OL~PL~PL~PL~PM~PM~QM~PM~QN~RN~QN~RN~RN~RN~RN~RO~RN~RO~RO~RO~SP~SP~SP~TP~SP~TP~TP~TP~TQ~TQ~TQ~UR~UR~UR~UR~UR~UR~VS~VS~WS~WT~VS~WT~WT~WT~XT~XU~YV~YV~YV~YV~ZV~[W~[W~[W~\X~[X~\X~\Y~]Y~]Y~]Y~]Z~]Z~^Z~]Z~^[~_[~_\~_[~_[~_[~`\~`\~a^~`]~a^~b^~b^~b^~b_~c_~c`~d`~ea~da~ea~eb~fb~fc~fc~gc~gc~hd~hd~he~if~if~jf~jf~kg~kg~kg~lh~lh~lh~mi~mi~mi~nj~ok~ok~ok~pl~pm~pm~qm~ro~ro~ro~ro~ro~to~tq~tq~vs~vs~vs~vs~vs~vs~ws~yu~yu~yu~yu~zv~{w~{x~{x~|x~|x~}y~}y~?=~|~@>?=@>@>@>@?@?@?@?A?A@B@B@B@B@B@CACACACACBCBCBDBDCDCECECECECFDFDFDFEFDGEGEGEGEGEHFGFHFHGHGIGIGIHIHIHJIJHJIJIKIKIKILJLKLKLKMKMKMKMKNLNLNLNLOMOMOMOMOMONONONPNPNPNPNPNPNPNPNPNPNPNPNONPNPNPNPNQOQOQOQOQOQOQOPNQOPNQORPRPRPQPQOQOQOQOQOQOQOQOQOQOQOQOPOPNPNPNOMOMPNPNPNOMOMNLNLNLNLNLNLNLNLMKMKMKLKLJLJLJLJKIKIKIKIKIKIKIJIJHJHIGIHIHIHIHIGIGHGHGHGHFGFHFHFGEGEGEFDGEFDFDFDFDFDEDFDECECECDBDBDBCBCBCACACACAB@B@B@B@B@A?A?A?@>@>@>@>?>?>?=?=?=?=><|x~|x~{w~zw~zv~zv~zv~xt~xt~xt~xt~xt~xt~wt~vr~vr~vr~uq~uq~tp~tp~so~so~so~rn~qm~pm~pm~pm~pk~pk~pk~nk~nk~mi~mi~mi~mi~li~li~li~lh~kg~kg~jf~jf~jf~ie~ie~hd~hd~gc~gd~gc~fb~fc~fb~fb~ea~ea~ea~d`~c_~c_~c_~b_~b_~b^~a^~b^~`]~`]~`]~`\~`\~_\~^Z~^Z~^Z~]Z~^[~]Z~]Z~\Y~\Y~\X~\X~[X~[X~[W~[W~[W~ZW~ZV~ZV~YV~YV~YU~YV~XU~XU~XT~XT~WS~WS~WS~WS~VS~VS~VS~UR~UR~UR~UR~UR~UQ~UR~TQ~TQ~SP~SP~SP~TQ~SP~SP~SP~RO~RO~RO~RO~RO~QN~QN~QN~QN~QN~QM~QM~QM~PM~PM~PL~PM~PL~PL~PL~PL~PL~PL~OK~PL~OK~OL~OK~OL~OK~OK~OK~OK~NK~NK~NK~NJ~NK~NK~NJ~NJ~NK~MJ~NJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MJ~MI~MI~MI~NJ~MJ~MJ~MI~MJ~MJ~MJ~LI~LI~LI~LI~LI~MJ~MI~MI~MI~MI~MI~MI~MI~MJ~MJ~MI~MI~MJ~NJ~MI~NJ~NJ~NJ~NJ~NJ~NJ~NJ~MJ~NJ~MJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NK~NJ~NJ~NK~NJ~NK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~PL~PL~PL~PL~OL~OL~PM~PM~PL~PL~PL~PM~PL~PL~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~RN~RN~RN~RN~RN~RN~RN~RN~SN~RN~RO~RO~RN~SO~SO~SO~SO~TP~TP~TP~TQ~TQ~UQ~UQ~UP~UQ~UQ~UQ~UQ~UQ~UQ~VR~VR~VR~VR~VR~VR~WR~WS~VR~VS~WS~WS~WS~WS~WS~WS~WS~XT~XT~XT~XT~XT~XT~XT~XU~XT~YT~YT~XT~YT~XT~YU~YU~YT~YU~YU~YU~YU~YU~YU~YU~ZV~ZV~YU~ZU~ZU~ZU~ZU~ZU~ZV~ZV~ZU~[V~[V~ZU~[V~ZV~ZV~ZV~ZV~[V~[W~[W~[W~[W~ZV~[W~\X~[W~[W~[W~[W~[W~[W~[W~[W~[W~\W~[W~[W~[W~[W~[W~\X~[W~\W~[W~\W~[V~\W~\W~[X~[W~\W~\W~\W~[W~\W~\W~\W~\W~\W~[W~[W~[X~[W~\W~\W~\W~\W~\W~\W~[W~\W~\W~\W~\W~[W~\W~[W~\W~\W~\W~\W~\W~[V~\W~[W~\X~\W~\W~[W~[W~[W~\W~[W~[W~[W~[W~[W~[W~[V~[V~[V~[W~[V~ZV~ZV~[V~ZV~[V~ZV~ZV~ZV~ZV~ZV~ZV~ZV~YU~YU~YU~YU~YU~YU~YU~YU~YU~YU~XU~YU~YU~YU~XT~XT~XT~XT~XT~XT~WS~XT~XT~XT~XT~WS~WS~XT~XT~XT~WS~WS~WS~WR~WS~WS~WS~VS~VR~VR~VR~UQ~VR~VR~VR~VR~UQ~UQ~UQ~UQ~UP~UP~UP~TP~TP~TP~TP~TP~TP~TP~TP~SO~TP~SO~TP~SO~SO~SO~SO~RN~SO~RN~RN~RN~RN~RN~RN~RN~RN~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~PM~QM~PL~PL~PL~PL~PL~PL~PL~OK~OK~PL~OL~OL~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~NK~NK~NK~NK~NJ~NK~MJ~NJ~NJ~NK~MJ~MJ~NJ~NJ~NJ~NJ~MI~MI~MI~MI~MI~MI~MI~MJ~MI~MI~MJ~MI~MI~MI~MJ~MI~MI~MI~MJ~MJ~MJ~MI~MJ~MI~MI~MJ~NJ~MJ~MI~MI~NJ~MJ~MI~NJ~MI~MJ~NJ~NJ~MJ~MJ~MI~MJ~MJ~MJ~MJ~MJ~MJ~NJ~MJ~NK~NJ~NJ~NK~NK~NK~NK~NK~NK~OK~NJ~NJ~NK~OK~OL~NK~OL~OL~OL~OL~OL~OK~OL~PL~PL~PL~PL~PM~PM~PL~QM~QM~QM~QM~QM~QN~RO~RN~RN~RO~RO~RO~SO~RO~SO~SP~SP~TP~TP~TP~TP~TP~UQ~TQ~TQ~UR~TQ~UR~UR~VR~VR~VS~UR~VS~VS~WS~WT~WT~WT~XU~XU~YU~YV~YV~YV~YV~ZW~ZW~ZW~[X~[X~\X~\Y~\Y~\Y~\Y~]Y~]Z~]Z~^Z~^[~^[~^[~_[~_\~`\~`\~`\~`]~`]~a^~a^~b^~b^~c_~c_~c`~c_~c`~da~d`~ea~fb~fb~fb~gc~gc~gc~gd~hd~he~ie~jf~jf~jf~kg~kg~kg~kg~lh~mi~mi~mi~mi~nj~nj~ok~ok~pl~pl~qm~pm~pm~ro~ro~ro~sp~tq~tq~tq~uq~ur~vs~vs~wt~wt~xu~xt~yu~yu~yu~zv~{w~{w~|x~|x~|x~?=?=?=?=?>@>@>A?A?A?A?A@A@A@A@A@B@B@CAB@CACACACACBCBDBDCDCDBEDEDEDEDFDFDFDGEGEGEGEGEGFHGHFHGHGHGIGIGIGIHIHJIJIJIJIKIKILJLJLJLJMKMKMKNLNLNLNLNMNLOMOMOMOMOMONPNPNPNPNPNPNPNQOQOQOQOQOQOQOQOPOQOPOQOQOQOQORPRPRPRPRPRPRPRPRPRPRPRPSQRPRPRPRPRPRPRPRPRPRPRPRPQORPQOQOQOQOPNPNPNPNPNOMPNPNOMNLNLOMNLNLNLNLNLNLNLMKMKMKMKLJLJLJLJLJKIKIKIKIKIJIKIJHJHJHJHIHIHIHIGHGHGHGHGHGHGGFGFGEGEGEGEGEGEFDGEFDFDFCECECDCECDBDBCBCBCACACAB@B@CAB@A?B@A?A?@>@>@>@>?>?>?=?=?=><><|x~><><|x~zv~zv~zv~zv~yv~xt~xt~xt~xt~xt~vr~vr~vr~uq~uq~tp~tp~so~so~so~qn~qm~qm~pm~pm~pk~pk~nk~nk~nk~ni~ni~li~li~li~li~kg~kg~kg~jg~jg~jf~jf~jf~ie~hd~hd~gc~gd~gc~gd~fc~fb~eb~eb~ea~d`~d`~d`~d`~c_~c_~b_~b^~a]~a]~`]~`\~_\~`]~_[~_[~^[~_[~^[~^Z~]Z~]Z~\Y~\Y~\Y~\X~\X~[X~[X~[W~[W~ZW~ZW~ZW~ZW~YV~YU~XU~XU~XU~XT~XU~XU~WT~WT~WT~VS~VS~VS~VS~UR~UR~UQ~UR~UQ~UQ~TQ~TQ~TQ~SP~SP~SP~SP~SP~SP~SP~RO~RO~RO~RO~RO~RN~RN~RN~QN~QN~QN~QN~QN~QN~PM~PM~PM~PL~PL~PL~PM~OL~OL~OL~OL~OK~PL~OK~OK~OK~OK~OK~OK~OK~OK~NK~NK~OK~OK~NK~NK~OK~NK~NK~NJ~NK~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~MI~NJ~NJ~MJ~NJ~NJ~NJ~MJ~MJ~MJ~MJ~MJ~MI~MJ~NJ~NJ~MI~MI~NJ~NJ~NJ~NJ~MJ~NJ~MJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NK~NK~NK~NJ~NK~NK~NK~NK~NJ~NK~NK~NK~NK~OK~OK~OK~OK~OK~OK~PL~OL~PL~OL~PL~PL~PL~PL~PL~OL~PM~PM~PM~PL~QM~QM~QM~QM~QM~QM~QM~QM~RM~QM~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~SO~SO~SO~SO~SO~SO~SO~SP~TP~TP~TP~UQ~UP~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~VR~VR~UR~VR~VR~VR~WS~VS~WS~WR~WS~WS~WS~WS~WS~WS~XT~XT~XT~XT~XT~XT~XT~XT~XT~YU~YU~YU~YU~YU~YU~YU~YU~YU~YU~YU~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~[V~ZV~ZV~ZW~[W~ZV~ZV~ZV~ZV~[W~[W~[W~\X~[W~[W~[W~\X~[W~[W~\W~\W~\W~\W~\X~\X~\X~\X~\W~\X~\X~\W~\X~\X~\W~\X~\X~\X~\X~]Y~\X~\X~[W~\W~\X~\X~\W~\X~\X~]Y~\X~\X~\X~\X~\X~]X~\X~\X~\X~\X~\X~\X~]X~]X~]X~\X~\X~\X~\X~\X~\X~\X~\X~\X~\X~\X~\X~]X~\W~\W~\X~\X~\X~\X~\X~\X~]X~\X~\W~\X~]X~[W~\W~\W~\X~\X~[W~\W~\W~[W~\X~[W~[W~[W~[W~[W~[W~[W~[W~[W~[W~[W~ZV~ZV~ZW~[W~ZV~ZW~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~YU~YU~YU~YU~YU~YU~YU~YU~YU~YT~XT~XU~XU~XT~XT~XT~XT~XT~WS~XT~XT~WS~XS~WS~WS~WS~WS~WS~VR~WS~WS~VR~VR~VR~VR~VR~VR~UQ~UQ~UQ~VQ~VQ~UQ~UQ~UQ~UQ~TQ~TP~TP~TP~TP~TP~SO~TP~TP~SO~SO~SO~SO~SO~RN~SO~SO~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~QM~RN~RN~QM~QM~QM~PL~PM~PM~PL~PM~PL~PL~PM~PL~PL~PL~PL~PL~PL~OL~OL~PL~OK~OK~OK~OK~OK~OK~OK~OK~OK~NK~OK~NK~NJ~NK~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~MJ~MJ~NJ~NJ~MJ~MJ~MJ~MJ~MJ~MI~NJ~NJ~MJ~MJ~MJ~NJ~NJ~NJ~MJ~MJ~NJ~NJ~MJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~MJ~NJ~MJ~NK~MJ~NJ~NJ~NJ~NJ~NK~NK~MJ~NJ~NJ~NK~NK~NK~NK~NK~NK~NK~NK~NK~OK~OK~OK~OK~OK~OK~OK~OL~OL~OL~PM~PL~PL~PL~PL~PL~PM~PM~PM~PM~QM~QM~QM~QN~RN~RN~RN~RO~RO~RO~SO~RO~SP~SP~SP~SP~SP~TP~TQ~TQ~TQ~TQ~TQ~TQ~UR~UR~UR~VS~VS~VS~VS~VS~VS~WS~WT~WS~WT~XT~XT~XU~XU~YU~YU~YV~YV~YV~ZV~[W~[X~ZW~[X~[X~\Y~\Y~\Y~\Y~]Z~^Z~]Z~^[~^[~^[~_\~`\~_\~_[~`\~`]~`]~a]~a^~b^~b^~b^~b_~c_~d`~c`~ea~ea~ea~ea~fb~fb~fb~gc~gd~hd~ie~ie~ie~jf~ie~jf~jf~jf~kg~kg~lh~lh~mi~mi~nj~ni~nk~ni~ok~pk~pm~pm~qm~rm~qn~ro~ro~so~tq~tq~uq~uq~ur~ws~ws~wt~wt~xu~xu~yu~yu~zv~zv~{w~{w~|x~|x~><?=><?=?>?>@>@>@>A?A?A@A@A@A@B@B@BACACACACACBCBDBDBDCDCDCDCECECECFDFDFEGEGEHFGEGEGEGEHFHFHGHGIGIHIHIHIHJHIHJHJIJIKIKILJLJLJLJMKMKMKNLNLNLNLOMOMOMOMOMOMPNPNPNPNPNPNPOQOQOQOQOQOQORPQORPRPRPRPQPQORPRPRPQOQPQPRPRPRPRPSQRQRPSQSQRQRPSQSQTRSQRQSQSQSQSRRPRPRQRPSQRPRPRPQOQPQPQOQPQOQOQOPOPOQOPNPNPNOMONOMOMOMOMNLNLOMNLNLMKMLMKLJMKLKMKLJLJLJKIKIKIJHKIKIJIKIJHJHJHJHIHIHIHHGHGHGHFHFHFHFHFGEGEGEGEGEFDGEFDFDFDFDECECDBDCDCCBCBCACACAB@B@B@B@A?A?A?A?A?A?@?@>@>?>?=?=?=><><><><>;zw~zw~zw~yv~yv~xu~xt~xt~xt~ws~vr~vr~uq~uq~uq~uq~so~so~so~rm~rm~qm~qm~pm~pm~pk~nk~nk~nk~nk~li~li~li~li~lg~lg~lg~kg~jf~kg~jf~jf~ie~ie~ie~hd~hd~gc~gc~gc~fb~fb~eb~ea~ea~d`~d`~d`~c_~c_~b_~b_~b^~b^~a]~a]~a]~a]~`]~_\~_\~_[~_[~_[~^Z~^Z~]Z~]Z~]Y~]Y~]Y~\X~\X~\X~[W~[W~[W~ZW~ZW~ZV~YV~YU~YU~YU~XU~XT~WT~XT~XT~WS~WS~WT~VS~VS~VS~UR~UR~UR~UR~UR~TQ~TQ~TQ~TP~TP~TP~SP~TQ~SP~SP~RO~SP~RO~SP~RO~RO~RN~RN~RN~QN~QN~QN~QN~QN~QN~PM~PM~PM~PM~PM~PM~PL~OL~OL~PM~OL~OL~OK~OK~OK~OK~OK~OK~OK~OK~OL~OL~NK~NL~OL~OK~OK~NK~OK~OK~NJ~NK~NJ~NK~NK~NK~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NK~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~MJ~NJ~NK~NK~NK~NJ~NJ~OK~OK~NK~OK~NK~OK~OK~OK~NK~NK~NK~OL~OK~PL~OK~OK~OK~PL~OL~OL~PL~PL~PL~PM~PL~PL~QM~PL~QM~QM~PM~QM~QM~QM~QM~QM~QN~RN~RN~QM~QN~QM~RN~RN~RN~QM~RN~SO~SO~SO~RN~SO~SO~SO~SO~SO~SP~SO~SO~SO~SO~SO~SO~TO~TO~TP~TP~TP~UP~UQ~VR~UQ~UQ~VR~VR~VQ~VR~VR~VR~VR~VR~WS~VR~VR~WS~WS~WS~WS~WS~WS~XS~XT~XT~XT~XT~XT~XT~XT~YT~YT~YT~YU~YU~YU~YU~ZV~YU~YU~YU~ZV~ZU~ZV~ZV~[V~ZV~[W~ZV~ZV~ZV~ZW~ZW~[W~[W~[W~ZV~[W~[W~[W~[W~[W~\X~[W~[W~\W~[W~\X~[W~\X~\X~\X~\X~\X~\W~\X~\X~\X~]X~\X~\X~\X~\X~]Y~\X~]Y~\X~]Y~]Y~]Y~]Y~]X~]Y~]Y~]X~]X~^Y~^Y~]Y~]Y~]X~]X~]Y~]Y~]Y~]Y~]Y~]Y~]Y~]Y~]X~]X~]X~]X~]Y~]Y~]Y~]Y~]X~]Y~]X~]Y~]Y~]X~]Y~]Y~]X~]X~]X~]Y~]X~]X~]X~]Y~]X~]X~]X~]X~]X~]X~]X~]X~]X~]Y~]Y~]Y~]Y~]X~]X~]X~\X~]X~\X~\X~\X~\X~\X~[X~\X~\X~\X~\X~\W~\X~\X~\X~\X~[W~[W~[W~[W~[W~[W~[W~[V~[W~ZW~ZW~[W~[W~[W~ZV~ZV~ZV~ZU~ZV~ZV~ZV~ZV~ZV~YU~ZV~YU~YU~YU~YU~YU~YU~YT~YT~YT~YU~XT~XT~XT~XT~XT~XT~XS~XS~XS~XS~XS~WS~WS~WS~WS~WS~VS~VR~WR~VR~VR~VR~VR~UR~UQ~UQ~UQ~UQ~UQ~UQ~UQ~TP~UP~TP~TP~TP~TP~SO~SP~TP~TP~SO~SO~SO~SO~SO~SO~SO~SO~SO~RN~RN~RN~RO~RN~RN~QN~RM~QM~QM~QM~RN~QM~QM~QM~PM~QM~PM~QM~QM~PL~PL~QM~PL~PL~PL~PL~OL~PL~PL~PL~OL~OK~OK~OK~OK~OL~OL~OK~OK~OK~OK~OK~OK~OK~NK~NK~NJ~NJ~NJ~NJ~NJ~NJ~NK~NJ~NJ~MJ~MJ~MJ~MJ~NJ~NJ~MJ~MJ~NK~MJ~NJ~NJ~NK~MJ~NK~NK~MJ~MJ~NK~MJ~NK~NJ~NJ~NJ~NK~NJ~NJ~NJ~NK~NJ~NK~MJ~NK~NJ~NJ~NJ~NK~NK~NK~NK~NK~NJ~NJ~NK~NK~NK~NK~NK~NK~NK~NK~NK~OK~OL~OL~OL~OL~OL~OK~OL~OL~OL~OL~PL~PL~PL~PL~PM~QM~PM~PM~QM~QM~QM~QN~QN~RO~RN~RN~RO~RO~RO~SO~RO~SP~SP~SP~SP~SP~TQ~TP~TQ~TQ~UR~UR~TQ~UR~UR~UR~UR~VS~VS~VS~WS~VS~WT~WT~XT~XT~XU~YU~YU~YV~YV~YV~YV~ZV~[W~[X~[X~\X~[X~\X~\Y~\X~]Y~]Y~]Y~^Z~^Z~^[~^[~_[~_\~_\~`\~`\~`\~`]~`]~a]~b^~b^~a^~b^~c_~c_~c`~d`~ea~ea~ea~fb~fb~fb~gc~gc~hd~gc~hd~hd~ie~ie~jf~jf~jf~kg~kg~lh~lh~mi~mi~mi~mi~nk~ok~ok~ok~ok~pm~pm~rm~rm~rm~ro~ro~ro~so~tp~uq~uq~ur~vr~ws~ws~ws~yu~yu~yu~zv~zv~zv~{w~{w~|x~|x~|x~><?=?=@>@>@>@>A?A?A?A?A?A@B@B@CACACACBCBCBCBCBDCDCDBDCECECEDECECFDFDGEGEGEHFGEHFHFHGHFHGIHIGIGIHIHIHJHJHJHJHKIKIKIKILJLJLJLJMKMKMKMKNLNLNLOMOMOMOMOMONPNPNPNPNPNQOQOQORPRPRPRPRPSQRPRPRPRPRQSQRPRQRQRQRQSQSQSRSQSQSRSRSRSRTRTRTRSRTRSQSQTRUSTRTRTRSRSRSRTRSQSQSQSQSQRPRPRPRQRPRPRQRPRPRPQPQPQPPOQOQOPOPOPNPNPNONPNPNOMOMOMNLOMNMNLNLMKMKMKMKLJLJLJKILJKIKIKIKIJHJHJHJHJHJHJHJHIGIGIGHGHGIGHFHFHFHFHFGEGEGEGEGEGEFDFDECECECECECDBDBDBDBCACACABAB@B@B@B@A?A?A?@>A?@>@>@>?>?=?=>=?<><><><{x~zw~zv~zv~yv~xu~xu~ws~ws~ws~ws~uq~vr~uq~uq~uq~tp~so~so~so~so~qn~qm~qm~qm~ok~ok~ok~nk~mj~mi~li~li~li~lg~lg~lg~lg~jf~jf~jf~jf~ie~he~ie~hd~hd~hd~gc~fb~fb~fb~fb~eb~ea~d`~d`~d`~c_~c_~c_~c_~b^~b^~a]~a]~a]~`\~`\~_\~_\~_[~^[~^[~^[~^Z~]Z~]Z~]Y~\Y~\Y~\X~[X~[X~[W~[W~ZW~ZW~ZV~ZV~ZV~YV~XU~XU~XU~XU~XT~WT~WT~WT~WS~WS~WT~VS~VS~VS~UR~UR~UQ~UQ~UQ~UR~TQ~UQ~TQ~TQ~TP~SP~SP~SP~SP~SP~RO~SP~SO~SO~RN~RN~RN~RN~RN~RN~RN~RN~QM~QN~QM~QM~PM~QM~QM~PL~PL~PL~PL~PL~OL~OL~PL~OL~OL~OL~OL~OL~OL~OL~OL~OK~OK~OK~NK~OK~OK~OK~OK~OK~NK~OK~NK~OK~NK~NK~NK~NK~NJ~NK~NJ~NJ~NJ~NK~NK~MJ~MJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NJ~NK~NJ~NK~NK~NK~NK~NJ~NK~NJ~OK~NJ~NK~NK~OK~OK~OK~OK~OK~OK~OK~OK~PL~PL~PL~OL~PL~PL~PL~PL~PL~PL~PL~OL~PL~PM~PL~PL~PL~QM~PL~PL~QM~QM~QM~QM~QM~RN~QN~QM~QM~QM~QN~RN~RN~RN~SO~RN~RN~RN~SN~SN~SN~RN~SO~SO~SO~SO~SO~SO~TP~TP~TP~TP~TP~UP~UP~UP~UP~UP~TP~UQ~UQ~UQ~VR~VR~VR~VR~VR~VQ~VQ~VR~WS~WR~WS~WS~WS~WS~WS~WS~WS~WS~WS~XS~WS~XT~XS~XT~XT~YT~YT~YT~YU~YU~YU~YU~YU~ZU~ZU~YU~ZV~ZV~ZV~ZV~ZV~ZV~ZV~[W~[W~[V~ZV~[W~[W~[W~[W~[W~[W~[W~[W~\X~\X~\X~\X~\X~\X~\X~\X~\X~\W~]X~\X~\X~\X~\X~\X~\X~]Y~]Y~]Y~]X~]Y~]Y~]Y~]Y~^Y~]Y~^Y~^Y~]X~^Y~^Z~^Y~^Y~^Z~]Y~]Y~^Y~^Y~^Y~^Y~^Y~^Y~^Y~]Y~^Y~]X~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~]Y~^Y~^Y~^Y~]Y~^Z~^Z~^Y~^Y~^Z~^Y~^Y~^Y~^Y~^Y~^Y~^Y~^Z~^Z~]Y~^Y~^Z~^Z~^Z~^Z~^Y~]Y~^Z~^Y~^Y~^Y~^Y~^Y~^Y~]Y~^Y~^Y~^Y~^Y~^Y~]Y~]Y~^Y~]Y~]X~]X~]X~]Y~]Y~]X~\X~\X~\X~\X~]Y~]Y~\X~]X~\X~\X~\X~\W~\W~[W~\W~[W~\X~\X~[W~[W~[W~ZV~[V~ZV~ZV~[W~[W~[W~[W~ZV~ZV~ZV~ZV~ZV~ZV~YU~YU~ZU~YU~YU~YU~YU~YU~YU~YU~YT~YT~YT~XT~XT~XT~XT~XT~XT~WS~XT~WS~WS~WS~WS~WS~WR~WR~WR~VR~VR~VR~VR~VR~VR~VR~UQ~UQ~UQ~UQ~UQ~UQ~TP~UQ~TP~UP~TP~TP~TP~TO~TO~TO~SO~SO~SO~SO~SO~SO~SO~RN~RN~RN~RN~RO~RN~RN~RN~RN~RN~QM~RN~QN~QN~QN~QM~QM~QM~PM~QM~QM~QM~QM~PL~PL~PL~PL~PL~PL~PL~PL~OL~PL~OL~OK~OK~OK~OL~PL~OL~OK~OK~OK~OK~NL~OK~OK~OK~OK~OK~NK~OK~NK~OK~NJ~NJ~NK~MJ~NK~NJ~NJ~NJ~NK~NK~NK~NK~NK~NJ~NJ~NJ~NJ~NJ~NK~NK~NK~NK~NK~NJ~NK~NK~NK~NJ~OK~NJ~NK~NJ~NK~NK~NK~NK~NK~NK~OK~OL~OK~NK~OL~NK~NK~OK~OK~OL~OL~OL~OK~OL~OL~OL~OL~OL~OL~OL~PM~PL~PL~PL~PL~PL~PM~PM~PM~QM~QM~QM~QM~QN~QN~QN~QN~QN~RO~RN~RN~RO~RO~SP~SP~SO~TP~TP~TQ~TQ~TP~UQ~TQ~TQ~UR~UR~UR~UR~UR~VS~VS~VS~WS~WS~WS~WT~WT~WT~XU~XU~XU~XU~XU~XU~YV~YV~ZV~ZW~ZW~ZW~[X~[X~[X~\X~\Y~\Y~]Z~]Y~]Z~^Z~^[~^[~^[~_\~`\~_\~`]~_\~a]~a]~a]~b^~b^~b^~b^~c`~c`~c`~d`~d`~ea~ea~ea~fb~fb~fb~gc~gc~hd~hd~hd~ie~ie~jf~ie~kg~jf~kg~kg~kg~lh~li~li~mi~ni~ni~nk~ok~pk~pk~pm~pm~ro~rm~so~so~ro~so~tp~tq~uq~ur~vr~vr~ws~ws~yu~yu~yu~{w~zv~zv~zw~{w~|x~|x~|x~>=?=?=?=?>@>@>@>A?A?A?A@B@B@B@CACACACACACBDBCBDCECDCDCECECEDFDFDFDFDFDGEGEGFGFHFHGIGIGIHIHIHJHJHJHJHJHKIKIKILJLJLJLJLJLJLJMKMKMKMKNLNLOMOMOMOMOMPNPNPNQOQOPOQOQOQOQORPRPRQRQRQRQSQSRSQSRRQSRTRTRTRTRTRSQTRSRTRSRTRTRTRUSTSTSUSUSUTUTUSTSUSTRUSUTTSTSTSUSUSTRTRTSTRTRTRSQSQSQTRTRSRSQSRSRSQRQSQSQSQRPRPRPRPQOQOQOQOQOQOQOPOPOPNPNONOMOMOMNMNLNLNLMLMKMKLKLKMKMKLJLJLJLJKIKIKIKIKIKIKIJHJHJHIHIHIGIGIGIGHFHFHFHFHFGEGEGEGEFDFDFDFDECECECDCDCDBCACACABAB@BAB@B@A?A?A?A?A?A?@>@>@>?=?=?=?=?<><><><>;=;=;zw~zw~xu~xt~xt~ws~ws~vr~uq~uq~uq~uq~tp~so~tp~rn~rn~rn~rn~qm~pl~ok~ok~ok~ok~ok~nk~mj~mi~li~li~lg~lg~lg~jg~jg~jf~jf~jf~ie~ie~ie~hd~hd~hd~gc~gc~fb~fb~fb~eb~ea~d`~d`~d`~c_~c_~b_~b^~b^~a^~b^~a]~`]~`]~`]~`]~_[~_[~^[~^[~^Z~]Z~]Z~]Y~\Y~\Y~\Y~[X~\Y~[X~\X~[W~ZW~ZV~ZW~ZV~ZV~YV~YV~YU~YU~YU~XT~XT~XT~WT~WT~WS~WS~VS~VS~WS~VR~VR~UR~UR~UQ~TQ~UQ~TP~UQ~TP~TP~TP~TP~SP~SP~SO~SO~RO~RO~RO~RO~RN~RN~RN~RN~RN~QN~RN~RN~QM~QN~QM~QM~QM~QM~QM~PM~QM~QM~PM~PL~PM~PM~PL~PL~PL~OL~PL~PL~PL~PL~OK~OL~OL~OL~OK~OK~OK~OK~NK~OK~OK~NK~NK~NK~OK~OK~NK~OK~OK~NJ~NJ~NK~OK~NJ~OK~NK~OK~NK~NK~OK~NJ~OK~NK~OK~NK~OK~OK~NK~OK~OL~OK~OK~OK~OK~OK~OK~PL~OK~OK~OK~OK~PL~OL~OL~PL~OK~PL~PL~PL~PL~PL~PL~PL~PL~PM~QM~QM~QM~QM~QM~QM~QM~QM~RN~QN~QN~QN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~SO~SO~SO~SO~SO~SO~SO~TP~TP~SO~SO~TP~TP~TP~TP~TQ~TP~UQ~TP~UQ~UQ~UP~UQ~UQ~UQ~UQ~UQ~VQ~VR~VR~VR~VR~VR~VR~WS~WS~WS~WS~WS~WS~WS~WS~XT~XT~XT~XS~XT~XT~XT~XT~XT~XT~YU~YU~YU~YU~YU~YU~YU~ZV~ZV~[V~[V~[V~ZV~[W~[W~ZV~[W~[W~[W~[V~[W~\W~\W~\W~[W~\X~[W~[W~\X~\X~\X~\X~\X~\X~]Y~]Y~\X~\X~]Y~]X~]Y~]Y~]Y~]Y~^Y~]X~]Y~^Y~]Y~^Z~]Y~^Z~^Y~^Z~]Y~^Y~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~_Z~_Z~_Z~^Z~_Z~_Z~^Z~^Z~_Z~^Z~^Z~^Z~^Z~_Z~_Z~^Z~_Z~_Z~_Z~_Z~_Z~_Z~_Z~_Z~_Z~_Z~_Z~_Z~_Z~_Z~_Z~_Z~^Z~^Y~^Z~^Z~_[~_[~^Z~_Z~_Z~^Y~_Z~^Z~_Z~_Z~_Z~^Y~^Y~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~_Z~_Z~_Z~^Z~^Z~^Y~^Y~^Y~^Y~^Z~^Z~^Y~^Y~]Y~]Y~]Y~]Y~]Y~]Y~]Y~]Y~]Y~\X~\X~]X~]Y~\X~\X~\X~\X~\X~\X~\X~\X~[W~\X~\W~[W~[W~[W~[W~[W~[W~[W~[W~[W~[V~ZV~[V~ZV~[W~ZV~ZV~ZV~ZV~YU~YU~YU~YU~YU~YU~YT~YT~YU~YU~XT~XT~XT~XT~XT~XT~XT~WS~WS~WS~WS~WS~WS~WR~WR~VR~VR~VR~VR~VR~UQ~UQ~UR~VR~UQ~UQ~UQ~UQ~UQ~UQ~UP~TP~TP~UP~TP~TP~TP~TP~TP~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~RN~RN~RN~RN~RN~RN~RN~QM~RN~RN~RN~QM~QM~QM~QM~QM~QM~PL~QM~PL~PL~PL~QM~PL~PL~PL~OK~PL~PL~PL~OL~OL~OK~OL~OL~OL~OL~OL~OK~OK~OK~OK~OK~NK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~NK~OK~NK~OK~OK~OK~OK~NK~NK~NK~NK~NK~NK~NK~NK~NK~OK~OK~OK~OK~OK~NK~OL~NK~OK~NK~NK~NK~OL~OL~OL~OL~OL~OL~OK~OL~OK~OL~PL~OL~PL~OL~OL~PM~PM~PL~OL~PM~PL~PM~PM~PM~PM~QM~QM~QM~QM~QM~QN~QM~QN~QN~QN~QN~RO~RN~RO~RO~RN~SO~SO~SO~SO~SO~TP~TP~TP~TP~TP~TQ~TQ~TQ~UR~UR~UR~VS~VR~VR~VR~VS~VS~WS~WS~WT~WT~WT~XT~XU~XU~XU~YU~YV~YU~ZV~ZV~ZW~[W~[X~[X~[X~\Y~\X~\X~\Y~\Y~]Y~^Z~^Z~]Z~^[~^[~_[~`\~`\~`\~a]~`\~a]~a^~b^~b^~b^~c_~c_~c`~c`~d`~d`~da~ea~fb~fb~fb~gc~fc~gc~gc~hd~hd~ie~ie~ie~jf~kg~kg~kg~kg~kg~li~li~li~ni~nk~nk~nk~nk~nk~pk~pk~rm~rm~ro~ro~so~so~to~to~uq~uq~vr~vr~vr~ws~xt~xt~xt~yu~zv~zv~{w~zw~|x~><><><><>=>=?>@>@>@>@?A?A?A@A@B@B@CACACACBCBCBCBCBDBDBDCDCEDEDFDFDFDGEFDGEGEGEGEGFHGHGHGHGIGIHIHJHJHJHJHJHKIKIKILJLJLJLJMKLJMKMKMKMLNLNMNMNMOMONONOMPNPNPOPOPOQPQPQPQPQPRPRPRPRQSRSQSQTRSRTRTRTRUSUSUSTRUSUSUSUSUSUSUSUSUSUSUSVTVTVTVTVTVTWUVTVTVTVTVTVTVTVTVTUSUSUSUSUSUSUSTRTRTRTRTRTRTRUSUSUSUSTRTRTRSQSQSQSQSQSQSQRPRQRPRPRPQPQPQOQOQOQOPNPNPNOMPNONNMNMNLNLNLNLNLNLMKMKMKMKLJLJLJLJLJKIKIKIKIKIKIJHJIJHJHIGIGIGIGHGHFHFHFHFGEGEGEGEGEFDFDECECECEBDBDBCBCACACABACAB@B@B@B@A?A?A?A?@>@?@>@>?=?=?=?=><><><=<zw~yv~yv~yv~xu~yv~xt~wt~vs~vr~vr~uq~uq~tp~tp~tp~so~rn~rn~rn~qm~qm~pl~pl~ol~ok~ok~nk~mi~mi~mi~mi~li~li~lg~lg~jg~jg~jf~jf~ie~jf~ie~he~he~hd~hd~gc~fb~fb~fb~fb~ea~da~d`~d`~d`~c_~c_~b_~c_~b_~a^~a]~a]~a]~`]~`\~_\~_\~_\~^[~^Z~^Z~^Z~]Z~]Y~]Y~\Y~\Y~\X~\X~[W~[W~[W~ZW~ZV~ZV~YV~YV~YU~YU~XU~YU~XU~XU~XT~XT~WT~WT~WS~WS~VS~VS~VR~UR~VR~UR~UQ~UQ~UQ~UQ~TQ~TQ~TP~TP~TP~TP~SP~SP~SP~SO~SO~SO~SO~RN~RO~RO~RO~RN~RN~RN~QM~QM~QM~QM~QN~QN~QM~QM~QM~QM~QM~QM~QM~QM~PL~PL~PM~PL~PM~PL~PL~OK~PL~PL~OL~OL~PL~OK~PL~OL~OK~OL~OL~OL~OK~OK~OK~OK~OK~OK~NK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~OK~PL~OK~PL~PL~OL~OL~OL~OL~OL~OL~OL~OL~PL~PL~OL~PL~PL~OL~PL~PM~PM~PL~PL~PL~PL~PL~PL~PL~QM~PL~QM~QM~QM~QM~QM~QM~QM~QN~RN~RN~RN~RO~RO~RN~RN~SO~SO~RN~RN~RN~SN~SN~SO~SO~SO~SP~TP~TP~TP~TP~TP~TP~TP~TP~UP~UP~TP~TP~UQ~UQ~UQ~TP~UQ~UQ~VQ~VQ~UQ~VR~VR~VR~UQ~VR~WS~VR~WS~VR~WS~WS~WS~WS~WS~YT~XS~XT~XT~XT~XT~YT~YU~YU~YU~YU~YU~YU~YU~ZU~YU~ZU~ZV~ZV~[V~ZV~[V~ZV~ZV~[V~[V~[W~[V~[W~[W~[W~\X~\W~\W~\W~\X~\W~\X~\X~\X~]Y~]Y~\X~\X~]Y~]Y~]Y~]Y~]Y~]Y~]Y~]Y~]Y~]Y~^Z~^Y~^Z~^Z~^Z~^Y~^Y~^Y~^Y~^Z~_[~^Z~^Z~^Z~^Z~^Z~_Z~`[~_Z~`[~_[~_[~_[~_[~_[~_Z~_Z~`[~`[~_[~_[~_[~_Z~`[~`[~_[~_[~_Z~_Z~_Z~_[~_[~_[~_[~_[~_[~_[~_[~`[~`[~`[~_Z~_Z~`[~_[~_[~`[~_[~`[~_Z~`[~`[~`[~_[~_[~_Z~`[~_Z~_[~_Z~_Z~_Z~_Z~_Z~_Z~_[~_[~_[~_[~_Z~_[~`[~_Z~`[~_Z~`[~_Z~_Z~_Z~_Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~]Y~^Z~^Z~^Y~^Y~^Y~^Z~^Y~^Z~^Z~]Y~]Y~]Y~]Y~]Y~]Y~]X~\X~]Y~]Y~]X~]Y~]Y~]Y~\X~\X~\X~\X~\X~\X~[W~\X~\X~[W~[W~[W~[V~[W~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZU~ZU~ZU~YU~YU~YU~YU~YU~ZU~YU~YU~XT~XT~XT~XT~XT~XT~XU~XT~WS~WS~WS~WR~WS~WS~VR~VR~VR~VR~VR~VR~VR~VR~UQ~VR~VR~UR~VQ~UP~UQ~UQ~UQ~UQ~TP~TP~TP~TP~TP~TP~TP~TP~TP~SP~TP~SO~SO~SO~SO~SO~SO~RO~SO~RN~RO~RN~RO~RN~RN~RN~RN~RN~QM~QN~QN~QN~QN~QM~PM~PM~QM~QM~PM~PM~QM~QM~QM~PL~PM~PL~PM~OL~PL~OL~OL~OL~OL~OL~OL~OL~OL~OL~OL~OL~PL~OL~OK~OK~OL~OK~OK~OL~OK~OK~PL~OK~OK~OK~OL~OL~NK~OL~OK~OL~OK~OL~OL~OK~OK~OK~OL~OL~OL~OL~OL~OL~OK~OL~OL~OL~OL~OL~OL~OL~OL~PL~PL~OL~OL~PL~PL~PL~PL~PL~PL~PL~QM~PL~QM~QM~PM~PM~PM~PM~QM~QM~QM~QM~QN~QN~QN~QN~QN~QN~RN~RN~RO~RO~SO~SO~SO~SO~TP~TP~SO~TP~TP~UQ~TP~TQ~TQ~UR~UR~UR~UR~VR~VS~UR~VS~VS~WS~VS~WS~WT~XT~XT~XU~XU~XU~YU~YU~YU~YV~ZV~ZV~ZW~ZW~[W~[W~\X~\X~\X~\X~\Y~]Y~]Z~^Z~]Z~^[~^[~_[~_[~_\~`\~`]~`]~`]~a^~a^~b^~b^~c_~c_~c`~c`~d`~d`~da~ea~ea~fb~ea~fb~gc~gc~hd~hd~hd~ie~hd~ie~jf~jf~jf~kg~kg~kg~kg~lg~li~li~ni~nk~nk~nk~nk~nk~pk~pk~pm~rm~ro~ro~ro~so~sp~so~uq~uq~uq~vr~vr~ws~xt~xt~yu~yu~yu~zw~{w~{w~|x~|x~><><><?=?=?=?>?>A?A?A?A?A?B@B@B@CACACACACACBCBDBDBDCECDCECFDFDFEGEGEGEGEHFHFHFHFHFIGIGIHIHIHJIJHKIKIKIKIKILJLJLJLJLJLJMKMKMKMKMLMLNLNLOMONOMPNONPNPOPOQOQOQOQOQORPRPRPRPRPRPSQSQSQTRSQTRTRUSUSUSUSUSUSUSUSVTVTVTVTVTUSUSUSVTVTVTVTVTWUVTWUWUWUWUWUWUWUWVXVXVWUWUVTWUVTVTVTUSVTVTUSUSUSUSUSVTVTUSUSUSUSTRTRTRTRTRTRTRTRTRTRSQSQSQSQSQSQRPQOQORPQOQOQOPNPNPNPNONPNOMOMOMOMOMNLNLNLNLMKMKLKLKLJLJLJLJLJLJKIKIKIKIJIJHIHIHJHIGIGIGIFHFHFHFHFHFGEGEGEFDFDFDFDECECDCDBDBDBDACACACAC@B@B@B@A?A?A?A?@>@>@>@>@>?=?=><?=><><><=;=;=;yv~yu~wt~xu~wt~vs~vs~vr~vr~tp~tp~tp~so~so~so~rn~rn~rn~qm~pl~ok~ok~ok~ok~ok~ok~mi~mi~mi~mi~li~li~jg~jg~lg~jf~jf~ie~ie~ie~hd~he~hd~hd~gc~gc~gc~ea~eb~ea~ea~da~d`~d`~c_~c_~b_~b_~a^~b^~a^~a]~a]~`\~`\~_\~_[~_[~_[~^Z~^Z~^Z~]Y~]Y~]Y~\Y~\Y~\Y~[X~[W~[X~[W~ZV~ZV~ZV~ZV~YU~ZV~YU~YU~YU~XU~XU~XU~XU~WT~WT~WT~VS~VS~WS~VS~VS~VR~UR~UR~UR~UR~UR~UR~TQ~TQ~TQ~TQ~TQ~TP~TP~TP~SP~TP~SP~RO~RO~RO~RO~RO~RN~RN~RN~RN~RN~RO~QN~QN~QM~QM~QM~QN~QM~QM~QM~PM~PM~QM~PM~PM~PM~PL~PL~PL~PM~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~OK~OL~PL~PL~PL~OK~PL~OK~OK~OK~PL~PL~PL~PL~OK~PL~OK~OK~OK~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PM~PL~PL~PM~PM~PM~PM~PM~QM~PL~QM~QM~QM~QM~QM~QM~QM~QM~RN~RN~RM~RN~QM~RN~RN~RN~RN~RN~RN~RN~SN~SO~RN~SO~SO~SO~SO~SO~SO~SO~SO~TP~TP~TP~TP~TP~TP~UQ~TQ~UQ~TP~UQ~UQ~UQ~UQ~UQ~VQ~UQ~VR~VR~VR~VR~VR~VR~VS~VR~VR~WS~WS~WS~WS~XS~XT~XT~XS~XT~XT~XT~XT~YT~YT~YU~YU~YU~YU~ZU~ZU~ZU~ZU~ZV~ZV~ZV~ZU~[V~[V~[V~[V~[V~[V~\W~\W~\W~[W~\W~\X~\X~]X~\X~\X~\X~]X~]X~]X~\X~\X~]Y~]X~]Y~]Y~^Y~^Y~^Y~]Y~^Z~^Y~^Y~^Y~^Z~^Y~^Z~^Z~_Z~_Z~^Z~^Z~^Z~_Z~_Z~_Z~_Z~_Z~_[~`\~_[~`[~`[~_[~`[~_[~_[~`[~`[~`[~`[~`\~`\~`[~`[~`[~`[~`\~`[~`[~`[~`\~`[~`\~`\~`\~`[~`\~`\~a\~a\~`[~`\~`\~`\~a]~`\~`[~`[~`\~`\~`\~`\~`\~a\~`[~`[~`[~`[~`[~`[~`[~`[~`\~`\~`\~`\~`[~`[~`[~`\~`[~`[~`[~`[~`[~_[~`[~`\~`[~`[~`\~`[~`[~`[~`[~`[~_[~`[~`[~`[~_[~_Z~_[~_[~_[~^Z~_[~^Z~_[~^Z~^Z~_[~^Z~_Z~^Z~^Z~^Y~^Y~^Y~^Y~]Y~]Y~]Y~]Y~]Y~^Z~]Y~]Y~]Y~]Y~]X~]X~]Y~]Y~\X~\X~]X~\X~\X~\X~\X~[W~[W~\W~\W~[W~[W~\W~[V~[V~[V~[V~[V~[V~ZV~ZW~ZV~ZV~YU~ZU~YU~YU~YU~YT~YU~XT~XT~XT~XT~XT~WS~XT~WT~WS~WS~WS~WS~VS~WR~WR~WR~WR~WR~WR~VR~VR~VR~VQ~VR~VR~UQ~UQ~UQ~UQ~UQ~TP~UQ~UQ~TP~TP~TP~TP~TP~TP~TP~TP~SO~SO~SO~SO~SO~SO~SO~SO~SO~RO~RO~RN~RN~RN~RN~RN~QM~RN~RN~RN~QN~QN~RN~QM~QM~QM~QN~PM~PM~QM~QM~PM~QM~QM~PL~QM~PM~PM~PM~PM~PL~PM~PM~PL~PL~PM~OL~OL~OL~OL~OL~PL~OL~OL~OL~OL~OL~PL~OL~OL~OL~OK~OL~OL~OL~OL~OK~OL~OL~OL~OL~OL~OL~PL~OL~OK~OK~OL~OL~OL~PL~OL~PL~PL~PL~OL~OL~PM~OL~PM~PM~PM~PM~PM~PL~PL~QM~PM~PM~PL~QM~QM~QM~PM~PM~PM~QM~QM~QN~QN~QN~QN~QN~RN~RN~RO~RN~RO~SO~SO~SO~SO~SO~SP~SP~TQ~SP~TP~TP~TP~TQ~TQ~UR~UR~UR~UR~VS~VS~VS~VS~VS~WT~WT~XT~XU~WT~XU~XU~XU~YU~YU~YV~YU~YV~ZW~ZW~[W~[W~[X~[W~[W~\X~\X~\X~]Y~]Y~]Z~]Z~]Z~]Z~^[~^[~_[~_\~`\~`]~a^~a]~a]~b^~b^~c_~c_~b_~c`~d`~d`~d`~d`~d`~ea~ea~fb~fb~fc~gc~gc~gc~hd~hd~hd~ie~jf~jf~kg~kg~kg~kg~kg~lh~li~li~ni~ni~nk~nk~nk~ok~ok~pm~qm~qm~qm~rm~so~so~so~tp~uq~uq~uq~vr~vr~vr~ws~ws~yu~yu~yu~zv~{w~{w~{w~><><>=?=?=?=?>@>@>@>A?A?A?B@B@B@B@B@C@CACBDBDBDBDCDCECECEDFDFDFEGEGEGEGEGEHFGEHFIGIGIHIGIHJHIHJIJIKIKILJLJLJMKMKMKMKLJMKMKMKMLNLNLNLOMOMOMONPNPNPNPOPOQOQPRPQORPRPRPRPSQSQSQSQSQTRSQSQTRTRTRUSUSUSVTVTVTVTVTVTVTVTWUWUWUWUWUVTWUWUWUWUWUWUWUWUXVXVXVXVXVXVXVYWYWZXYWXVXVWUXVWUWUWUWUWUVTWUWUVUWUWUWUVTVTVTVTVTVTUSUSUSUSUSUSUSTRTRTRTRUSTRTRSQSQSQRPRPRPRPQOQOQOQOQOPNQOPNPNPNPNOMNMOMNMNLNMNLMLMKLKLKLKMKLJLJLJLJLJKIKIKIKIJHKIJHIHIHIGIGIFIGHFHFHFGEHFGEGEFDECECFDECECDBDBDBDBCACACACAB@C@B@B@A?A?A?A?@>@>@>@>?=?=?=?=><><><=;=;=;yv~yv~yu~xt~wt~wt~vs~vs~uq~tq~tq~tp~tp~tp~rn~rn~qm~qm~qm~qm~qm~pl~pl~ok~nj~nj~nj~mi~mi~mi~mi~lg~jg~jg~jg~jg~jf~ie~ie~ie~ie~ie~hd~gc~gc~gc~gc~fb~fb~fb~ea~ea~da~d`~c_~c_~b^~b^~b^~b^~a^~a]~a]~`\~`\~`\~_\~_[~_\~^[~^[~^Z~]Z~]Y~\Y~\Y~\Y~\Y~\Y~[X~[W~[X~[W~ZW~ZV~ZV~YV~ZV~YU~YU~YU~YV~XU~XT~XT~WT~WT~WT~WS~WS~VS~WS~VS~VR~VR~VR~VR~VR~VR~UQ~UQ~UQ~TQ~TQ~TQ~TQ~TQ~SP~TP~SP~SP~SP~SP~SO~SP~RO~RO~SO~RO~RO~RN~RN~RN~RN~QM~QN~QN~QN~QN~QM~QN~PM~QM~QM~QM~QM~QM~QM~PM~PL~PL~QM~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PM~PL~PL~PL~PL~PL~PL~PL~PL~PL~PL~PM~PM~PL~PM~PM~PM~PM~PM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QN~QM~QN~RN~QN~RN~QN~QN~RN~RO~RN~RN~RN~RN~RN~RN~RN~SO~SO~SO~SO~SP~SO~SO~TO~TO~SO~TP~TP~TP~TP~UQ~UQ~UQ~UQ~UQ~UP~UQ~UQ~UQ~UQ~UQ~VQ~VQ~UQ~VR~VR~VR~VR~VR~VR~WR~VR~VS~VS~WS~WS~WS~WS~WS~WS~XT~XT~XT~XT~YU~XT~YU~YU~YU~ZV~YU~ZV~ZV~ZV~ZV~ZV~ZV~ZU~ZV~ZV~ZV~[V~[V~[V~[V~[V~\W~\W~\W~\W~\W~\W~\X~\X~\X~\X~\X~]Y~]Y~]Y~]Y~]Y~]Y~]Y~]Y~^Y~^Y~^Y~^Y~^Y~^Y~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~_[~_Z~_Z~_Z~`[~`[~`[~`Z~`Z~`[~`[~`[~`\~`\~`\~`[~`[~`\~`\~a]~`\~a\~`[~a\~a\~a\~a\~a]~b]~a\~a\~a\~a\~a\~a\~a\~a\~a\~a]~a\~a\~a\~a\~a\~a\~a\~a\~a\~a\~b]~a\~a\~a\~a\~a]~a]~`\~a\~a\~a]~a\~b]~a\~a\~a\~a\~a\~a\~a\~a\~a\~a\~a\~a\~a]~a\~b]~a\~a\~a\~a\~a\~a\~a]~a\~a\~`\~a\~`\~a\~a\~`[~a\~a[~a\~`\~`\~`[~_[~`[~_[~`\~`\~`\~_[~_[~_[~`[~_[~`[~_Z~_Z~_Z~_Z~_Z~_Z~_Z~^Z~^Z~^Z~^Z~^Z~^Z~^Y~^Y~]Y~]Y~^Y~]Y~]Y~]Y~]Y~]Y~\X~\X~]X~\X~]X~]X~\X~[W~\X~\W~\W~[W~[W~[W~[W~[V~ZW~ZV~ZV~[V~ZU~ZV~ZU~YU~YU~YU~YU~YT~YU~XT~XT~XT~XT~XT~XT~XT~XS~XT~WT~WT~WT~WS~WS~WS~WS~VR~VR~WR~VR~VR~VR~VR~VR~VR~VR~UQ~VQ~VQ~UQ~UQ~UQ~UQ~TP~UQ~UQ~TP~TP~TP~TP~TP~TP~TO~SP~SP~SP~SP~SP~SO~SO~SO~SO~SO~SO~SO~RN~RO~RO~RO~RO~RN~RN~RN~RN~RN~RN~RN~QM~QN~QN~QN~QM~QM~QM~QM~QM~QM~QM~PL~QM~PM~PM~PM~PM~PM~PM~PM~PL~PL~PM~PL~PL~OL~PL~OL~OL~OL~OL~OL~PL~OL~OL~PL~OL~PL~OL~OL~OL~PM~OL~PL~PL~OL~PM~PL~PL~PM~OL~PM~PM~PL~PL~PM~PM~PL~PL~PL~PM~PM~QM~PM~PM~PM~QM~QM~QM~QM~PM~QN~QN~QN~QN~QM~QM~RN~RN~RN~RN~QN~RO~RN~SO~SO~SO~SO~SO~SO~SP~SP~SP~TP~TP~TP~TQ~TQ~TQ~TQ~UQ~UR~UR~UR~VR~UR~VS~VS~VS~WT~WT~WT~WT~XT~XT~XU~YU~YU~XU~YU~YV~YV~YV~ZV~[W~ZW~[W~ZW~[X~[X~[X~[X~\X~\X~]Y~]Z~^Z~^Z~^Z~^[~^[~^Z~_[~_\~`\~_\~`]~`]~a]~a^~b^~c_~c_~c_~c`~d`~d`~da~ea~ea~ea~ea~ea~eb~fc~gc~gc~hd~hd~ie~hd~ie~ie~if~jf~jf~kg~kg~kg~lg~li~li~li~li~nk~nk~nk~ok~ol~ok~qm~qm~qm~rm~rm~so~so~uq~uq~uq~uq~vr~ws~ws~xt~xt~xu~yu~zv~zv~zw~{w~|x~><>=?=?=?=?=@=@>@>@>@>A?A?B@B@CACACACADBDBDBDCECECDCEDECFDFDFDFEFEGEGEHFHFHFHGHGHGIGHGIHIHJHJIJIKILJLJLJLJLJMKMKMKNLMKMKMKNLNMNMNMNLOMOMONOMPNPNQOQOQORPRPRPSQRPSQSQSQSQSQSQTRTRTRTRTRTRTRTRUSVTVTVTVTWUVTWUWUWUWUWUWUWUXVXVXVXVXVXVXVXVXVXWXVYWYWYWYWYWYWZXZXZXZX[Y[YZYZXYWYWYWXVYWXVXWXWXVXVXVXVXVWUXVXVWUWUWUWUWUWUVTVTVTVTWUVTVTVTVTUSVTUSUSTRTRTRSQSQSQSQSQRPRPRPRPQOQOQOQOQOQOPNPNPNPNOMOMOMOMNLNLMLMLMLMKMKMKMKLJLJLJLJLJLJKIKIKIKIJHJHIHIHIHIGIGHGHGHFHFGEGDGEFDFDFDECECECECDBDBDBCBCBCACAB@B@B@B@B?A?A?A?@>@>@>?=?=?=?=?=><=;><=;=;=;yu~yv~wt~wt~ws~ws~vs~vs~tq~tq~tq~tp~tp~so~rn~qm~qm~qm~pl~pl~pl~pl~ok~ok~nj~nj~nj~mi~mi~kh~kh~kg~jg~jg~jf~jf~jf~jf~hd~ie~he~hd~hd~hd~gc~fb~fb~fb~ea~ea~ea~d`~d`~c_~c_~c_~b_~b_~b^~a^~a]~a]~`\~`\~`\~_\~_[~_[~^[~^Z~^Z~^Z~]Z~]Y~]Y~\Y~\Y~[X~[X~[X~[W~[W~ZW~ZV~ZV~ZV~ZV~YU~XU~XU~XU~XT~XT~XT~XT~WS~WS~WT~WS~WS~VS~VS~VS~VS~VR~VR~VR~UR~UR~UR~UR~UQ~TQ~TQ~TQ~TP~TP~SP~SP~SP~TP~SO~TP~SO~SO~SO~SO~SO~SO~SO~RO~RO~SO~RO~RO~QN~QN~RO~QN~QN~QM~QM~QM~RN~QM~QM~QM~QM~QM~QM~QN~QM~QM~QM~QM~QM~QM~QM~PM~PL~QM~QM~PM~PM~PL~PM~PM~QM~PM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~PM~PM~PM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~RN~RN~QM~RN~RN~RN~RN~RN~QN~QN~RN~RN~SO~SO~SO~SO~SO~SO~SN~SO~SO~SP~SP~SO~TP~TP~TP~TP~TP~TP~TP~TP~TP~UQ~UQ~UQ~TQ~UQ~UQ~UQ~VR~UQ~VR~VR~VR~VR~VR~VQ~WR~WS~VS~VS~WS~WS~WS~WS~WS~WS~WS~WS~XT~XT~XT~XS~XT~XS~YU~YU~YU~YU~XT~YU~ZV~ZU~ZV~ZV~ZV~ZV~[W~[V~[V~[V~[W~[V~[W~[V~\W~\X~[W~\W~\X~[W~\X~\X~]X~\X~]Y~]X~\X~]Y~]X~]Y~]Y~]Y~^Y~^Y~^Z~^Z~^Z~^Z~^Z~_Z~_Z~^Z~_[~_[~^Z~^Z~_[~_[~_[~_[~`[~`[~`\~`[~`[~`\~`\~`\~`\~`\~`\~`\~a\~`\~`\~`\~a]~a]~a]~a]~a\~b]~a]~a]~a]~a]~a\~b]~b^~b]~a]~a]~a]~a]~b]~a]~b]~b]~a]~a]~b]~b]~b^~b^~b]~b^~b\~b]~b]~b]~b]~b]~b]~b]~b]~b]~b^~b]~a]~b^~b^~a]~b^~a]~b^~a]~b]~a]~a]~a]~a]~b]~b]~b]~a]~a]~b]~b]~b]~a]~a]~a]~a]~a]~b]~a]~b]~a]~a]~a]~a]~b^~a]~a]~b]~a\~a\~a]~a]~a]~a]~a]~a\~a\~`\~`\~`\~`\~`\~a\~a\~a\~`\~`\~`[~`\~`[~_[~_[~_[~_Z~`[~_Z~_Z~_Z~_[~^Z~^Z~^Z~^Z~_Z~^Z~^Z~^Y~^Y~^Z~^Z~]Y~]Y~]Y~]Y~]Y~]Y~]X~]X~\X~\X~\X~]X~\X~[W~[W~\X~\X~[W~[W~[W~[W~[W~[V~ZV~ZU~[V~ZU~ZU~ZV~ZV~YU~YU~YU~XT~XT~XT~YT~YT~XT~XT~XT~WS~WS~WS~WS~WS~WS~WS~WS~VS~WS~WS~VR~VR~VR~VR~VR~VR~VQ~VQ~UQ~VQ~UQ~UQ~VQ~UQ~UQ~UQ~UQ~TP~UQ~UQ~TP~TP~TP~TP~TP~TP~TP~SO~SO~SO~SO~SO~SO~SO~RN~SO~SO~SO~SO~RN~SO~RN~SO~RN~RN~RN~QN~RO~QN~QN~QN~QN~QM~QN~QM~QM~QN~QN~QM~QM~QM~QM~PM~PM~QM~QM~QM~QM~QM~PM~PM~PM~PM~PM~QM~QM~QM~PM~QM~PM~PL~PM~PL~PL~PL~PL~QM~PM~PM~PL~PL~PL~QM~PL~PM~PM~PM~PM~PM~PM~QM~PL~PL~QM~QM~QM~QM~QN~QM~RN~QM~QM~QM~RN~RN~RN~RN~QN~QN~RN~RN~RN~SO~RN~RO~RO~RO~RO~SP~SO~SO~SO~TP~SP~SP~SP~TP~TQ~TQ~UQ~UQ~UQ~UQ~UR~UQ~UQ~VR~VR~VR~VS~VS~VS~WT~WT~WT~XT~XT~XU~XT~YU~YU~YU~YV~YV~YV~ZV~ZW~ZW~ZW~[W~[W~[X~[X~\Y~\Y~]Y~]Y~]Y~^Z~]Z~]Z~^[~^[~^Z~^[~_[~`\~`\~`]~`]~a]~b^~b^~b^~b_~b_~c`~c`~d`~ea~d`~ea~ea~eb~fb~fb~fb~gc~gc~hd~hd~hd~ie~ie~ie~ie~jf~jf~kg~kg~jg~jg~li~li~li~li~nk~nk~ok~pk~pk~pm~qm~qm~qm~so~qm~so~so~tp~tp~uq~uq~vr~vr~ws~xt~xt~xt~yu~zv~zv~{w~|x~><><>=?=?=?=@>@>@>@>@>A?A?B@B@B@CACADBDBDBDBDBDCDCECECEDEDFDFDFEGEGEGEHFHFHFHFHGIGIGIGIHJHJHJHJHKILJLKLJMKMKMKMKMKMKMKNLMLNLNLOMNMOMOMOMOMPNPNPNPNQOQOQORPRPRPRPSQSQSQTRTRTRTRTRUSTRUSUSUSVTVTVTVTVTVTWUWUWUXVXVXVXVXVXVXVYWXWYWYWYWYWYWZXZXZXZXYWZXZXZXZXZXZX[Y[Y[Y[Y[Y\Z\Z\Z[Y[Y[YZYZXZXZXYXYWZXYWYXYWYWYWYWYWYWYWXVXVXVXVXVXVXVXVXVWUWUWVWUWUWUWUVTVTUSUTTRTRTRTRSQSQSQSQSQSQSQSQRPQOQOQOQOQOQOPNPNPNPNOMOMOMNMNMNLNLMLMLMKMKMKMKLJMKLJLJLJLJKIKIKIJHJHJHIHIGIHIGHFHFGEGEGEGEGEFDFDFDFDECECECECDBDBDBCABABABAB@A@A@A@A?A?@>@>@>@>?>?=?=><><><><=;=;=;yv~yu~yu~ws~ws~ws~vs~vs~vr~tq~tq~tq~to~ro~ro~qm~qm~qm~pm~pl~pl~ok~ok~ok~nj~nj~mi~mi~mi~li~li~kg~kg~jg~jg~jf~jf~je~ie~ie~ie~hd~gc~hd~gc~fb~fb~fb~fb~ea~d`~ea~d`~d`~d`~c_~b^~b_~b^~a]~a^~a]~a]~_[~`\~_\~^[~_[~^[~]Z~]Z~^Z~]Y~]Y~\Y~\Y~\Y~\X~\Y~\X~[X~ZW~ZW~[W~[W~ZV~ZV~ZV~YV~YV~YU~XU~XU~XT~XU~WT~WT~WT~WS~WT~WS~VS~VS~VS~VS~VR~VS~VR~VR~UR~UR~UR~UR~UQ~UQ~TP~TQ~TQ~TQ~TP~TP~SP~SP~SO~SP~SO~SP~SO~SO~SO~SO~SO~SO~RO~RO~RO~RN~RO~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~QM~QM~QM~QM~QM~QM~QM~RN~QM~QM~QM~QN~QM~QM~QM~RN~QM~QM~RN~QM~QM~RN~QM~QM~QM~QM~QM~QN~QN~QM~RN~QN~QN~RN~RN~QN~RN~QN~RN~QN~RN~QN~RO~RO~RN~SO~SO~SO~SO~SO~RO~RN~SO~SP~SP~TP~SO~SO~SP~TP~SP~TP~TP~TP~TP~UQ~UQ~TQ~UQ~UQ~UQ~UQ~TQ~UR~UR~UR~VR~VR~UR~VR~VR~VR~VR~VR~VR~VR~VR~WS~VS~WS~XT~WT~WS~WS~WS~XT~XT~XT~WS~XT~XT~XT~XT~XT~XT~XT~YU~ZV~ZU~ZV~ZV~[V~ZV~ZV~ZV~[V~ZV~[V~[W~[W~[V~[V~[W~\W~\W~\W~\W~\X~\X~]X~]X~\W~]X~]Y~]Y~]Y~]Y~^Y~^Y~^Y~^Y~^Z~^Z~^Z~_[~^Z~^Z~^Z~_Z~_Z~_[~_Z~_[~`[~_[~`\~`[~_[~`\~`\~`\~`\~`\~`\~`\~`\~a\~a\~a\~a]~a]~a]~a]~a]~a]~b]~a\~b]~b]~a]~b]~a]~b^~b]~b^~b^~b^~b^~b^~b^~b^~b^~b^~c_~b^~b^~b^~c_~b^~b^~b^~b^~b^~c^~c_~b^~b^~b^~c^~c^~b]~c_~c_~c^~b]~c^~c^~c^~b^~c_~c_~c^~b^~c^~b]~c^~b^~b^~b^~b^~c^~b]~c^~b^~c^~c^~b]~b]~c^~c^~c^~c^~b^~c_~b^~b^~b^~c_~b^~b^~b^~b^~b]~b^~b^~b^~b^~b^~c^~c^~b^~b^~b^~b^~a]~b]~b]~a]~b]~a]~b]~a]~a]~b]~a\~a\~a\~a]~a\~`\~`\~`\~`\~`[~a\~a\~`[~_Z~`[~`\~`[~_[~_[~_[~^Z~_[~_[~_[~^Z~^Z~^Z~^Z~^Z~^Z~]Y~^Z~]Y~]Y~]Y~]Y~]Y~\X~]Y~]X~\X~\X~\X~\X~[W~\X~[W~[W~[W~[W~[W~[W~[W~ZV~ZV~ZV~ZV~ZV~ZV~YU~YU~YU~YU~YU~YU~XT~XT~YU~XT~XT~XT~XT~XT~XT~WS~XT~XT~WS~WS~WS~WS~WS~WS~WR~WR~WR~VR~WR~VR~VR~VR~VR~VR~UQ~VR~UQ~UQ~UQ~UQ~UQ~UQ~TP~UQ~TQ~TQ~TP~TP~TP~TP~TP~TP~SO~TP~TP~TP~SP~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~RO~RN~RN~RN~RN~RN~QN~QN~QN~QN~QN~QM~QM~QM~QM~RN~QN~QM~QM~RN~QM~QM~QM~QM~PM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~QM~PM~QN~QN~QN~QM~QM~QM~PL~QM~PM~QM~QM~QM~QM~QN~QM~QM~QN~QM~QN~QN~QN~QN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RO~RO~RO~RO~RO~SO~RO~RO~SP~SP~SP~SP~TP~TP~TP~TQ~SP~TQ~UQ~UQ~UQ~UR~UR~UR~UR~UR~UR~UR~UR~VR~VS~VS~VS~WT~WS~WT~WT~XU~XU~XU~XU~XU~YV~YV~YV~YV~ZW~ZW~ZW~ZW~[X~[W~[X~\X~\X~\X~\Y~]Y~]Z~]Z~^Z~^Z~^[~_[~_[~`\~_\~`\~`\~a]~`]~a^~a^~a]~b^~c_~c_~c`~d`~d`~da~ea~fb~fb~fb~fb~fb~gc~gc~gc~hd~hd~hd~hd~ie~ie~ie~jf~jg~kg~jf~lg~lg~lg~li~li~li~ni~nk~ok~ok~ok~ok~qm~qm~qm~rn~so~so~tp~tp~tp~tp~vr~ws~ws~ws~xt~xu~xu~yu~zw~zw~{x~=<><><>=>=?=@>?=@>A?A?A?A?A?B@B@BACACADBDBDCDCDCECECEDEDFDFDGEGEGEGFHFHFHFHFIGIGIGIGIHIHIHJHJIKIKIKILJMKMKMKMKMKMKMLNLNLNMOMOMONOMONPNPNPNOMPNQOQOPNQOQOQORPRPSQSQSQSQTRTRUSUSUSUSUSUSUTVTVTVTVTWUWUWUWUWVXVXVXVYWYWYWYWYWYWYWYWYWZXZXZXZXZXZY[Y[Y[Y[YZX[Y[Y\Z\Z\Z\Z\Z\Z\Z][][][^\][][\Z\Z[Z\Z[Y[Y[YZYZXZYZYZYZXZXZXZY[YZXYXZXYWYWYWYWYXYWYWYWYWYWYWXVXWXVWVWUVTVTVTUSVTUSUSUSTRTRTRTRTRSQSQSQRPRPRPRPRPQOQOQOQOPNPNPNPNONPNOMOMNMOMNLNLMLMKMKMKMKMKMKMKLJLJKIKIKIKIJHJHIHIHIGIGHGHFGFGEGFGEGEFDFDFDECECECECDCCBCBDBCACACAB@A?A@A?A?@?@?@?@?@>?=?=?=?=><><><=;=;=;=;yu~yu~xt~xt~ws~vs~ur~ur~uq~tq~tq~to~so~ro~ro~rm~qm~pm~pm~pm~ok~ok~ok~nj~nj~nj~mi~mi~mi~mi~lh~kg~jg~jg~jf~jf~jf~ie~ie~ie~hd~hd~gc~gc~gc~fb~fb~fb~ea~ea~eb~da~d`~d`~c_~c_~b_~b^~b^~a]~a]~`\~_\~`\~`\~_\~_[~_[~^[~^Z~]Z~^Z~]Y~]Y~\Y~\Y~\Y~\X~\X~[W~[X~[X~[X~[W~ZW~ZW~ZV~YV~YV~YV~YV~YU~XU~XT~XT~XT~XT~XT~XT~WS~WT~WS~WS~WS~VS~VR~VS~VS~VS~UR~UQ~UR~VR~UQ~UR~UQ~UQ~UQ~TP~TP~TP~TP~TP~TP~SP~TP~SO~TP~SO~SO~SO~SO~RO~RO~RO~RO~RO~RO~SO~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~QN~RN~RN~RN~QN~QN~QN~QN~RN~QN~QN~QN~QN~QN~RN~RN~RN~RN~RN~RN~RN~RN~QN~RO~RO~RN~RN~RO~QN~RO~RO~RO~RO~SO~SO~SO~SP~SP~SO~SO~SO~SP~SP~TP~TP~SO~SP~TP~TP~TP~TQ~TQ~UQ~TQ~UQ~TQ~TQ~UQ~UQ~UQ~UQ~UR~UQ~VR~VR~VR~VR~VS~VS~VS~VS~VS~WS~VS~VS~VS~WS~WS~WS~WS~XT~XT~XT~XT~XT~XT~XT~YU~YU~YU~YU~YT~XT~YU~YU~YU~YU~YU~YU~YU~ZV~ZV~ZV~[W~[W~[V~[V~[V~\W~\W~\W~\W~\W~\X~\W~\W~\X~\X~\X~\W~]Y~]X~]Y~]Y~]X~]X~]X~^Y~^Y~^Y~^Z~^Z~^Z~^Z~_Z~_Z~_Z~_[~_[~_[~`[~_[~_[~`[~`[~`\~`\~`\~`\~`\~a\~a\~a]~a]~a]~a]~a\~a]~a\~b]~a]~a]~b]~b^~b^~b^~b^~b^~b^~c^~b^~c^~c^~b^~b^~c_~b^~b^~c^~c_~c_~c_~c_~c_~d_~c_~c^~c_~c^~c_~c^~c_~c^~c^~c_~d_~c_~c_~c_~c_~d_~c_~d_~d_~d_~d_~d_~c^~c^~d_~c_~d_~c^~d_~c^~c^~c^~c^~c^~c^~c^~c^~c^~c^~c^~c^~c^~c^~d_~d_~d_~c^~d_~c_~b]~c^~d_~c_~b^~c_~c_~c^~c_~c_~c^~d_~c_~c^~c_~c_~c_~d_~c_~c_~c_~c^~c^~b^~b^~b^~b^~b^~b^~b^~b]~c^~b^~b^~b]~b^~b^~a]~a]~a]~a]~`\~a\~a\~a\~a\~`\~`\~`\~`\~`[~`[~`[~`[~_[~_[~_[~_[~_[~_[~_[~_[~^Z~_[~^Z~^Z~^Z~^Z~^Y~^Y~]Y~]Y~]Y~]Y~]X~]X~]X~\X~\X~]X~\X~\X~[W~[W~[W~[W~ZV~[W~[W~[V~[V~ZV~ZV~ZV~ZV~ZV~ZV~YV~YU~YU~YU~YU~YU~XT~YU~XT~YT~XT~XT~XT~XT~XT~WS~XT~WS~WS~XS~XS~WS~WS~WS~WS~WS~VR~WS~WS~WS~VR~VR~VR~VR~VR~VR~UQ~UQ~UQ~UQ~UQ~TQ~TQ~UQ~TP~TP~TP~TP~TP~TP~TP~TP~SP~SP~SP~SO~TO~SO~SO~SO~SO~RO~SO~RO~RN~RN~SO~RN~RO~RO~RN~RN~RN~SO~RN~RN~RN~RN~QN~RN~RN~RN~QN~QN~RN~RN~QM~QN~QN~QM~RN~RN~QM~QM~QM~QM~QN~QN~QM~QN~QN~QN~QM~QM~QN~QN~QN~QN~QN~QN~RN~RN~RN~QN~QN~QN~QN~QN~QN~RN~RN~RN~RO~QN~RN~QN~RO~RO~RO~RN~SO~SO~SO~SO~SO~SO~SO~TP~SP~SP~SP~TP~TP~TP~TP~TP~UQ~UQ~UQ~UQ~VR~UR~UR~VR~VS~VR~VS~VS~VS~VS~WS~WT~WT~WT~XT~XU~XU~XU~XU~XU~YU~YU~YV~YV~ZW~ZW~ZW~ZW~[W~[X~[X~[W~\X~\X~\Y~\Y~]Y~]Y~]Z~^[~]Z~^[~^Z~_[~_[~_\~`\~`\~`]~a]~a^~a^~a^~b^~b_~c_~c`~c`~d`~d`~ea~ea~eb~fb~fb~fc~fc~gc~gc~hd~hd~hd~ie~he~ie~ie~ie~jf~jf~jg~jg~kg~lg~li~li~mj~mj~nk~nk~ok~ok~ok~qm~qm~rm~rm~rn~so~so~uq~uq~uq~uq~vr~vr~vs~xu~xu~xu~xu~zw~=;=;><><><>=?=?=?=@>@>@>A?A?A?B@B@B@CACACBCBDBDBDCECECEDFDFDFDFEGEGEHFHFGEHFHGHGIGIGIHIHIHJHJHJHKIKILJLJLJLJMKMKNLNLNLNMNLOMOMOMONPNPNPNQOQOQOQOQOPNQOQOQORPRPRPSQSQSQTRTRUSUSUSUSUSVTVTVTUTVTWUWUWUWUXVXVXVXVYWYWYWYXYWZXZXZXZXZXZXZXZYZY[Y[Y[Z\Z\Z\Z\Z\Z][\[\Z\[\Z\Z][][][][^\^\^\^\^\_]^]^\^\][][][]\\[\Z\Z[Y[Z[Z[Z[Z[Z[Z[Z[Y[Z[ZZY[Y[Y[Y[Y[Y[YZYZYZXYXZXZXYXYWYWXVXVWUWUWUVTUTUTVTVTVTVTUSUSTRTRTRTRSQSQSQSQRPRPRPRPQOQOQOQOQOQOPNPNPNOMONNMNMNMNLNLMLMKMKMKMKMKLJLJKJKIKJKIKIJHJHJHIGIGHGHGHFHFHFGEGEGEGEFDFDFDECECECECDBDBCACACABAB@B@A@A?@?@?A?@>@>?>?>?=>=>=><><><=;=;=;<:<:xt~ws~ws~ws~ws~uq~uq~uq~sp~tq~tq~so~ro~ro~ro~pm~pm~pm~pl~pl~ok~ok~ok~nj~mi~mi~mi~lh~kh~kh~kg~jg~jg~je~jf~ie~ie~hd~hd~hd~hd~hd~gc~fb~fb~fb~ea~ea~eb~d`~d`~d`~c_~c_~c_~b_~b^~a^~a^~a]~a]~`\~`\~_[~_[~^[~_[~^[~^Z~]Z~]Z~]Y~\X~]Y~\X~\X~[X~\Y~[X~[X~[X~[W~ZW~ZW~ZW~YV~YV~YV~YV~XU~XU~XU~XT~XU~XU~XT~WT~WT~WT~WT~WT~WS~WT~VS~VS~VR~VS~VR~VR~UR~UR~UR~UR~UR~TQ~TQ~UQ~UQ~UQ~UQ~UQ~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~SP~SO~SP~SO~SP~SO~SO~RN~SO~RN~RN~SO~RN~RO~RN~SO~RN~SO~RO~RO~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~QN~RO~RN~RN~RO~SO~RO~RO~RO~RO~RO~RO~SO~SO~RN~SO~SO~SO~SP~SP~SO~SO~TP~SO~SO~SP~SP~SP~TP~SP~TO~SO~TP~UQ~TP~UQ~UQ~TQ~TQ~UQ~UQ~UQ~UR~UQ~UQ~VR~VR~VR~VR~VR~VR~WS~VR~WS~WR~WS~WS~WS~WS~WT~WT~WT~WT~XT~XT~XT~XT~XT~XT~XT~XT~XT~XT~XT~XT~YU~YU~YU~YU~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~[W~[W~\W~\W~\X~\X~\X~\X~]X~\W~\X~]X~]X~]X~]X~]X~]X~]Y~^Y~]X~^Y~^Y~^Y~^Z~^Z~^Z~^Y~^Z~_Z~^Z~_[~_[~_[~_[~`[~`[~_[~`\~`\~`\~`\~`\~`[~`\~`\~`\~a]~a]~a]~b]~a]~a]~a]~b^~b^~b^~b]~b^~b^~b]~b]~b]~b^~b^~c^~c^~b^~b^~b^~b^~c_~c_~c_~c_~c_~d_~c_~c_~c_~d_~d_~d`~d`~d`~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~c_~d_~d`~d_~d_~c_~b^~c_~c_~c_~c_~c_~c_~c_~c^~c_~c^~c^~b^~b^~b^~b^~b]~b^~b^~a]~b^~b]~b^~a]~b^~a]~a]~a]~`\~a]~`\~a]~`\~`\~`\~`\~`\~`\~`\~`\~_[~_[~_[~_[~_Z~_[~_[~^Z~^Z~^Z~^Z~^Z~^Y~]Y~^Z~]Y~]X~]X~]X~\X~\X~\X~\X~[X~\W~\W~\W~\W~[W~ZV~ZV~[W~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZU~ZU~YU~YU~YU~YU~YU~XT~YU~XT~XT~XT~XT~YU~XT~XT~XT~XT~WT~WT~WT~WS~XS~XS~XS~WR~WS~WS~VR~VR~VR~VR~VS~VR~VR~UQ~VR~VR~VR~VR~UQ~UQ~UQ~UR~TQ~TQ~TQ~TQ~TQ~TQ~TQ~TP~TP~TP~TP~TP~SP~TP~SP~SP~SP~SP~SO~SO~SP~SO~RO~SO~SO~SO~SO~RN~SO~RO~RO~SO~RN~RN~RN~RO~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~RN~QM~QN~QN~QN~QN~RN~RN~RN~QN~RN~QN~RN~RN~QN~QN~RN~QN~RO~RO~QN~RO~RN~RN~RN~RO~RO~RO~RO~SO~SO~SO~SO~SO~SP~SO~TP~TP~TP~TP~TP~TQ~TQ~TQ~TQ~TQ~UQ~TQ~UR~UR~UR~UR~UR~VS~VR~VS~VS~VS~VS~VS~WT~WT~WS~XT~XU~XU~XT~XU~YU~XU~XU~YU~YV~YV~ZV~ZW~ZW~ZW~ZW~ZW~[W~[W~[X~\X~\Y~\Y~\Y~\Y~]Z~]Z~^[~^[~_[~^[~_\~_[~_\~`]~a]~`]~a]~a]~a^~b_~c_~b_~b_~c`~d`~d`~d`~d`~ea~eb~fc~gc~gc~gc~gc~gc~gc~hd~hd~ie~jf~jf~jf~jf~jf~jg~jg~jg~kg~kh~kh~li~mi~mi~nj~ok~ok~ok~pk~qm~qm~rn~rn~so~so~so~so~uq~uq~uq~vr~ws~ws~xt~xu~zw~zw~zw~zw~=<><><><?=?=?=@>@>@>A?A?A?B@B@B@B@CACACADBDBDCECECEDFCFDGEGEGEGEGEHFIGHFIGHGIGIHIHIHIHJHJIKIKJLJLJLJLJMKMKMKMKNLNLOMOMOMOMONONPOPOQOQOQOQOQOQORPRPRPQORPRPRPSQSQSQTRTRUSUSVTUSVTVTVUWUWUWUWUWUWUXVXVXVYWYWYWYWZXZXZXZXZYZXZYZY[Z[Y[Y[Z[Z[Z\[[Z\[\[][][][^\^\^\]\]\]\^\^\^]^\_]_]_]_]_]_]`^`____^_]_]^]^]^]]\]\][][][]\]\][\[\[\[]\\[\[\[\Z][\Z\Z\[[Z[Z[Z[Z[Z[Z[Y[YZYZYZXYWYWXVXVXVWVVUWUWUWUWUWUVTVTVTVTUSUSTRTRTRTRSQSQSQRPRPRPRPRPQORPQOQOQOQOPNPNONOMOMNMNMNMNLNLMKMKMKMKMKMKLJLJKIKIKIKIJIIHJHIGIHHGHFHFHFGEGEGEFDFDFDFDECECECDBDBDBCACACABAA@B@A?A?@?@?@>@>?>?=?=?=><><><=;=;=;=;=;<:xt~xt~ws~ws~ws~vr~uq~uq~uq~tq~tq~to~ro~ro~rn~rm~qm~pm~pl~ok~ok~ok~nj~nj~nj~mi~mi~lh~lh~kg~kg~kg~jg~jg~jg~ie~je~hd~hd~hd~gc~hd~gc~fb~fb~fb~eb~ea~ea~d`~d`~d`~c_~c_~c_~b^~a^~a]~a]~a]~`]~`\~`\~_\~_\~`\~^[~^[~^[~^Z~^Z~]Y~]Z~\Y~\Y~\X~\X~\X~\X~[X~[X~ZW~[W~[W~ZW~ZW~ZV~ZV~YU~YU~YU~XU~XU~YU~XU~XU~XT~XT~XT~XT~WT~WT~WT~WT~WT~WS~WS~VS~VR~VS~UR~VR~VR~UQ~VQ~UQ~UQ~VQ~UQ~TQ~UR~TQ~UR~UQ~TQ~TQ~TQ~TQ~TP~TP~TP~SP~TP~SO~SO~TP~SO~SO~TP~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~RN~SO~SO~SO~SO~SO~SO~SO~SO~SO~SP~SO~SO~SP~SP~SO~SO~SP~SP~SP~TP~TP~SP~SP~SP~TP~TP~TQ~TQ~UQ~UQ~TP~UQ~UQ~UQ~UQ~UQ~UQ~UR~UR~UR~UQ~UR~VR~VR~VS~VR~VS~VS~VS~VS~WS~WS~WS~WS~WT~WS~WS~XT~WT~XT~XT~XT~XT~YU~YU~XT~YU~YU~YU~YU~YU~YU~YU~YU~ZV~YU~YU~ZV~ZV~ZV~ZV~ZW~ZW~ZW~[W~[W~ZV~[W~[W~[W~\X~\X~\X~\X~\X~\X~\X~]X~]Y~]Y~]Y~]Y~^Y~^Y~^Z~^Z~^Y~^Z~^Z~^Z~^Y~^Y~^Z~^Z~^Z~_[~^Z~_Z~_[~`[~`[~`\~_[~`\~`[~`\~`\~`\~a\~a]~a]~a]~a\~a]~b]~a]~a]~a]~b^~b^~a]~b^~b^~b^~b^~c^~c^~c^~c^~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~da~da~d_~d_~d_~d_~d_~d_~e`~fa~ea~d_~d_~d_~d_~d_~ea~ea~ea~ea~ea~ea~ea~d_~ea~ea~d_~fa~fa~fa~e`~e`~d_~e`~e`~e`~ea~d_~e`~d_~d_~e`~d_~d_~d_~d_~d_~d_~d_~d_~d_~ea~d_~fa~e`~d_~d_~d_~ea~d_~d_~d_~d_~d_~d_~e`~da~da~da~ea~d_~d_~e`~d_~d_~d_~d_~c_~d_~c_~d`~d`~d`~c_~c_~d`~c_~c_~c_~c_~c_~c^~b^~b^~c_~b^~b^~b^~b^~b^~b^~b^~b^~a]~b]~b^~a]~a]~a]~a]~a\~a]~a]~a\~`\~`\~a\~`\~a\~`\~_[~_[~_[~_[~_[~_Z~^Z~^Z~^Z~^Z~^Y~^Y~^Y~]Y~]Y~]Y~]Y~]Y~]Y~\X~\X~\X~\X~\W~\W~\W~\W~[W~ZV~[W~[W~[W~[W~ZV~[V~ZV~ZV~ZV~ZU~ZU~YU~ZV~YU~YU~YV~YU~YU~YU~YU~YU~YT~XT~XT~XT~XT~XT~XT~XT~XT~XT~XS~XT~XT~WS~WS~WS~WS~WS~WS~VS~VS~VS~VR~VR~VR~VR~VR~VR~UR~UR~UR~UR~UQ~TQ~UQ~UQ~TP~UQ~UQ~UQ~TP~TP~TQ~TQ~TP~TP~TP~TP~TP~TP~SP~SP~SP~SP~SP~SP~SP~SO~SO~SO~SO~SO~SO~SO~RO~SO~SO~SO~SO~SO~RN~SO~RN~RN~SO~RN~RN~RN~SO~RN~RN~RN~RO~RO~SO~RN~RN~RO~RO~RO~RO~RO~RO~SO~SO~RO~RO~RO~SO~SO~SO~SO~SO~SO~SO~TP~SO~SO~TP~TP~TP~TP~TP~TP~TP~TP~UQ~TQ~UQ~TQ~TQ~UQ~UQ~UR~UR~UQ~VR~VR~UR~VS~VS~VS~VS~WS~WT~WT~WT~XT~XT~XT~XU~XU~XU~YU~XT~XU~YU~YU~ZV~ZV~ZW~ZV~ZW~ZW~[W~[X~[W~[W~\X~\X~]Y~]Y~]Z~]Z~^Z~^Z~^Z~^[~_[~_[~_[~`\~`]~`\~`]~a]~a]~a^~b^~a^~b^~b_~b_~c`~d`~d`~d`~ea~ea~fb~fb~fb~gc~gc~gc~gc~hd~ie~ie~ie~ie~jf~jf~kg~kg~kg~kg~jg~kg~kg~li~mi~mi~mi~mi~mi~ok~ok~pl~pl~qm~qm~rn~rn~so~so~tp~uq~tq~tq~vs~vs~wt~xu~yu~yu~=;=;=;=<=<>=><?=?=?>@>@>@>A?A?A?B@B@B@BABACBCBDBDCECECECFDFDFDGEGEGEHFHFHFHGIGIGIHIHJIJIJIJIKIKIKILJLJMKMKMKMKMKMKNLNLNMOMOMPOPNQOQOQOQPQOQORPRPRPRPRPRPRPRPRPSQSQSQTRTRTRTRUSUSVTVTVTWUXVXVWVXVYWYWYWYWXWXWZXZXZXZXZXZX[YZY[Y[Y\Z\Z\[\Z][][\Z][]\]\]\^\^\^\^\^\_]_]_]_^_^_^_]`^`^`^`^`_`^`^`^`_`_a`a``_`_`^`_`_`_`__^_^_]^\^]^]_]^]_]^]_]^\_]^]^\^\]\^\^\^\][\[\[\[\[\[\[\[[Z\[[ZZXZXYWXWXWXWXVXWYWXVXVWUWUWUWUVTVTVTVTUSVTUSUSUSTRSQSQSQSQSQSQRPRPRPRPRPQOQOQOQOQOPNPNOMOMOMNLNLNLMKNLMKMKMKLJLJLJLJKIKIKIJIIHIHIHIGHFHFHFGFGFGEGEFDFDEDECECECDBDBDBCACACAB@B@B@A@A@A?@?@>@>?>?>?=?=><><><=;=;=;=;=;<:<:xt~ws~ws~ws~vr~ur~vr~uq~uq~sp~so~so~ro~ro~qm~pm~pm~pk~ok~ok~ok~ok~nj~mi~mi~mi~lh~lh~kg~kg~kg~jg~jg~if~if~ie~ie~hd~hd~hd~gd~gc~fb~gc~fb~ea~ea~ea~da~da~d`~d`~c_~c_~b_~b^~b^~a]~a]~`]~`]~a]~`\~`\~`\~_[~_[~_[~^Z~^Z~]Z~]Z~]Z~]Y~\Y~\Y~\Y~\Y~\X~[X~[W~[X~[W~[W~ZW~ZW~ZW~YV~ZV~ZV~YV~YV~YU~YU~YU~YU~XU~XU~XT~XT~XT~WT~WT~WT~WT~WT~XT~WS~WS~WS~WS~VS~VR~VR~VR~VR~VR~VR~VR~UR~UQ~UR~UQ~TQ~TQ~TQ~UP~TP~UP~UQ~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TP~SO~SO~TP~TP~TP~TP~TP~TP~SP~SP~SP~SP~SP~TP~TP~TP~TP~TP~SP~TP~TP~TP~TP~TP~TP~TP~TP~TP~TQ~TQ~TQ~TP~TP~UQ~UQ~UQ~UQ~UQ~TQ~UQ~UQ~UQ~UR~UR~UR~VR~VR~VR~VR~UR~VS~VR~VS~WS~VS~VS~WS~WS~WS~WS~WS~XT~WT~XU~XT~XT~XT~XT~XT~XU~XU~XU~XU~YU~XU~YV~YU~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZW~ZW~[W~[W~ZV~[V~\X~[W~[W~[W~[W~[W~\X~\X~\X~[W~\X~]Y~]Y~^Y~]Y~]Y~]Y~]Y~]Y~^Z~^Z~^Z~^Z~^Y~^Y~^Z~^Z~_[~_[~_[~_[~^Z~_[~_[~_[~`\~`\~_[~`\~`\~`\~`\~`\~`\~`\~a]~a]~b^~a]~b^~b^~b^~b^~b^~b^~b^~b^~b^~b^~b^~b^~b^~c_~c_~c_~c_~d`~c_~d`~d`~c_~c_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~d_~da~ea~d_~d_~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~fa~fa~fa~fa~ea~ea~ea~ea~ea~ea~ea~fa~fa~fa~fa~fa~fa~fa~fa~fa~fa~fa~fa~fa~fa~fa~fa~fa~fa~ea~ea~gb~fa~gb~fa~fa~f`~fa~fa~fa~fa~gb~fa~fa~ea~ea~fa~fa~fa~fa~fa~fa~fa~ea~ea~fa~ea~ea~ea~fa~fa~fa~fa~fa~ea~fa~fa~ea~fa~fa~d_~ea~ea~ea~da~da~da~da~ea~d`~d_~d_~d`~d`~c_~c_~c_~d_~d_~c_~c_~c_~c_~c_~b^~b^~c_~b^~b^~c^~c_~b^~b^~b^~b^~b]~a]~a]~a]~a]~a\~a\~a]~`\~a\~a\~`\~`\~`[~`[~`[~_[~_[~_[~_[~^Z~^Z~_Z~^Z~^Y~^Z~]Y~]Y~]Y~]Y~]Y~\X~\X~\X~\X~]X~\W~\X~\X~\W~[W~[W~[W~[W~\W~\W~[W~[W~[W~[W~[V~ZV~ZV~ZV~ZV~ZV~ZV~YV~ZV~ZU~YU~YU~YU~YU~YU~YU~YU~YU~XT~XT~YT~YT~XT~XT~XT~XT~XT~WS~WS~WS~WS~WS~WS~VS~VR~VR~VR~VR~VR~VR~VR~UQ~UQ~VR~UQ~VQ~VQ~UQ~UR~UQ~UQ~UR~UQ~TQ~UQ~UP~UQ~TQ~TP~UQ~TP~TP~TP~SP~TQ~SP~SP~SP~SP~TP~SP~SP~SP~SP~SO~SO~SO~SO~SO~SO~SP~SP~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~SO~TP~SO~SO~SO~SO~TP~TP~TP~TP~TP~TP~TQ~UQ~TQ~TQ~TQ~UQ~UR~UQ~UQ~UR~UR~UR~UR~UR~VS~VR~VR~VR~VS~VS~WS~WS~WS~WT~XT~WT~WT~XT~XU~XU~XU~XU~YU~XU~YU~YV~YV~ZV~ZV~ZV~ZW~ZW~[W~[W~[X~[X~[X~\X~\Y~\Y~\Y~]Y~]Y~^Z~]Z~]Z~^Z~_[~_[~_\~`\~`\~`\~`]~a]~a]~a^~b^~b^~b^~b_~b_~c`~d`~d`~d`~d`~ea~ea~fb~eb~gc~gc~gc~hd~hd~hd~hd~ie~ie~ie~ie~jf~kg~lg~jg~jg~jg~li~li~mi~mi~mi~ok~ni~ok~ok~ok~pl~pl~pl~qm~rn~ro~rn~so~tq~tq~uq~vs~vs~wt~xu~xu~yu~yu~<:=;=;><><><?=?=@>@>@>@>A?A?A?A?B@B@BABACACBCBDCDCECECECFDFDFDGEGEGEHFIGIGIGIGIHJHJIJIJIJIKIKJKILJMKMKMKMKMKNLNLNLNLOMONPNPOPOQOQOQPQPRPRPRPRPSQSQSQSQSQSQSQTRTRTRTRUSTRUSUSUSUSUTVTVTWUXVXVYWYWYWYXYXZXYWZXZXZXZX[Y[Y[Y\Z\Z\[][][][]\]\]\]\^\^\_]_]_]_]_^_^`^_^`^`^`^`^a_`_a_a_a_a_a_b`b`a`b`b`b`b`babacbbaa`bab`baa`a`a``_`_`_`^`_a_`^`^`_`^`^_^_^_^`^`^_^_^^]^]^]]\]\]\]\]\]\]\]\\[\Z[Z[Y[YZXYXYXYXZXZXYWYWXWXVWUWVXVWUWUVUVUUTUTUSUSUSUSUSUSTRUSTRSQSQSQSQSQRPRPRPQPQOQOQOPNPNONONOMOMOMNLMLMKMKMKMKLJLJLJLJKIKIKIJHJHIHIGIGHGHGGFGFGFGEFEFDFDEDEDECDBDBDBDBCACACACAB@B@A?A?A?@?@??>?>?=>=>=?<><><=<=<=;=;<:<:<:ws~ws~ws~ws~vr~uq~uq~uq~sp~sp~so~so~so~so~qm~pm~pm~pl~ol~ok~nk~ok~nj~mi~lh~lh~lh~mi~kg~kg~jg~if~jg~ie~ie~ie~ie~ie~hd~hd~gd~gc~fb~fb~fb~fb~ea~ea~ea~d`~c`~c`~c_~c_~b^~b^~b^~a]~a]~a]~a]~`\~`\~`\~_[~_[~_[~^Z~_[~^Z~^Z~]Z~]Z~]Z~\Y~]Y~\X~\X~\Y~[X~[X~[X~[X~[W~[W~ZW~ZW~ZV~ZW~ZV~ZV~YV~ZV~YV~YU~XU~YU~XU~XT~XU~XU~XU~XT~XT~XT~WT~XT~WT~WT~WS~VS~WS~WS~VR~WS~VR~VS~VS~VR~VR~UR~VR~UR~VR~UQ~UQ~UQ~UQ~UQ~TQ~UQ~TP~TQ~TQ~TQ~TQ~TQ~TQ~TQ~TQ~TQ~TP~UQ~UQ~TP~TP~TP~UQ~TQ~TP~TQ~TP~TP~TP~TQ~TP~UQ~UQ~TP~TQ~UQ~TP~TQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UR~UQ~UQ~UQ~UQ~UQ~UR~VR~VR~UR~VR~VR~VR~VR~VR~VS~VS~VS~WT~VS~WS~WS~WS~WT~WT~WT~WT~WT~WT~XT~XT~XT~XT~XT~YU~YU~YU~YU~ZU~YU~ZV~YV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZW~ZW~[W~[W~[W~[W~[W~[W~[W~\X~\X~\X~\X~\X~\X~\X~\X~]Y~\X~\Y~]X~\X~\X~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~_[~_[~^Z~_[~_[~_Z~`[~`[~`[~`[~`[~`\~`\~`\~`[~`\~`\~`\~`\~`\~a]~a]~a]~a]~a]~b^~b^~b^~b^~b^~b^~b^~b^~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~d`~c_~d`~d`~d_~d_~da~d_~d_~ea~d_~e`~ea~d_~d_~ea~ea~ea~fa~ea~fa~fa~fa~ea~ea~fa~fa~gc~fa~fa~fa~fa~gb~gb~gc~gc~gc~fa~fa~gc~gc~fa~gc~gc~gc~gc~gc~gb~gc~gc~gb~gb~gc~gb~gc~gc~gb~gb~gb~gb~hc~gc~fa~fa~gb~hc~gb~hc~gb~gb~gc~gb~gb~gb~gb~gb~gb~gb~gb~gb~gb~gb~gb~fa~fa~gb~fa~fa~gb~fa~fa~fa~fa~fa~gc~fa~fa~gc~fa~fa~fa~fa~fa~fa~ea~ea~ea~ea~ea~ea~ea~ea~da~ea~da~fa~d_~d_~d_~d_~d_~d_~d`~d_~d_~d_~d`~d`~c_~c_~c_~c_~c_~c_~c_~c_~c_~c^~b^~b^~b^~b^~b^~b^~b^~b^~b]~a]~a]~a]~a]~a\~a\~`\~`\~`\~`\~`\~`[~_[~_[~_[~_Z~_[~^Z~^Z~^Z~^Z~^Z~^Z~^Z~]Y~]Y~^Z~]X~]X~]Y~]X~]X~]X~\X~\X~\X~\X~\X~\W~[W~[W~\X~\X~[W~[W~[W~[V~[V~ZV~ZV~ZV~ZV~ZV~ZV~YU~YV~ZV~ZV~ZV~YU~YU~YU~YU~YU~YU~XU~XT~XU~XT~XU~XT~XT~XT~WT~XT~WT~WS~WS~WS~VS~VS~VS~WS~VS~VS~UR~VR~VR~VR~VR~VR~VR~UR~UR~UR~UR~UQ~UQ~UQ~UR~TQ~UQ~UQ~UQ~UQ~TQ~TQ~UQ~UQ~TQ~TP~TP~TP~TQ~TQ~TP~TQ~SP~SP~TP~SP~TP~TP~SP~SP~SP~SP~SP~TP~TP~TP~TP~TP~TP~SP~SP~SP~SP~SO~TP~SO~TP~TO~TO~SP~TP~TP~TP~TP~TP~TP~TP~SP~TP~TP~TQ~TP~TQ~TQ~UQ~UQ~UQ~UR~UR~UR~UR~VS~VR~UR~VR~VR~VS~VS~VS~VS~WS~WT~WS~WT~WT~WT~XT~XT~XT~XU~XU~YV~XU~XU~YU~YU~YV~YV~ZV~ZV~[W~[W~ZW~ZW~ZW~[X~[W~[X~\X~\X~\X~\Y~\Y~]Z~^[~^[~^[~^Z~^[~_[~_[~_\~`\~`\~`]~`]~a^~a]~a]~b^~b_~c_~c`~c`~c_~d`~d`~d`~ea~ea~ea~fb~gc~gc~gc~hd~hd~hd~hd~ie~ie~ie~ie~jf~jf~jf~jg~kh~kh~kh~li~mi~mi~mi~ni~mj~ok~ok~ok~ok~qm~pl~qm~qm~rn~rn~ro~so~tq~tq~vq~vq~vq~vs~xs~yu~yu~=;=;=;=;><><><?=?=?=@>@>@>A?A?A@A@B@B@B@BACBCBCBCBECDCEDFDFDGEGEGEGEHFHGHGIGIGIGJHJIJIKIKJKJKILJLJLJMKMKMKNLNLNLNMNMOMONONPOQPQOQPQPRPRPRPSQSQTRTRTRTRTRTRTRTRTRTRUSUSUSUTUTVTVTVTVTVTWUWUWUYWXVYWZXYWZXZXZY[Y[Y[Y[Y\Z\Z][][][^\^\^\^\^]_]_^_]_]^]_^_^_^`_`^a_a_a_a_a_b`a_b`b`b`babab`b`cacacacacbcbcadbdbdbdcdbdbdcdbdbdbdccababab`a`a`b`b`b`b`a`a`a`baa`a_a_a_a_a_a_`_`^`__^_^_^_^_^_^^]^\][][\[\Z\Z\Z[Y[Z[ZZYZXZXYWYWYWXWXWXWXVWVWUWUWUWUVTVTVTVTVTVTVTUSUSUSTRTRSQSQSQRPSQSQRPQOQOQOQOPNPNPNONOMOMNMNLNLNLMKMKNLMKLJLJKIKIKIJIKIJHJHIGHGHGHFHFGEGEFEFDEDFDECECECDCDBDBDBCACACAB@B@A?A@A?A?@>@>@>?>?=>=>=><><=;=;=;=;<:<:<:<:<:xt~ws~vr~vr~vr~uq~uq~so~so~so~so~ro~qn~qn~pm~ol~ok~pm~ok~nk~nj~nj~nj~nj~mi~mi~kg~kg~kg~kg~jg~je~jg~jf~ie~ie~hd~hd~hd~gc~gc~fb~fb~fb~fb~ea~ea~ea~da~da~c`~c_~c_~c_~b^~b^~b^~a]~a]~a]~`]~`\~`\~`\~_[~_[~_[~^Z~^Z~^Z~^Z~^Z~]Y~]Y~\Y~]Z~\X~\X~\X~\X~\X~\X~\X~[W~[W~[W~ZW~ZW~ZW~ZW~ZW~YV~ZV~ZV~YV~ZV~ZV~YU~YU~YU~XT~XU~XU~XU~XT~XT~WT~WT~WT~WT~WS~WS~WS~WS~VS~VS~WS~VS~VS~VR~VR~VR~VR~VR~VR~VR~VR~VR~UQ~UR~UQ~UQ~UR~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UR~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~VR~VR~UR~UR~UQ~VR~UR~VR~VR~VR~UR~VR~VR~VR~VR~VR~VR~WS~WS~WS~VS~WS~WS~WT~VS~WS~WT~WT~WT~WT~WT~XT~XU~XU~XU~YU~YU~YU~YU~YV~YV~YU~YV~YV~ZV~ZV~ZV~ZV~ZV~[W~[W~ZW~[W~[W~[X~[X~[W~\X~\X~[W~\X~\X~\X~\X~\X~\X~\X~]X~]Y~]Y~]Y~]Y~]X~]Y~]Y~]Y~^Z~]Y~^Z~^Z~_[~_[~_[~_[~_[~_[~_[~`\~`[~_[~_[~`\~`\~`\~a\~`[~a\~a\~a\~a]~a\~a\~a\~a]~b]~a]~b^~a\~b^~b^~a]~b]~b^~c^~c_~b^~c^~c_~c_~c_~c_~c_~c^~d`~d`~d`~d_~c_~d_~d_~d_~d_~d_~da~da~da~da~ea~ea~ea~ea~ea~ea~d_~fa~fa~fa~fa~fa~ea~fa~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~hc~hc~gb~hc~gc~gc~gc~hc~hc~hc~hc~hc~hc~id~hc~hc~hc~hc~hc~id~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~gc~gc~hc~gc~gc~gc~gc~gb~hc~gb~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~fa~gc~fa~fa~fa~fa~fa~ea~fa~ea~da~ea~ea~ea~ea~d_~da~da~d`~d_~d`~da~d`~c_~d_~c_~d`~c_~c_~c_~c_~c_~c_~c_~c_~c_~c^~b]~b]~b^~b^~b^~b^~b]~a]~a]~a]~a\~`\~`\~`[~`\~`[~`\~`\~_[~_[~_[~_[~_[~_[~^Z~^Z~^Z~]Y~^Z~^Y~^Y~]Y~]Y~]Y~]Y~]Y~]X~]Y~]Y~]Y~]Y~]X~\X~\X~\X~\X~[W~[W~\X~[W~[W~[W~[W~[W~[W~ZV~ZV~ZW~ZW~ZW~ZV~ZV~ZV~ZV~ZV~ZV~YV~YU~YU~YV~XU~XU~XU~XU~XT~XT~XT~XU~XU~XT~WT~WT~XT~WS~WT~WS~VS~WT~VS~WS~VS~VS~VS~VS~VR~VR~VS~VR~VR~VR~VR~UQ~VR~UR~TQ~UQ~UQ~UQ~TQ~UR~UR~UQ~UQ~UQ~UQ~UQ~UQ~UR~TQ~TQ~UQ~TQ~UQ~TQ~TQ~TQ~TQ~TQ~TP~TQ~TP~TP~TP~TP~TQ~TP~TP~TP~UQ~TP~TQ~SP~TP~TP~TP~UQ~TQ~TP~TQ~UQ~TQ~UQ~UQ~UQ~UQ~TP~UQ~UQ~UR~UR~UR~VR~VR~VR~VR~VR~WS~VS~VR~VS~VS~WS~WS~WT~WT~WT~WT~WT~WT~XT~WT~YU~YU~YU~YV~YU~YU~ZV~YU~ZV~[W~ZW~YV~ZW~ZW~ZW~[W~[X~[W~\X~\Y~\X~\X~\Y~\Y~]Z~]Z~\Y~]Z~]Z~^Z~^[~^[~_[~_\~`\~`\~`\~`\~a]~a]~a]~b^~b^~b^~c_~c_~c_~c`~d`~ea~ea~ea~eb~eb~fb~fb~gc~gc~hd~hd~hd~ie~ie~ie~jf~ie~jf~jg~jg~jg~jg~li~li~li~mi~mi~ni~nj~nj~ok~ok~pl~pl~pl~qm~qm~rn~rn~ro~ro~tp~tq~uq~vq~vs~vs~ws~xt~<:yu~=;=;=;=;><><><?=?=?=@>@>@>@>A?A@A@B@B@BACBCBCBDCDCDCECFDFDFEGEGEGEHFHFIGIGJHJHJHKIKIKIKJKJKJLJLJLJMKMKNLNLNLOMOMOMOMPNONPNPOQOQORPRQSQSQSQSQSQTRSQTRTRUSUSUSUSUSVTVTVTVTVTWUWUWUWUXVWUWUXVXVYWYWYWYWZX[Y[Z\Z[Z\Z\Z\Z][][^\^\^\_]^\_]_]`^`^`^`^`^`_a_`_a``_`_a`a`b`b`b`cacacacadbdbdbdbdbdcdcecdcdcdcdbecececedfdfdfdfdfdfdeddcdcdccbdccbcbcbcbdbcacabacacbbacacbbabababab`b`a`a`a`a`a`a``_`__^_^_]^\^\^\]\]\]\\[\[\Z[Z\Z[YZYZXZXZXYXXWYWYWXWXVXVXVXVXVXVXVWUWUWUVTVTVTUSUSUSTRTRTRSQSQRPRPRPRPQOQOQOQOQOPNONONOMOMOMOMNLNLMKMKMKLJLJKIKIKIKIJHJHJHIGIGHGHFGFGEFEFEFDFDFDECDCDCDCDBCACACACACAB@B@B@A?A?A?@?@>?>?>?=?=>=><=<><=;=;=;=;<:<:<:<:ws~vr~ws~ws~vr~tp~tp~tp~so~rn~rn~rn~qn~qn~pk~pk~pm~ok~nk~nj~nj~nj~mi~mi~mi~mi~kg~kg~kg~jg~jg~jf~jf~je~ie~ie~hd~gc~gc~gc~gc~fb~gc~ea~fb~ea~ea~ea~d`~d`~c`~c`~b_~b_~b^~b_~a^~a]~a]~a]~`\~`\~`]~_[~_[~^[~^Z~^Z~^Z~^Z~^Z~^Z~^Z~]Z~]Y~\Y~\Y~\Y~\X~\X~[X~\X~[W~\X~\X~[X~[X~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZV~ZV~YV~YU~YV~YV~YV~YU~YU~YU~XU~XU~XT~XT~XT~XT~WT~WT~WT~WT~VS~WS~WS~XT~WS~WS~WS~VS~WS~WR~VR~VR~VR~VR~VR~VR~VS~VR~VR~VR~VR~VR~VR~UR~VR~VR~VR~VR~VR~VR~UR~UR~VR~VR~VR~VS~VR~VR~VR~VR~VR~VR~VR~VS~WS~VR~VR~VS~WS~WS~WS~WS~WS~WS~WT~WS~WS~XT~XT~WT~WT~WT~WT~WT~XT~XT~XT~XU~YU~XU~XU~XU~YU~YU~YV~YU~YU~YU~YV~YV~ZV~ZV~ZV~ZV~[W~ZW~ZV~[W~[W~[W~[W~\X~\X~[X~\X~\X~]Y~]Y~\X~\X~\Y~]Z~]Y~]Y~]Y~]Y~]Y~]Y~^Y~^Y~]Y~^Z~]Y~^Y~^Z~^Z~_[~_[~^Z~^Z~^Z~`[~`[~`[~`\~a]~a]~a\~`\~a]~a]~a]~a]~a]~a]~a]~a]~a]~a]~a]~b]~b]~b^~b^~b^~b^~b^~c_~c_~c_~c_~c^~c_~d_~d_~c_~c_~d_~d_~d`~d`~ea~ea~d_~da~d`~da~da~ea~ea~ea~ea~ea~fa~ea~ea~ea~ea~ea~fa~gc~gc~gc~fa~fa~fa~fa~gc~gc~gc~gb~gc~gc~gc~fb~gc~hc~hc~hc~hc~hc~hc~hc~id~hc~hc~hc~hc~ie~hc~ie~ie~ie~ie~hc~hc~id~ie~ie~ie~ie~je~ie~ie~ie~id~id~hc~je~id~je~hc~ie~ie~ie~je~hc~ie~ie~ie~ie~hc~hc~hc~hc~hc~id~id~je~id~id~id~id~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~hc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~fa~fa~gc~ea~gc~ea~fa~fa~fa~ea~fa~fa~fa~ea~da~ea~d_~d_~d_~d_~e`~d_~d_~d`~d`~c_~d`~c_~c_~c^~d^~c^~c_~c_~c_~b^~b^~b^~b^~a]~b^~a]~`\~a]~a\~a]~`\~`\~`\~`\~`[~_[~`\~_[~_[~_[~_[~_[~_Z~_Z~_Z~_Z~_Z~_Z~^Z~^Y~^Y~^Y~]Y~]Y~^Y~]Y~]Y~]Y~]Y~]Y~\X~\X~\X~\X~\X~\X~\X~\X~\W~\W~[X~[W~[X~[W~[W~[W~[W~[W~ZV~ZV~ZV~ZV~ZV~ZV~YV~YV~YV~YU~YU~XU~XU~XU~XU~XU~XU~WT~XU~WT~XT~XT~XT~WS~XT~WS~XT~WS~WT~WS~VS~VS~WS~WS~WS~VS~VS~WS~VR~UR~UR~VR~VR~VR~VR~UQ~UR~UR~UQ~UR~UR~UR~UQ~UR~UR~TQ~UQ~UQ~UR~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~TQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UQ~UR~UR~UR~VR~VR~UR~UR~UR~VR~VR~VR~VS~VS~VS~VS~WS~VS~VS~VS~WS~WS~WS~XT~WT~WT~XU~XU~XU~XU~XU~XU~XU~XU~YV~YV~YV~YV~YV~ZW~ZW~ZW~ZW~[W~[W~[W~[X~[X~\X~\X~\Y~\Y~\Y~\Y~]Y~]Y~]Z~^Z~]Z~^Z~^Z~_[~_[~_\~`\~`\~`]~`]~a]~a]~a]~a^~b^~b^~b_~b_~c_~d`~d`~d`~d`~ea~ea~fb~fb~gc~gc~gc~gc~hd~hd~ie~ie~ie~jf~jf~jf~jg~jg~jg~lg~li~li~li~li~mj~mi~mj~ok~ok~ok~pl~pl~pm~pm~qm~rn~rn~so~sp~so~tq~tq~uq~vs~uq~ws~ws~xt~xt~<:<:=;=;><><><><?=?=@>@>@>A?A?A?A@B@B@BABACADBCBDBDCECEDFDFDFDGEGEHFHFHGHGIHJHJIKIKIKJKJLKLKLKMKMKNLMKMKNLNLOMOMOMOMPNPNQOPORPQOQORPRPSQSQTRTRTRTRTRUSUSUSVTVTVTVTVTVUWUWUWUXVXVXVXVXVXWYWYWYWYWYWZX[Y[Y[Z\Z\[\[][]\][][][^\_]`^_^_^`^`_a_a_a_a`a`a`b`bababababacacacbcbcbdbdbecececfdfdfdfdgegefefefefefefefegefefefefegfgfgegegefefefdfdedededededececdcdcdcdcecdcecdcdcdcdcdccbdbcbbacbbababaa`a`a_`_`^_^_^_^^]^]^\^]]\]\][\Z\Z\[[Y[Y[Y[Y[YZXZXYWYWYWYXYXYWXVXVXVXVXVXVWUWUWUWUVTVTUSUSTRTRSQSQSQSQRPRPRPRPQOQOPNPNPOPNPMONOMNLNLNLMKMKMKLJLJKIKIKIJHJHIGIGHGHGHFHFGFGEGEFDFDEDEDECECDCDCDCCBCACACABAB@B@A?A?A?A?@>@>?>?=?=>=><><><=<=;=;=;<:<:<:<:<:ws~vr~vr~vr~uq~uq~tp~tp~so~so~ro~rm~rm~qm~qm~ol~pk~nk~ok~nk~nj~nj~mi~mi~mi~lh~kg~kg~kg~jg~jg~jg~ie~ie~he~hd~ie~gc~gd~gd~fc~fc~fb~ea~ea~d`~d`~d`~d`~d`~c_~d`~c_~b^~b^~b^~b^~a]~a]~a]~`\~`\~`]~`\~_\~_[~^[~^Z~^Z~^Z~^[~]Z~]Z~]Z~]Z~]Z~]Y~]Z~]Y~\X~\Y~\X~\X~\X~\X~[W~\X~[W~[W~[W~ZW~ZV~ZV~ZW~ZV~ZW~YV~YV~YV~YV~YV~YV~YU~YU~YU~YU~YU~XT~XT~XT~XT~YU~XT~XT~XT~XT~XT~WS~WS~WS~WT~WS~WS~WS~WS~WS~WT~VS~WS~WS~VR~VR~WS~WS~WS~WS~WS~VS~WS~WS~VR~WS~VR~WS~WS~WS~VS~WT~WS~WS~WS~WS~WT~WS~WS~WS~WS~WT~WT~WT~WT~WT~WT~WT~WT~WT~WT~XS~XS~XT~XU~XU~XU~XU~XU~XU~XU~XU~YU~YV~YV~ZV~YV~YV~YV~YV~YV~ZV~ZV~[W~[W~[W~[W~[W~[W~[W~[X~\X~[W~\X~\X~\X~\X~\X~]Y~]Y~]Y~\X~]Z~]Z~]Y~^Z~^Z~]Y~]Y~^Z~]Y~^Z~^Z~^Z~_[~_Z~_[~_Z~_[~_[~_[~_[~_[~`\~`\~_[~a]~a]~a]~a]~a]~b^~b]~b]~b]~b]~a]~b]~c^~c^~c^~b^~b^~c^~c^~c^~c_~c_~c_~c_~b^~c_~c_~c_~d`~d`~d`~c_~d`~d_~d_~d_~fa~d_~da~ea~da~ea~ea~ea~ea~fa~fa~ea~ea~ea~ea~ea~ea~ea~ea~gc~fb~gc~gc~gc~gc~gc~gc~gc~gc~hd~hd~hc~hc~hc~hc~hd~hd~hd~je~hd~hd~ie~je~je~je~je~je~je~je~ie~je~je~je~je~ie~je~je~je~je~je~je~je~ie~je~je~je~je~je~je~je~je~je~je~je~je~je~ie~je~je~je~je~je~kg~kg~je~je~je~je~je~je~je~je~je~jd~je~je~je~je~je~je~je~je~je~ie~je~je~je~je~je~ie~id~id~je~je~je~je~je~hc~hc~hc~id~id~hc~id~id~hc~hc~gc~gc~hc~hc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~fa~fa~fa~ea~ea~ea~ea~ea~ea~ea~ea~ea~da~ea~d_~da~d_~d`~ea~d`~e`~d_~d`~c_~c_~c_~c_~c^~c_~c_~c^~c_~b^~b^~b]~b]~a]~a]~a]~a]~a\~a\~a\~a\~`\~`\~`\~`\~`\~`\~`[~_[~`[~_[~_[~_[~`[~_Z~_Z~_Z~_Z~_Z~^Z~^Y~^Z~^Z~]Y~^Z~^Z~]Y~]Y~]Y~]Y~]Y~]Y~]Y~\X~\X~\X~\X~\X~\X~\W~[W~\X~\X~[X~[W~[W~ZV~ZV~[W~ZW~ZV~YV~YV~YV~ZV~ZV~YU~YV~YU~YU~XU~YU~XU~XU~XU~YU~XT~YU~XT~XT~XT~XT~XT~WT~WT~WT~WT~WS~WS~WS~WS~WS~VR~VR~WS~WS~VS~VS~VS~VS~VR~VR~UR~UR~VS~VR~VR~VR~UR~VR~UR~UR~UQ~UR~UR~UR~UR~UR~UQ~UR~UQ~VR~VR~UR~VR~UR~UR~VR~VR~UR~VQ~VR~VR~VR~VR~VR~VR~VR~VS~VS~VS~VR~VR~VS~WS~VS~WS~WS~WS~WS~WT~WT~WT~WT~XT~XT~XT~XT~XT~XU~XU~YU~YV~YV~YV~ZV~YV~ZV~ZV~ZV~ZV~ZV~ZW~[W~[W~ZW~[W~[W~[W~[X~\Y~\Y~\Y~\Y~\Y~\Y~]Y~]Y~]Z~^Z~]Z~]Z~]Z~^[~_[~_[~_[~_\~_\~`\~`\~a]~a]~a]~a]~b^~b^~c_~b_~c_~d`~c_~d`~da~ea~ea~fb~fb~fb~fb~fc~gd~gd~ie~he~jf~ie~jf~jf~kg~jg~jg~lg~li~li~li~li~li~mj~nj~ok~ok~ok~ok~pl~pl~pl~pm~qm~qm~rn~ro~ro~tp~tq~tq~uq~uq~ws~ws~ws~xt~xt~yu~<:=;=;=;><><>=?=?=?=?=@>@>@?A?A?A@B@BACABACACBCBDCECECEDFDFDGEGEGEHFIGIGIGIHIHJIJIKJKJLJLKLKMKMLNLNLOMNLNLOMOMPNPNPNPOQOQPQPQPSQSQSQSQSRTRTRTRUSUSUSVTVUWUVTWUWUWUXVXVXVXVXVXVXVYWYWYXZXZXZXZXZX[Y[Y\Z\Z\Z\Z][]\]\]\]\^\^]_]_^`_`^a_a`a`bab`b`bacbcacbcbcbcadbdbeccbdcdcdcecedfefefefdfdgegfgfhfhfhfhfgfhfhfigighgihgfhfgfhghghfhfhfigigihhfgfgffegfgfgegegefegefefefefefdfefefefeedfeededededdcdccbcbcbcabababaa`a``_`__^_^_^^]^]^]]\]\][][][\[[Z[Z[Y[Y[Y[Y[Y[ZZYZXZXYWYWYWYWYWXVWVXVXVWUWUVTVTUSUSUSTRSQSQSQSQSQRPRPRPRPQPQOQOPOPNONOMNLNLNLMKMKLJMKLJLJKIKIKIJHJHIGIGHFHFHFHFGFFEFEFEFDFDEDECDCDCDBCBCBCACAB@B@B@B@A?A?A?A?@>@>@>?=?=><><=<=<=;=;=;<;<:<:<:<:<:;9vr~vr~vr~uq~uq~tp~tp~sp~so~so~qm~qm~qm~qm~pk~pm~nk~nk~nj~nj~nj~mi~mi~mi~lh~kh~kg~kg~kg~jg~ie~ie~ie~ie~ie~he~gd~gd~gc~fb~fc~fb~fb~ea~ea~ea~d`~d`~d`~c_~c_~b_~b_~b^~b^~b^~a^~a]~`]~a]~`\~`\~`]~`\~_\~_\~_[~^Z~_[~^Z~^Z~^Z~]Z~^Z~]Z~]Z~]Z~]Z~]Y~]Y~\Y~]Y~\Y~\Y~\X~[X~\X~[X~\X~[W~[W~[W~[W~ZW~[W~ZV~ZW~ZW~YV~YV~YV~YV~YV~YV~YV~YV~YV~YV~YU~YV~YU~YU~YU~YU~XT~YU~XT~XT~XT~XT~XT~XT~XT~XT~XT~XT~XT~WS~XT~XT~WS~WS~XT~XT~WS~WS~WS~XT~WS~WT~WT~XT~XT~WS~XT~WT~XT~XT~XT~XT~XT~XU~XT~WT~XU~XU~XT~WT~XT~XU~XU~XT~XU~XT~XU~YU~YU~YU~YU~YV~YU~YU~ZV~YV~YV~ZV~ZW~ZV~[W~ZW~ZV~[W~[W~[W~ZW~[X~[W~[X~\X~\X~\X~\Y~\Y~]Y~\X~\Y~]Y~]Y~]Z~]Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~_[~^Z~_[~_[~_[~^Z~_[~_[~_[~`[~`\~_[~`\~`\~`\~`\~`\~`\~a\~a]~a\~a]~a]~b^~b^~b^~c^~c^~b^~b^~c_~c_~c_~c_~c_~c_~c_~c_~c^~d_~c_~c_~c_~c_~d`~d`~d`~d_~da~d_~e`~e`~fa~d_~fa~fa~e`~fa~ea~ea~fa~fa~fa~fa~fa~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~hc~hd~hd~hd~hd~hd~hd~hd~je~je~je~je~je~je~je~je~je~je~je~kg~je~je~je~kg~je~kg~kg~kg~kg~kg~je~kg~je~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~ke~kg~kg~lg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~ke~ke~kg~kg~lg~ke~ke~ke~ke~kg~kg~kg~je~je~ie~je~je~je~je~je~je~je~kg~je~je~je~je~je~je~je~ie~ie~ie~je~je~ie~ie~ie~ie~ie~ie~ie~hc~hd~hc~hc~hd~hd~gc~gc~gc~gc~gc~fb~gc~gc~gc~gc~ea~fa~fa~fa~fa~ea~fa~ea~ea~da~ea~fa~e`~e`~d_~d_~ea~e`~d`~d_~d`~c_~d`~d_~c_~c_~c_~c_~c_~b^~c^~c^~b^~b^~b^~b^~b]~a]~a]~a]~a]~a\~a\~a\~`\~`\~`\~`\~`\~`\~`[~`[~`[~`[~_[~_[~_[~_[~_[~_Z~_Z~^Z~_Z~^Z~^Y~_Z~^Y~^Y~^Y~^Y~^Y~]Y~]X~]Y~]Y~]Y~\X~]Y~\X~\X~\X~\X~\X~[X~[W~[W~[W~ZW~[W~ZW~ZW~ZW~ZV~ZW~YV~ZV~ZV~ZV~YU~YU~XU~XU~XU~XU~YU~YU~XU~XU~XU~XT~XT~XT~XT~XU~XT~XU~XT~XT~WT~XT~WT~WS~WS~WT~WS~VS~VS~WS~WS~WS~WS~WS~VR~WS~WS~WS~VS~VS~VS~VR~WS~VR~VR~VR~VS~VS~VR~VR~WS~WS~VR~VS~VS~WS~VR~VR~VR~WS~WS~WS~VR~WS~WS~WS~WS~WS~WS~VS~WT~WT~XT~XT~XT~XT~XT~XT~XT~XU~YU~YU~YU~YU~YU~YU~YU~YV~YV~ZV~YV~YV~ZW~YV~ZV~ZW~ZW~ZW~[W~[W~[W~[X~[X~[X~\X~\X~\Y~\Y~\Y~\Y~\Y~\Y~]Y~]Y~^Z~^Z~^Z~^Z~_[~^[~_[~_[~_[~_\~`\~`\~`]~`]~a]~a]~a]~a^~b^~c_~b_~c_~d`~d`~d`~ea~ea~ea~fb~fb~fc~gc~gc~gc~gc~he~he~ie~ie~jf~jf~jf~kg~lg~lg~li~kh~li~li~li~mi~mi~nj~nj~ol~ok~pl~pl~qm~pl~qm~rn~rn~ro~so~so~tq~uq~uq~ur~vr~ws~ws~ws~<:<:<:=;=;=;=;><><><?=?=?=?>@>@?A?A?A?A@B@B@BACACADBDBDBECFDFDFDFDFDGEHFHFHGIGIHIHJHJIJIKIKJKJLKLKMKNLNLNLOMOMOMOMPNPNPOQOQPRPQPRPRPSQSQSQTRTRUSUSUSVTVTVTVTWUWUXVXVXVXVXVXWYWYWYWZXZXZXZXZX[Y[Z[Y[Y[Y[Z\[][][][][^\^\^]_]_^`^`^`^`_`_a`a`babacbcbcbdccbdcdbececeddcedededfdfdedfefdfegehghggfighfigigigjiihihjhjhjhjhjhjhkijhjiihihjijhjikijijijhjijiihihigighghghfhgigighghgighghgighggfgfhfgfgffegefefdfeeddcecdbdcdccbcacbbaa`a`a`a``_`_`_`__]_]^\^\]\][][\[][][][\[\[\[\Z[Y[Y[YZYYXZXZXYWYWXVXVXVXVWUVTVTVTUSUSUSTRTRSQSQSQSQSQSQRPRPQOQOPOONONONOMNMMLMLMKMKLKLJKIKIKIJHJHIGIGIGIGIGHFHFHFGEGEFEFDFDEDECECDCDCDBCBCBBACAB@B@A?A?A?A?@>@>@>@>@>?=?=?=><><><=<=;=;=;=;<:<:<:wt~vr~vr~ws~uq~tp~tp~tp~so~so~so~so~qm~qm~pm~pm~pm~pk~nk~ol~nk~nj~mi~mi~mi~lh~kg~kg~kg~jg~jg~jg~jf~ie~ie~he~hd~gd~hd~gc~gc~fc~fb~fb~fb~ea~eb~eb~da~da~d`~c`~c`~c_~c_~c_~b^~a]~a]~a]~a]~a]~a]~`]~`]~_\~_\~_\~_[~_[~_[~_[~^Z~^Z~^Z~^Z~^Z~^Z~]Z~]Z~]Y~]Y~\Y~]Y~\Y~\Y~\Y~\Y~]Y~\X~[X~\X~[X~[W~[W~[W~[W~[W~[W~ZW~ZW~ZW~ZV~YV~ZV~ZW~YV~YV~YV~ZV~ZV~YV~YV~YV~ZV~YU~YU~YU~XT~XU~YU~XT~XU~XU~XU~YU~YU~XU~YT~XT~XT~XU~XT~XT~XT~XT~XT~XU~XT~XT~XU~XU~YU~XU~XU~XU~XU~XU~XU~XU~XU~XU~XU~XU~YU~YU~YU~YU~YV~YU~YU~YV~YU~YV~YV~YV~ZW~ZV~ZV~ZV~[W~[W~[W~[W~ZV~ZW~[W~[W~[X~[X~[X~\X~\Y~\X~\X~\X~]Y~]Y~]Y~]Y~]Y~]Y~]Y~]Z~^Z~^Z~^[~_[~^Z~^Z~_[~_[~_[~_[~_[~_[~_[~_[~`\~`\~`\~`\~`\~`\~`\~a\~`\~`\~`\~`\~a]~a]~a]~a]~b^~b]~b]~c_~b^~c_~d_~d_~d_~d_~c_~d_~d`~d`~d`~d`~c_~c_~d`~d`~d_~d_~d_~e`~d_~d_~ea~ea~ea~ea~fa~fa~fa~fa~fa~fa~fa~fa~fa~fa~fa~fa~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~hc~hc~hc~hd~hc~hd~ie~ie~ie~ie~je~ie~ie~je~je~ie~ie~je~je~kg~je~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~lg~kg~lg~kg~kg~kg~kg~kg~kg~kg~lg~mg~mg~mg~mg~mg~mg~lg~lg~lh~lh~mi~mi~lg~lh~mi~mg~mg~mg~mg~mg~mi~mi~mi~mi~mg~mg~mg~mh~mg~mg~ni~lg~mh~mh~mg~mg~mg~mg~lg~lg~mg~mg~mg~lg~mg~lg~lg~kg~kg~kg~kg~kg~kg~kg~lg~kg~kg~kg~kg~kg~kg~kg~kg~kg~je~je~kg~je~je~je~je~je~ie~ie~ie~je~je~ie~je~hd~hd~hd~hd~hd~hd~gc~hd~gc~gc~gc~gc~gc~gc~gc~fa~gc~gc~fb~ea~fa~fa~fa~fa~fa~fa~fa~fa~fa~fa~da~ea~da~ea~ea~d`~d`~e`~d`~c_~c_~c_~c_~c_~b]~b^~b^~b^~b^~c_~b^~b^~c^~b]~b^~b^~a]~a]~a\~a]~a]~a]~a\~a\~`[~`\~`\~`\~`\~`\~`\~_[~_[~_[~_[~_[~_[~_[~_Z~^Z~^Z~_Z~^Z~^Z~^Z~^Y~^Y~^Y~]Y~]Y~]Y~]Y~^Z~]Y~\Y~\Y~\X~\Y~\X~\X~\X~\X~[W~[W~ZW~[W~ZW~ZW~ZW~ZV~ZV~ZV~ZV~ZV~ZV~ZV~YV~YV~YV~YU~YU~YV~YU~YV~XU~XU~XU~YU~YU~YU~XT~XT~XT~XT~WS~XT~XT~XT~XT~XT~WS~XS~WS~WS~WT~WT~WT~WT~VS~WS~WS~WT~WS~WS~WS~XT~WS~WS~WS~WS~WS~WS~WS~XT~WT~WT~XT~WS~WS~XT~WS~XT~XT~XT~XS~XT~XT~XT~XT~XT~XU~XT~XT~XU~XU~XU~XU~YU~YU~YU~YU~YU~ZV~YV~YV~ZV~ZV~ZW~ZW~ZV~ZW~[W~[W~[W~[W~[X~[W~\X~\X~\Y~\Y~[X~\Y~\Y~\Y~\Y~]Y~]Y~]Y~]Z~]Z~]Y~]Z~^Z~^[~^[~^[~^[~_\~_\~`\~`\~`\~`\~a]~`\~a]~a]~b^~b^~b^~b_~c_~c`~c`~da~da~ea~ea~eb~fb~fb~gc~gc~gc~gd~hd~hd~ie~ie~if~jf~jg~kg~lg~lg~lg~li~li~li~mj~mj~nj~nj~ok~ok~ol~pl~pl~pl~pl~rn~rn~rn~ro~sp~sp~sp~tq~uq~ws~ws~vr~ws~ws~xt~<:<:=;=;=;><><><><?=?=?=?>?>@>@?@?@?B@B@B@BACACBDBDBDBDBECECFDFDGEGEGEGFHFHGIGIHIHJIJIJIKJKJLJLKMKNLNLNLOMOMPNPNPNPNQOQOQPRPRQSQSQTRTRTRTRTRTRUSUSVTWUWUWUWVWVXVXVYWYWZXZXZXYWZXZX[Y[Y[Z\Z\Z\Z\Z\[^\\[][][^\^\^]^]_]`^_^`_`_`_a`a`bab`b`cbcbcbdbecececedededfegegegegegefegfhfhfhfhfgfhgighghgihjijijikikikjkjkjkikikilkkjkjljljljljkjkiljkjkjkjkjljljlkljkjkjjijijijijhjijikjjijijijijhjhjhihihjhihjhigigighggfgfgehfgefeedededdcdcdcdcdccbbabab`a_`_`_`^`^_^_^^]_]^]^]^]^]^\]\^\\Z\Z\Z\Z[Z[ZZY[ZZYZXYXXVXWXVXVWUWUWUWUVTVTUSUSTRTRTRTRSQSQRPRPRPQPQOQOPNOMOMNMNMNMMLNLMKLKLJLJKIKIKIJHJHJHIGIGIGHFHFHFHFGEGEGEFDFDFDECDCDCDCCBCBBABAB@B@B@A?A?A?A?A?@>@>@>?=?=?=?=><><=<=<=;=;<;<:<:<:<:<:vr~vr~uq~uq~uq~uq~so~so~so~so~so~qm~qn~qm~qm~ol~qm~ok~ni~ni~mi~mi~lh~lh~lh~kg~kg~kg~jf~ie~ie~ie~ie~he~hd~gd~hd~hd~gd~gc~fc~fb~fb~eb~eb~ea~ea~d`~d`~da~d`~c_~c_~b_~c_~b^~b^~a^~a]~a]~a]~a]~`\~`\~`\~`\~`\~_\~_[~_[~_[~_[~_[~_[~^[~^Z~^Z~^Z~^Z~^Z~^Z~]Z~^Z~]Y~]Y~]Y~]Y~]Y~\Y~\Y~\Y~\X~\Y~[X~\X~\X~[W~[X~[X~[W~ZW~ZW~ZW~[W~[W~[W~ZW~ZW~ZV~ZW~ZV~ZV~YV~ZV~YU~ZV~YV~YV~YV~YV~YV~YU~YV~YV~YU~YU~YU~XU~YU~YU~YU~YU~YU~YU~YV~YU~YU~YU~YU~YU~YU~YU~YU~YU~YU~YV~YV~YV~YV~YV~YV~YV~YV~YV~YV~ZV~ZV~YV~ZV~ZV~ZW~ZW~ZV~[X~[W~ZW~[W~[X~[X~[W~[X~\X~[X~\X~\X~\X~\Y~]Y~]Y~]Y~]Y~]Y~]Y~]Y~]Z~]Z~^Z~^Z~^Z~_[~_[~^Z~_[~_[~`\~_[~`\~_[~`\~`\~`\~a]~a]~a]~a]~a]~a]~`\~a]~a]~a]~a]~a]~a]~b]~b]~b]~b^~b^~c^~c_~c_~c_~c_~d`~d`~d`~d_~d`~ea~d`~d`~d`~ea~ea~e`~ea~ea~e`~ea~fa~e`~fa~fa~fa~fa~fa~fa~fa~fa~gc~fa~fa~fa~gc~gc~gc~gc~fa~gc~gc~gc~gc~gc~gc~hc~gc~gc~gc~ie~hd~hd~hd~hd~je~ie~je~je~je~je~je~kg~kg~je~kg~je~kg~kg~kg~kg~kg~kg~kg~kg~kg~lg~kg~kg~lh~lh~lg~lg~mg~kg~mi~mi~mg~ni~ni~mi~mi~lh~mh~mh~ni~ni~ni~ni~ni~ni~ni~ni~ni~ni~mi~mi~mi~ni~ni~ni~ni~mi~ni~ni~ni~mi~ni~ni~ni~oi~ni~ni~ni~ni~ni~ni~ni~ni~ni~mh~ni~ni~ni~ni~ni~mh~mh~mh~ni~ni~mh~mg~mg~mg~mg~mi~mg~mg~mg~mg~mg~mg~mi~lg~kg~kg~kg~lh~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~je~kg~je~je~je~je~je~ie~je~ie~ie~hd~hd~ie~gc~hd~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~fa~fa~fa~fa~fa~fa~fa~fa~da~ea~ea~ea~da~d`~ea~d`~e`~d_~c_~c_~c_~c_~c_~c_~c_~c^~c_~c_~c_~c_~b^~b^~b^~c^~b]~b^~b^~b]~a]~b]~a]~a]~a\~a]~`\~`\~`\~a]~`\~`\~`\~`\~_[~_[~_Z~_[~_[~_[~_[~_Z~_Z~^Z~^Z~^Z~^Z~^Z~^Y~]Y~^Z~]Y~]Y~]Y~]Z~]Y~\Y~\X~\X~\X~\X~\X~[X~[W~[W~[W~ZW~[X~[W~[W~[W~[W~[W~ZW~ZV~ZV~ZV~ZW~YV~YV~YU~YU~YV~YV~YV~YV~XU~YU~YU~YU~XT~YU~YU~XT~YU~YU~XU~XU~XT~XT~XT~XU~XT~XU~XT~XT~XT~XT~XT~XT~XT~XT~XT~WT~WT~XT~XT~XT~XU~WT~XT~WT~XU~XU~XU~XU~XU~XU~YU~YT~YU~YU~XU~YU~YU~YU~YU~YV~YU~ZV~YV~YU~ZV~ZV~ZV~ZV~ZV~ZW~ZW~ZW~ZW~ZW~[W~[W~[X~[X~[X~[X~\X~\X~[X~\Y~\Y~\Y~\X~\Y~]Y~]Y~]Y~]Z~^Z~]Z~^Z~^Z~^Z~^[~^[~_[~_[~`\~_[~_\~`\~`\~`\~a]~a]~a^~a]~a^~b_~b_~b_~c_~c_~c_~c_~ea~d`~ea~ea~ea~fb~fb~fb~gc~gc~hd~hd~ie~jf~ie~jf~jf~kg~jf~jg~jg~li~li~li~mi~mi~mi~nk~ok~ol~ok~ok~ok~ok~pl~pl~qm~rn~rn~so~so~ro~sp~sp~uq~vr~vr~vr~ws~wt~xt~yv~=;=;=;=;><><><>=?=?=?=?=?>?>@>@?A?A?A@B@B@CACACADBDBECECECFDFDGEFEGEGFHFHGHGIGIHIHJHKIKIKILJLKMKMKMKNLNLOMOMOMPNPNPOQOQPRQRPSQSQSRSQTRTRUSUTVTVTVTWUWUXVXVXVXVXWYXYWZXZXZX[Y[YZX\Z[Z\[][][]\^\][^\^\^\^\^]_]_]`^_^`_`_a_a`a`babacacbbadccbdcecededfdfdfefegegfhghghgighgighghgihihigigjhjhjhjijijijikjkjljljmklklklklklkmkmlmlmlmlmlnlmklkmlmlmlmlnlmlmlmlmlmlmllklklklklklkljlklklklklkljljljljkjlkkjkjljkjkijijijhihhghggfgfhfgfgfgefefefefeededdcdcdbcacbbaa`a`a``_`_`_`^_^_^_^_^_]^\^\][^\][\Z\Z\Z\Z[Z[Y[YZXYWYWYWYWXVXVWVWUWUVTVTUSUSUSTRTRSRSQSQSQRPQOQOQOPNPNPNPNOMNMNLMLLKLKLJLJLJKIKIJHJHJHJHIGIGIGHFHFGEHFGEGEFDFDEDEDECECDBDBCBCABAB@B@A@A@A@A?A?@?@?@>@>@>?=?=?=>=><><><><=;=;<;<;<:<:wt~vr~vr~vq~uq~uq~tp~tp~tp~tp~so~rn~rn~qm~qm~qm~qm~pk~ok~nj~nj~nj~nj~lh~lh~lh~lh~kg~kg~jf~jf~if~ie~ie~he~he~hd~he~hd~gc~gc~gc~gc~gc~fc~fb~ea~ea~ea~d`~d`~d`~c_~c_~c_~c_~b_~b_~b^~c_~b^~a^~a^~`]~`]~`]~a]~`]~a]~`\~_\~`\~`\~_\~_\~_[~_[~^[~^[~^Z~_[~^[~^[~^Z~^Z~]Y~]Y~]Y~]Y~\Y~]Y~\Y~\Y~\Y~\X~\X~[X~[X~[X~\X~\X~[X~[X~[X~[X~[W~[W~\X~[W~[W~[W~[W~[W~ZW~ZW~[W~[W~ZW~ZV~ZV~ZV~ZW~ZW~ZV~ZV~ZW~ZW~ZV~ZV~ZV~ZV~ZV~ZW~YV~ZV~ZV~ZV~ZV~YV~ZV~ZW~ZW~ZW~ZW~ZW~ZV~ZV~ZV~ZV~ZV~ZW~ZW~ZW~ZW~[X~[W~[W~[W~[W~[X~[W~[X~\X~\Y~\X~[X~\X~\X~\Y~]Y~]Y~]Y~]Y~]Y~]Y~]Y~]Y~^Z~^Z~^Z~^[~^[~^Z~_[~_[~_[~_[~_[~`\~`\~_[~`\~`\~`\~`\~`\~a]~a]~a]~`\~`\~a]~a]~a]~b]~b]~b^~b]~c^~b]~c^~c_~c^~b^~c_~c_~c_~c_~c_~c_~c_~c_~ea~e`~d`~ea~ea~ea~ea~ea~ea~ea~ea~fa~ea~fa~fa~fa~fa~fa~fa~fa~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~hc~hc~gc~hc~ie~id~ie~ie~ie~ie~ie~ie~je~je~je~je~je~je~je~je~je~kg~je~kg~kg~kg~kg~kg~kg~kg~kg~lg~lg~lg~kg~kg~kg~lg~mg~lh~lh~mi~lh~lh~mh~ni~ni~ni~ni~ni~mi~nj~ni~ni~oj~ni~nj~nj~oj~nj~nj~nj~oj~ni~pk~ni~oj~oj~oj~oj~ni~pk~oj~oj~oj~pk~pk~oj~oj~ni~7575pk~858575pk~oj~oj~pk~oj~ni~oj~75ni~ni~ni~ni~oj~ni~oj~oj~oj~ni~ni~ni~ni~ni~ni~ni~ni~ni~ni~ni~ni~ni~ni~ni~mi~mi~mi~mg~lg~ni~mi~lg~lh~lh~mi~mi~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~ie~je~je~je~je~je~je~je~ie~hd~ie~ie~hd~hd~ie~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~fa~gc~fa~fa~fa~fa~fa~fa~ea~ea~ea~ea~ea~ea~d`~ea~d`~d`~c_~c_~d_~c_~c_~d_~c_~d_~d_~d`~c_~c_~b^~c_~c_~b^~b^~b^~b]~b^~b^~b]~b^~b^~b]~a]~a]~`\~`\~`\~`\~a\~`[~`\~`\~`\~`\~`\~`\~`\~`\~_[~_[~^Z~^Z~^Z~^Z~^Z~]Z~^Z~^Z~]Y~]Y~\Y~\Y~]Y~]Y~]Y~\X~\X~\X~\X~[X~\X~[W~[W~[W~[W~ZW~[X~ZW~[W~ZW~ZV~[W~ZV~YV~ZW~YV~YV~YV~YV~ZW~YV~YV~YV~YV~YV~YV~YV~YU~YU~YV~YV~YU~YV~YU~YU~YU~YU~YV~YU~YV~YU~XU~XU~XU~XU~YU~YU~YU~YU~YU~YU~YU~YU~YU~YV~YV~YU~YV~ZV~YV~YU~YU~ZV~ZV~ZV~ZV~ZV~[W~ZV~ZW~ZV~ZV~[W~ZV~[W~[W~[W~[W~[X~[W~[X~[X~[X~[X~[X~\X~\Y~\Y~\Y~\Y~\Y~\Y~]Y~]Y~]Y~]Z~]Z~]Z~^Z~^Z~^Z~^Z~^[~^Z~_\~^[~_[~`\~`\~_[~`\~`\~`\~`]~`]~`]~a^~a]~b^~b^~b_~b_~b_~c`~d`~d`~d`~da~ea~ea~ea~fb~fb~fb~fc~gc~gc~hd~hd~hd~if~jf~jf~jf~kg~kg~lg~lg~li~kh~mj~mj~mj~nj~nl~nl~pl~ok~pl~pl~pl~qm~rn~ro~ro~ro~ro~sp~tq~tq~ur~ur~ur~vr~wt~xt~<:<:=;=;=;=;=;><><><?=?=?>?>@>?>@>@?@?@?B@B@B@B@CACADBDBDBECECFDFDFDGEGEGFGFHFHGHGHGIHJHKIKJKJLJLJLJMKMKNLOMNLOMOMPNPNQOQOQPRPRQSQSQTRTRTRTSTSUTVTVTWUWUWUXVXVXVYWYWYWZXZX[Y[Y[Z[Y[Z\Z\[][][]\^]^\^\_]_]`^`^`^`^_^`_`_a_a`a`babacbcbdbdcececededfefdfdfdfegfgfgfhfhgihihjijhjhkikijijikikjkjkjkjkjkjkjlklklklkmklkmlmlnlmlnmomnmnmomnmnmonnmonomonononomompnpoononpnonononnmnmnmnmnmomnmononnmnmmlmlnlnlnmmlnmnmnlnmmlmklkljljljjijhjiihihjhihihihighggfgfgffefefeececdccbdccbcbbabaa`a`a`a_`_a_`^`^_^_]^]^\^\^\]\\[\Z\Z[Y[Y[YZYZXZXZXYWYWYWXVXVWUWUVTUSUSUSUSTRSQSQRPRPRPQOQOPNPNOMOMOMNMNLNLMKLKLKLJLJKIKIKIJHJHIHIHIGIGHFHFHFGEGEGEFDFDFDECECECECDBCBCBCABABABAB@B@A@A@A?@>@>@>@>@>?=?=?=><><><=<=<=;=;=;<:;:<:<9vr~vr~vr~vr~tp~tp~tp~tp~rn~rn~qn~qn~qm~qm~pm~pk~ok~ok~nk~nj~nj~nj~mi~lh~lh~lh~lh~kg~jf~if~if~jg~ie~he~if~he~hd~hd~hd~hd~gc~gc~fb~fb~eb~ea~ea~ea~ea~d`~d`~c`~d`~d`~c_~c`~c_~b_~b_~b_~b^~b^~a^~a^~b^~b^~a]~a]~`\~`\~`]~`]~_\~`\~_\~_[~_\~_\~^[~^[~^[~^Z~^[~^[~^Z~]Z~^Z~]Y~^Z~]Y~]Y~]Y~]Y~]Y~]Y~]Y~\Y~\Y~\Y~\Y~\Y~\Y~\Y~\X~\Y~[X~[X~[X~[X~\Y~[X~[X~[X~[X~[X~[W~[W~[W~[W~[W~[X~[X~[W~[W~[W~[W~[W~[X~ZW~[W~[X~[W~ZW~[W~[X~[W~[W~[W~[W~[W~[W~[W~[W~[W~[W~[W~[W~[W~[W~[W~[W~[X~\X~\X~\X~\X~\X~]Y~]Y~]Y~]Y~]Y~]Y~]Y~]Y~]Y~^Z~^Z~^Z~]Z~^Z~^[~^Z~_[~_[~_[~_[~_[~_\~_[~_[~_[~a]~a]~a]~a]~a]~a]~a]~a]~a]~a]~a]~b]~a]~b]~b^~c^~b^~b^~c^~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~d`~d`~d`~d`~e`~d_~d`~ea~ea~ea~ea~fa~fc~fa~fa~fa~gc~gc~fa~gc~gc~gc~gc~gc~gc~gc~gc~gc~hc~hc~gc~gc~hc~hd~gc~id~id~id~id~ie~ie~je~je~je~je~je~je~ie~kg~kg~kg~je~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~lg~lg~lg~lh~lh~mi~mi~mi~mi~ni~mi~ni~mi~ni~lh~ni~ni~ni~oj~ni~oj~oj~oj~oj~pk~oj~oj~7575858575oj~75868685868585858686pk~858585858685868585858585858585858585758685858585858585858585858585858585858575oj~oj~pk~oj~pk~oj~oj~oj~oj~ni~ni~ni~ni~ni~ni~ni~ni~ni~ni~ni~ni~mi~ni~mi~mi~mi~mi~mi~mi~mi~lh~mi~mh~mh~lg~kg~kg~kg~kg~kg~kg~kg~je~kg~kg~kg~je~if~if~jf~ie~jf~hd~hd~hd~id~ie~hc~ie~ie~gc~ie~gc~gc~hc~gc~gc~gc~gc~gc~gc~gc~fa~fa~fa~fa~fa~ea~ea~ea~ea~ea~ea~ea~ea~d`~e`~e`~d`~e`~d`~d`~d`~d`~d`~d`~d`~d`~c_~c^~c_~b^~b^~c_~b^~c_~b^~b^~b^~b^~b]~a]~a]~a]~a]~a]~a]~a]~a]~`\~`\~a]~`\~`\~`\~`\~_\~_\~_\~^[~^[~^Z~^Z~^Z~^Z~^Z~^Z~]Y~]Y~]Y~]Y~]Y~]Y~\X~]Y~\Y~\X~\X~\X~[W~\X~[X~[W~[W~[X~[X~[W~[W~[W~[W~[W~ZW~ZW~[W~ZW~ZV~ZV~YV~ZW~ZV~YV~ZV~YV~YV~ZV~YV~YV~YV~ZW~YV~YV~YV~YV~ZV~YV~ZV~ZV~ZW~ZW~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZV~ZW~ZW~ZW~ZW~ZV~ZW~ZW~ZW~ZW~ZW~ZW~[W~[W~[W~ZV~[W~[W~[W~[W~[W~[W~[X~\X~\Y~\X~[X~\X~\Y~\Y~\Y~\Y~\Y~]Y~]Y~]Y~]Y~]Z~^Z~^Z~^Z~^[~^[~_[~^Z~^[~_\~_\~_[~^[~_\~_\~_\~_\~`\~`\~`]~`]~a]~`]~a^~b^~b^~b_~b^~b_~b_~c`~c`~d`~c`~d`~d`~ea~ea~ea~fb~fb~fb~gc~gc~gc~hd~hd~ie~ie~if~jg~jg~kg~kh~kh~li~li~li~li~mj~mj~mj~nk~ol~pl~pl~pl~pl~pl~pl~rn~rn~rn~rn~sp~sp~tq~tq~uq~ur~ws~ws~xt~xt~xt~<;<:=;=;=<=;><><><?=?=?=?>?>?>@?@?A?A?A?B@B@B@CACACADBDBDBECECFDFDFEFEGEGFGFHGHGHGIHIHJIJIKILJLJLJMKMKMKNLNLOMOMOMPNPNQORPRPRQSQSQSQSRTRUSUSUTVTVTWUWUXVXVYWYWYWYWZXZXZYZY[Y\Z[Z\[]\]\^\^\^\^]^]^]_^`^`^a_a_b`a`a`a`bababacadbdbdcecedfdfdfdfegegegfhfhghghghgihihjijikjkjkikjlkkjlklklkmlmlmlmlmlmlnlnlnlnlnmnmononononpnpnpopoqopopopopopopoqpqpqprpqpqpqpqprpqprpqpqpqprqqpqpqoqoqppoqoqoqoqpqppopopoonpopopopopopnpnomomonnmnmnlmklklkkjkjkjkjkjkjkjjijiihihighghfgefegefeececdcdcdccbcbbababaa`a`a_`_`_`^_]`^_]_]^\]\^\][\[[Z\[[Y[Y[YZXZXZXYWYWXVXVXVWUVUVTUSUSTRTRSQSQSQRPRPQOQOPNPNPNPNOMOMNLNLNLMKMKLKLKLJKJKIKIJHJHJHIGIGIGHFGEHFGEGEFDFDECECECECDCDCDBDBCACACACABAA@B@A@@?A?A?@>@>@>@>?=?=?=?=><><=;=;=;<;<:<:;:;:wt~vr~vr~vr~tp~uq~tp~tp~sp~so~so~qn~qn~rn~qm~qm~pk~pm~ok~nj~mi~nj~nj~mj~lh~lh~lh~kg~kg~kg~jg~if~ie~ie~he~he~he~he~hd~hd~gc~gc~gc~gc~fc~fc~eb~ea~ea~ea~d`~d`~d`~d`~c_~c_~c_~c_~c_~c_~c_~b^~b_~b^~b^~a]~a^~a]~a]~b^~a]~a]~`\~`\~`\~`\~`\~`\~`\~`\~_\~_[~_[~_[~_[~^Z~^Z~^[~^Z~^Z~^Z~^Z~^Z~]Y~]Y~^Z~]Y~]Y~]Z~]Y~\Y~]Y~]Y~]Y~]Y~]Y~]Y~]Y~\Y~]Y~\X~\X~\X~\X~[W~\X~\X~\X~\X~\X~\X~\X~\W~[W~\X~\X~\X~\X~\X~\X~\X~\X~[X~[X~[X~[X~\Y~\Y~\X~[X~[X~[X~\X~\X~\X~\X~\X~\X~\X~]Y~\X~]Y~]Y~]Y~]Y~^Z~]Z~]Y~]Y~]Y~]Y~^Z~^Z~^Z~^Z~_[~_[~_[~_[~`\~_[~_\~`\~_[~`\~`\~`\~`\~a]~a]~a]~a]~a]~a]~b^~b^~b^~b^~b]~b^~c^~c^~c^~c_~c_~d`~c_~c`~c_~c`~c_~c_~c_~d`~d`~d`~d`~d`~e`~d`~e`~d`~ea~ea~ea~fa~ea~ea~fc~hc~gc~gc~hc~gc~gc~gc~gc~hc~gc~gc~hc~ie~ie~ie~ie~id~id~id~ie~id~ie~je~je~je~je~je~je~je~je~je~jf~kg~kg~kg~kg~je~kg~kg~kg~kg~kg~kg~lh~kg~kg~lh~kg~mi~lh~lh~mi~lg~lg~mi~ni~ni~ni~ni~nj~ni~ni~ni~ni~oj~oj~oj~oj~oj~858585858585858686868686858686868686868686868696868686868686868686869686869686868686969696968686969686868686868686858585858686868585858585858585858586858585pk~85858585oj~oj~oj~oj~oj~oj~ni~ni~nj~nj~mi~ni~nj~mi~mi~mi~ni~mi~mi~mi~mg~mg~mi~lg~mi~kg~lg~lg~kg~kg~kg~kg~kg~kg~kg~jf~kg~je~je~je~je~je~je~je~ie~je~ie~ie~ie~ie~ie~ie~hd~gc~gc~gc~gc~gc~hc~fa~gc~gc~gc~fa~fa~fa~fa~fa~ea~ea~ea~ea~ea~ea~ea~ea~ea~e`~e`~d`~ea~e`~d_~d`~d`~d_~d`~d`~c_~c_~c_~c_~c_~c_~c^~b^~b^~b^~b^~b^~b^~b^~b^~a]~a]~a]~`\~`\~`\~`\~_[~`\~`\~_[~_[~_[~_[~_[~^Z~_[~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~]Y~]Y~\Y~\Y~\X~]Y~\X~\X~\X~\X~[X~[X~[X~[X~[X~[X~[X~\X~[X~[W~[X~[W~[W~[W~[W~[W~ZW~[W~[W~ZW~[W~[W~[W~ZW~ZW~ZW~[X~[W~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~ZW~[W~[X~[X~[X~[X~[W~[X~[W~[W~[X~[X~[W~[X~[X~[X~[X~[X~[X~\X~\Y~\Y~]Y~]Y~]Y~\X~\Y~]Y~\Y~]Y~]Y~^Z~]Y~]Z~^Z~^Z~^Z~^[~^[~^[~_[~_[~^[~^[~_\~`\~`\~_\~_\~`]~`\~`\~`]~`\~a]~a]~a]~b^~a]~b^~b^~b^~b^~c_~c_~c_~c`~da~da~da~ea~ea~eb~eb~fb~fb~fc~fc~gc~hd~he~ie~ie~ie~ie~jf~jf~jg~kh~kh~kh~kh~li~li~mi~mj~nj~nj~nj~ol~pl~pl~pl~qm~qm~rn~rn~rn~ro~ro~sp~sp~tq~uq~ur~vs~ws~ws~xt~yv~xt~=;=;=;=;=;><><><>=>=?=?>?>?>@>@?A?A?A?B@B@B@CACACADBDBDBDBECECEDFDFEGEGEGFHFHGHGIHIHJHJHKIKILJLJLKMKMKNLNLOMOMOMPNPNQOQPQPRQSQSQTRTRTRTRUSVTVTWUWUWUXVXVXVYWZXZXZX[Y[Y[Y\Z[Z\Z\[]\]\]\^\_]_]_]_^`^`_`_a_a`b`b`babacbcbcbcbdcececfdfdfdgegehfgfhghghgihihjijijijikikjlklklkmlmlnlnlnmnmnmnmonpoononpoonpopopopopopoqoqoqpqpqprqrprprqrqrqrqrqrqrqsqsrsqrqsqtrsqsrtrrqsqrqsqsrsrtrsrsrtrsrsrsrsrsrrqsrsqrqrqrqrqqpsrrqrprqsrqppopopopopopoonnmnmnmnmmmmlmkmllklkkjkjkjkjjiihihihhghggfgffefdededdcdccbcbcbcacabaa`a`a_a_`_`^`^_^_^_]^]]\][][][\Z[Z[Y[YZXZXZXYWYWXVWVWUWUVTUTUSTRTRSQSQSQQORPQOQOQOPNPNOMOMNLNLNLNLNLMKMKLJLJKJKJKJJIJHIHIHIHIGHGHFHFHFGEGEFDFDFDFDECECECDBDBDBCACACABABAA@A@A@A?A?@>@>@>?>?=?=?=?=><><=;=;=;<;<:<:<:<:vr~vr~vr~vr~vr~uq~tp~tp~tp~so~rn~rn~rn~qm~qm~qm~pm~pm~ol~ol~nj~mj~mj~mj~lh~lh~lh~kg~kg~lh~kg~kg~if~if~if~if~he~he~he~hd~hd~gd~gc~gc~gc~fc~eb~eb~eb~ea~ea~d`~d`~ea~ea~d`~d`~c_~d`~c_~c_~b_~b_~b_~b^~b^~a^~b^~b^~a^~b^~a]~a]~a]~a]~`\~a]~a]~`\~`\~`\~`\~`\~_\~_\~_\~_\~^[~_[~_[~^[~^[~^[~^[~^Z~^Z~]Z~^Z~^Z~^Z~^Z~^Z~]Z~]Z~^Z~^Z~]Z~]Z~]Y~^Z~]Y~]Y~]Z~]Y~]Y~]Y~]Y~]Y~]Y~]Y~\X~]Y~]Y~\Y~\Y~\Y~\Y~\Y~]Y~\Y~\Y~]Y~]Z~]Y~\Y~\Y~\Y~]Y~]Y~\Y~]Y~]Y~]Y~]Y~]Y~]Y~]Y~^Z~]Y~]Y~^Z~^Z~]Y~^Z~^[~^[~^[~_[~^[~_[~_[~_[~_[~_[~`\~_[~`\~`\~`\~a]~`\~`\~a]~b^~a]~a]~a]~b^~b^~b^~b^~b^~b^~c^~c^~c_~c_~d`~c_~c_~c_~d`~d`~d`~d`~d`~d`~e`~e`~d`~e`~e`~e`~ea~ea~ea~fa~ea~ea~ea~fa~fa~ea~fc~gc~gc~gc~gc~hc~ie~hc~ie~ie~ie~ie~ie~ie~ie~ie~ie~ie~ie~je~je~je~je~je~kg~kg~kg~kg~kg~kg~kg~kg~lg~kg~kg~kg~lg~lg~kg~kg~lg~kg~mi~mi~kg~mg~lh~lh~lh~ni~mi~ni~ni~ni~ni~ni~ni~ni~nj~oj~oj~oj~oj~oj~85oj~75758585858675858586868686868686868686868686978686969686979797979797969796979796979797979797979797979797969796979797979797969797969696969696969696968686869696868686868586868585868675758585858585pk~pk~pk~oj~oj~oj~oj~nj~oj~ni~nj~nj~ni~mi~ni~mi~ni~mi~mi~mi~mi~mi~mi~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~je~je~je~kg~je~je~je~je~ie~ie~ie~ie~ie~ie~hc~hc~hc~hc~hc~hc~gc~gc~gc~gc~fa~fc~fa~fc~fa~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~d`~e`~d`~d`~d`~d`~c_~d_~d_~c_~c^~c^~c_~c_~b^~b^~b^~b^~b^~a]~b^~a]~a]~a]~`\~a]~`\~`\~`\~`\~`\~`\~_\~_\~_[~_[~^[~^[~^[~^Z~_[~^[~^[~^Z~^Z~]Z~^Z~]Z~]Z~]Z~\Y~]Y~]Y~]Y~\X~\Y~]Y~\X~\Y~\Y~\X~\X~\X~\X~\X~\Y~\X~\Y~\X~\X~[X~[X~[X~[X~[X~[X~[W~[X~[X~[X~\X~[W~\X~[X~[X~\X~\X~[W~[W~[X~[X~[X~\X~\X~[X~\Y~[X~\Y~\X~\X~\X~\X~\X~\X~\X~\Y~\Y~\Y~\X~\Y~\Y~\Y~]Y~]Y~]Y~\Y~]Z~]Z~^Z~^Z~^[~^[~^[~^[~^[~^[~^[~^[~^[~_[~_\~_\~_\~_\~_\~_\~_\~_\~`\~`\~`\~`]~`]~a]~a]~a]~a]~a^~a^~b^~b_~b^~c_~c_~c_~c_~d`~ea~ea~da~da~ea~ea~ea~fb~fc~fc~gd~gd~gc~he~hd~he~ie~ie~if~jf~jf~jf~jg~kh~kh~kh~li~mi~mj~mj~ok~ok~pl~pl~pl~pl~qm~rn~rn~ro~ro~ro~tq~tq~tq~tq~ur~ur~vr~wt~ws~ws~<:yu~=;=;=;=<=<=<><><?=?=?=?>?>@>@?@?A?A?A?B@B@B@B@CACADBDBDBDBDCECEDFDFDFEGEGFGFGFHGIGIGJHJHJIKIKIKJLJLJMKMKNLNLNLOMPNPNPOQORPRPSQSQSQSQTRTRUSUSVTVTWUWUXVXVYWYWZXZX[Y[Y\Z\Z[Z\[][\[]\^]^\^]_^`^`^`^`_`_a`b`b`bababacbcbcbdcdcedededfefefegehfhfhgihihihjijikjkjkjlklkljmlmlmlnmmlnmnmononononpopoqpqprprpqprqrqrqrqrqrqsqrprqsqsrsqtrtstststststrtstsustsutustsususutusvtututututusututusvtutvtututututvtususustsusustsuststsuststssrrqrqrrrrrqrqqppopopopopoononononnmnmnlmllklkljkjjijiihihighfgfgfgefefdededecdcecdbcbcbbabaa`a`a_a_`^`^`^`^_]^\][][][][\Z\Z[Z[YZYZXYXXWXVXVWUWUVTVTUSUSUSSQSQRPRPRPQOQOQOPNPNOMOMOMNLNLNLNLMKMKMKMKMKLJKJJIJIJHIHIHIGHGIGHFHFGEGEGEFDFDFDECECECDBDBDBDBDBBACABABAA@A@A?A?A?@?@>@>@>?=?=><?=><><=<=;=;=;<:<:<:<:wt~vr~vr~vr~vr~tp~tp~tp~tp~so~so~rn~qn~qn~qm~pl~ol~ol~nk~nk~nk~nj~lh~lh~lh~lh~lh~kg~kg~jf~jf~jf~if~if~if~he~he~ie~hd~hd~gc~gd~gd~gc~fc~fc~fb~fb~fb~fb~fb~eb~ea~ea~d`~d`~d`~d`~c_~c`~c`~c_~c_~c_~b^~c_~b^~b^~b^~b^~b^~b^~a]~b^~a]~a]~a]~a]~a]~a]~`\~a]~`\~`\~`\~`\~_\~`\~_\~_[~_[~_[~_[~_[~^[~^[~_[~_[~_[~_[~^Z~^[~^[~_[~^[~^[~_[~^Z~^Z~]Z~^Z~]Z~]Z~^Z~^Z~^Z~^Z~]Z~^Z~]Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^Z~]Y~^Z~^Z~^Z~^Z~]Y~^Z~^Z~^Z~^Z~^Z~^[~_[~_[~_[~_[~_[~_[~_[~_[~`\~_[~`\~`\~`\~`\~`\~a]~a]~a]~a]~b^~b^~b^~b^~b^~b]~b^~c^~c_~c_~c_~c_~c_~c_~c_~d`~d`~d`~d`~ea~d`~d`~e`~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~fb~fc~fa~fc~fc~fc~fc~fc~gc~hc~ie~ie~ie~ie~ie~ie~ie~ie~ie~je~je~je~je~je~je~jf~kg~lg~je~kg~lg~kg~kg~kg~kg~kg~kg~kg~lg~mi~mi~lg~mg~mg~mi~mi~mi~mi~mi~mi~mi~ni~ni~ni~ni~nj~ni~ni~ni~ni~ni~ni~75oj~oj~oj~oj~8585858586858686868686868686868696868697969797979797969797:7:7:7:7:79797:7:7:7:7:7:8:89797:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8:797:7:7:8:8:7:7:7:7:7:7:7:7:79797979797979796969697969696869686868686868686868686868686858686868585pl~pk~pk~85oj~oj~oj~nj~nj~ni~ni~ni~nj~ni~mi~mi~mi~mi~mi~mi~mi~mi~lg~mi~kg~lh~lh~kg~kg~lg~lg~lg~kg~kg~kg~kg~kg~jf~jf~jf~je~ie~ie~ie~ie~ie~ie~ie~ie~ie~ie~hc~gc~hc~hc~hc~hc~hc~hc~hc~gc~fc~fc~fc~fa~fb~fb~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~d`~ea~ea~ea~d`~d`~d`~d`~d_~c_~c_~d_~b^~c_~c_~b^~b^~b^~b^~b^~b^~b^~a]~a]~a]~a]~a]~a]~a]~a]~`\~`\~`\~`\~_[~`\~_[~_[~_\~^[~_[~_[~_[~^Z~^Z~^Z~^Z~_[~^Z~^Z~]Z~]Z~]Y~]Z~]Y~]Y~]Y~]Y~]Y~]Y~]Y~]Y~]Y~]Y~\Y~\Y~\Y~\Y~\Y~]Y~]Z~]Z~\Y~\Y~\X~\Y~[X~\Y~]Y~\X~\Y~\Y~\Y~\Y~\Y~\Y~\Y~\Y~\X~\Y~]Y~]Y~]Y~]Y~\Y~]Z~]Z~]Z~]Y~]Z~]Z~]Y~]Y~^Z~^Z~^Z~^Z~^Z~^Z~^Z~^[~^Z~^[~^[~_[~^Z~^Z~^[~^[~_[~_[~_\~_\~_\~_\~`\~`\~`\~`\~`\~`\~a]~`\~a]~a]~a]~a^~a^~b^~a^~b^~b^~b^~c_~c_~c`~c_~c`~d`~d`~d`~ea~ea~eb~fb~eb~fb~fb~gc~fc~gc~hd~hd~hd~hd~hd~he~ie~if~jg~jf~kg~kg~kh~kh~li~li~li~mj~mj~mj~nj~ol~pl~pl~qn~qn~qn~rn~rn~ro~sp~sp~sp~sp~sp~ur~ur~ur~ws~ws~wt~xt~<:<:<:=;=;=;><><><>=>=?=?>?>?>@?@?@?A?B@B@B@B@B@CACADBDBDBDCDBDCECECEDFEFEFEGFGFHFHGIGIHJHJHKIKIKILJLJLJMKMLNLNLOMOMPNPOQOQORPRPRPSQSQSQTRUSUSUSVTVTWUXVXVXVYWYWZXZY[Y[Y\Z\Z][]\]\^\^\^]^]_^`^`_`_a_a`a`babacbcbcbdcdcdcedededfefefegfhghghgigjhjhjijikikjkjljlkmllkmlmlnmomnmononononpopopoqorprprpqprqsqsrsrsrtsustststsuststsutututututwuvuututvtvuvuvuvuwuwvwuwvwvwuwvwuxwwvwvwvwvwvwvxvxvwvxvxvxwxwwvwvwvvuwuvuwvwuvuwuwuwvwuwvwuututttutttuttstssrsrrqssrrrqqpqpqppopopoononomnmnlmllklkljkjkijhihihhghghfgegefefdedededdcdccbcbcacababaa_a_a_`^`^`^_]^\^\^\][][[Z[ZZYZYZXYXYWYWXVXVWUWUVTVTUSTRTRTRRPSQRPQOQOQOPNPNOMOMOMOMNLNLMLMKMKMKMKLJLJKJKIKIKIJIIHIGIGIGHGHFGEGEGEGEFDFDECECECECDBDBDBCACABABABAB@A@A@A@@?@?@?@>?>?=?=?=><><><><=;=;<;<;<;<:;:;:vr~vr~vr~vr~uq~tp~tp~tp~tp~so~rn~rn~rn~rn~qn~ol~ol~ol~nk~nk~nj~mj~mj~li~lh~lh~kg~lh~lh~kg~kg~if~if~if~if~if~he~he~hd~hd~hd~hd~gc~gc~gc~gc~gc~fb~fb~ea~ea~ea~ea~ea~ea~ea~da~da~d`~d`~d`~d`~d`~d`~c_~c_~c_~c_~c_~b_~b^~b^~b_~b^~b^~a^~a]~a^~a^~a]~a]~a]~a]~a]~`]~`\~`\~`\~`\~`\~`\~`\~`\~`\~_[~_[~`\~`\~`\~`\~_\~_\~`\~_[~_[~_[~_[~_[~_[~_[~_[~_[~^Z~^[~_[~_[~_[~_[~_[~^Z~_[~_[~_[~_[~^[~_[~_[~_Z~_[~_[~_[~_[~^[~^[~^[~^[~_[~^[~^Z~_[~^[~_[~_[~_[~_[~_[~_[~_[~_[~_[~`\~`\~`\~`]~a]~a]~a]~a]~a]~b^~b^~b^~b^~a]~b^~b^~c_~c^~c_~c_~c_~d`~d`~c_~d`~d`~d`~d`~d`~d`~d`~ea~ea~ea~d`~ea~fa~fa~fa~ea~fc~fb~fb~fb~fc~fc~fc~fc~fc~gc~gc~fc~fc~hc~hc~hc~hc~ie~ie~ie~ie~je~ie~jf~kg~jf~jf~kg~kg~kg~lg~lg~lg~lg~lg~lg~lg~lg~mi~mi~mi~mi~mi~mi~mi~mi~mi~mi~nj~ni~ni~nj~nj~nj~nj~nj~nj~nj~pk~oj~75oj~oj~oj~oj~7575867575858575758686868686868696868696869796969697:7979797:797:897:7:7:8:7:7:8:8:8:8:8;8;8;8;8;8:7:8;8:8;8;8:8:8;8;8;8;8;8;8;8;8;8;8;8;8:8;8;8;8;8;8;8;8;8;8:8:8:8;8;8;8:7:7:8:7:8:7:7:8:7:7:7:7:897:7:7:7:79697979697979686869796968686868686868686869685868685pk~8585pk~pk~pk~pk~oj~oj~nj~nj~ni~mi~mi~nj~mi~mi~mi~mi~mi~mi~mi~mi~mi~mi~mi~mi~lh~mi~lg~lh~kg~kg~kg~jf~jf~jf~je~jf~jf~je~ie~jf~ie~ie~ie~ie~ie~hc~ie~hc~ie~hc~hc~hc~hc~hc~hc~fc~fc~fc~gc~gc~gc~ea~fb~ea~fa~fb~fb~fa~ea~ea~ea~ea~fa~fa~ea~ea~d`~d`~d`~d_~d_~d_~d_~c_~c^~c_~c_~c_~b^~b^~b^~b^~b^~b^~b^~a]~a]~b]~a]~a]~`\~`\~`\~`\~`\~`\~_\~_\~_\~_\~_[~_[~_[~_[~_[~_[~^[~^Z~]Z~^[~^[~^[~^Z~^Z~^Z~^Z~^Z~]Z~^Z~^[~^Z~^Z~^Z~^Z~]Z~]Z~^Z~^Z~^Z~]Z~^Z~]Y~]Z~]Z~]Z~]Z~]Z~]Z~]Z~]Z~]Z~]Z~]Y~]Z~]Z~^Z~^Z~]Z~]Z~]Z~]Z~]Z~^Z~]Z~^[~^Z~^Z~^[~^[~^[~^[~^[~^[~_[~_[~_[~^[~_[~_[~_[~_[~_[~_[~_[~_[~_\~_\~`\~`\~`\~`\~`]~`]~`]~a]~a]~a]~a^~a^~b^~b^~b^~b^~b_~b^~c_~c_~c_~c_~c_~d`~d`~c`~da~da~ea~ea~ea~ea~fb~fb~fc~fc~gc~gc~hd~hd~he~if~if~if~if~if~jf~jg~jg~kh~kh~li~li~li~mj~mj~mj~ok~ok~ok~pl~pl~pl~pl~qn~qn~ro~rn~sp~sp~tq~sp~tq~ur~ur~ur~ur~wt~xt~xt~xt~<;<;<;=;=;><><><><>=?>?>?>?>@?@?A?A?A?B@A@B@CACACACADBDBDCDCECDCECEDFDFEFEGEGFGFHFIGIGJHJHKIKIKIKIKILJLJMKNLNLOMOMOMPNPNQOQORPSQSQSQSRTRTRUSVTVTUSVTWUXVXWYWYWZX[Y[Y[Z\Z][^\][]\^]^]_^`^`^_^`_a`a`b`bababacbcbdcedededfeedfegfgfhggfhgigihihjijhjhjikjlklkmlmlmlnmnmnmnmononpoqoqoqpqpqorprqsqsqsrsqtrtrtstsusvtutvtvuwuvuwuwvvuvuwvvuwvxvwvwvxvxwxvxwxwxwyxxwywywzxzy{zzxzxzyyxyx{y{z{yzyyxyxyxyxyxyxzyzxzyyxxxxwxwxwxwywyxyxxwyxywxwyxxwxwvvwvwvxvwuwvwvutvtuttststssrtrsrrqrqqprqqpqppoonnmnmmlmlmllkkjkjjijijiihihhggfhfgfgegeedededdcecdccbcbcbcab`a`a``_`_`^_]_]^\^\]\][\[[Z[Y[Y[YZXYWYWXVXVWUWUVTUTUSUSTRTRSQRPRPRPRPQOQOQOPNPNOMOMOMNLNLNLMKMKMKLJLJLJLJKIKIJHJHIGIGIGHGHFHFGFGEGEFDFDFDECECDBDBDBDBDBDBCACABABAA@B@A?@?@?@?@>?=?=>=?=><><><><=;=;=;=;<;<:<:<:<9vr~vr~vr~vr~tp~tp~tp~tp~rn~so~rn~rn~rn~qm~pm~ol~ol~ok~mj~mj~mj~mj~mj~lh~lh~lh~lh~lh~kh~jh~jf~jf~jf~if~if~ie~ie~ie~hd~he~he~hd~gd~gd~fc~gc~gc~fb~fb~fb~fb~fb~fb~ea~ea~ea~ea~ea~d`~d`~d`~d`~d`~d`~d`~d`~d`~d`~c_~c_~c_~c_~b_~b^~b^~b^~b^~b^~b^~b^~a^~a^~a]~a]~a]~`]~a]~a]~a]~a]~`\~`\~`\~`\~`\~`\~`\~`\~`\~`\~`\~`\~_\~`\~`]~`\~`\~_\~_\~_\~_[~`\~`\~`\~`\~`]~_[~`\~`\~a]~`\~`\~_[~`\~`\~`\~_[~_[~`\~_[~`\~_[~`\~_[~`\~`\~`\~`\~`\~`\~`\~`\~`\~a]~a]~a]~a]~a]~a]~a^~a]~a^~b_~c_~b^~c^~c_~c_~c_~c_~c_~c_~c_~d`~c_~d`~d`~d`~d`~d`~ea~d`~ea~ea~ea~ea~ea~eb~ea~eb~fb~ea~ea~gc~gc~fa~gc~fc~fc~fc~gc~gc~hc~hc~gc~gc~hc~he~ie~ie~ie~ie~ie~ie~ie~jf~jf~kg~kg~kg~kg~lg~lg~lg~lg~mi~lh~mi~mi~mi~mi~mi~mi~ni~ni~ok~nj~mi~oi~ni~oi~ok~nj~nj~ok~pk~pk~pk~oj~oj~ol~pk~pk~pk~85858585858586868686868686868686868697969797979696:7979797:7:7:7:7:7:8:8:8:8:8;8:8;8;8:8:8;8;8;8;8;8;9;9;8;9;8;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;8;9;9;9;9;9;9;9;9;9;9;9;9;8;9;8;9;8;9;9;9;8;8;8;8;8;8;8;8;8;8:7:7;8;8;8;8;8:7:7:7:7:8:7:7:7:7:7:7:7:797979797978686969696969686868696868585pk~868585oj~pl~pk~pk~pk~ok~ok~nj~nj~nj~nj~nj~nj~nj~ni~mi~mi~ni~mi~mi~mi~mi~mi~mi~mi~mi~lg~lg~lg~kg~kg~kg~kg~jf~jf~je~je~ie~ie~jf~ie~ie~ie~ie~ie~ie~ie~ie~hc~hc~hc~hc~hc~hc~gc~hc~hc~gc~gc~gc~ea~fb~ea~fb~gc~fb~fa~fb~ea~e`~e`~ea~ea~ea~ea~ea~ea~e`~ea~d`~d`~d_~d`~c_~c_~c_~c_~b^~c_~b^~b^~b^~b^~b^~a]~a]~a]~a]~a]~a]~a]~a]~a]~`]~`]~`\~`\~`\~`\~_[~_[~_[~_[~_[~_[~_[~_[~_\~_[~^[~^[~_[~_[~_[~_[~^[~_[~_[~^[~^[~^[~^Z~^Z~^Z~^[~^[~^[~^Z~^Z~^Z~_[~_[~_[~^Z~^[~^Z~^Z~^Z~_[~_[~_[~_[~^Z~_[~_[~^Z~^Z~^Z~^[~_[~_[~_[~_[~_[~_[~_[~_[~_[~`\~_\~_[~`\~`\~`]~`\~`]~`\~`]~`]~`]~`\~a]~a]~a^~a^~a^~b^~a^~b^~b^~b_~b^~b^~b^~b^~c_~c_~c_~c`~c_~d`~c`~d`~ea~ea~ea~ea~ea~ea~eb~fb~fb~fb~fb~gc~gd~hd~he~he~he~he~if~if~jf~jf~jg~jg~jg~kh~kh~kh~kh~mi~mj~mj~nj~nj~ol~pl~pl~pl~pl~rn~rn~rn~rn~ro~ro~tq~sp~tq~ur~ur~ur~wt~wt~wt~xt~xt~<:xu~=;=;=<=<=<><>=>=>=?>?>?>@>@>@?A?A?B@B@B@B@CACACACBDBDBDCDCECEDFDFDFDFDGEGEHFHFHFHFIGIGJHJHKIKILJLJLJMKMKMLNLNLOMPNPNPOQOQORPRPSQSQTRTRUSVTVTVTWUWUWUXVYWYWZXZX[Y[Y\Z\Z][^\^\_]_]_]`^`_a_a`a`a`bababacbcbdcdcededfegffegfhghfhghgihihjhjhjikjkjkjljmkmlmlnmnmnmononpopoqoqorpqprqsqsqsrsqsrsrtsutututvtvuvuwvvuwvxvxwywywxwxwxwxwxwyxxwyxzxywzxzxzxzxzy{yzyzyzyzyzy{z|z|{|{|{|z|{|{|{~|}|}||{|z|{{z{z{z|z}{|{|z{z{zzyzy{zzz{z{z{z{y{z{zzy{zzy{zyxyxyxzyyyzyyxwvwvwvvuvvvuvtvtutustssqtssrrqrqqppopoononnmnmmllklklklkkjjikiihihihhghghfgffefefeededececdccbcbbaa`a`a_`^`__^_^^]^\]\][\Z\Z[Y[YZXZXZXYWXVWVWUVTVTVTUSUSTRTRTRSQSQSQRPQOQOQOQOPNPNOMOMOMNLNLMKMKMKMKLJKJLJKIKIKIJHJHJHIGIGHFHGGFGFFEFDFDECECECECDBDBDBDBDBCACABAB@B@A@A?@?@?@?@>@>?=?=?=><?=><><=;=;=;=;<;<:<:<:<:;9;9vr~uq~uq~uq~tp~tp~rn~sp~rn~rn~rn~rn~pl~pl~pl~ol~ol~ok~nk~nj~li~li~mj~lh~lh~lh~lh~jf~jf~jf~jf~jf~jf~jf~if~ie~ie~hd~hd~hd~gd~he~hd~hd~gc~gc~gc~fb~gc~gc~fb~fb~ea~fb~fb~ea~ea~ea~ea~d`~ea~d`~d`~d`~d`~d`~c`~c_~c_~c_~d`~c`~c_~c_~c_~c_~b_~b^~b_~b^~b^~b^~a^~a^~b^~a]~a^~a^~a^~b^~b^~a]~b^~a]~a]~a]~a]~a^~`]~`]~a]~a]~a]~a]~a]~a]~a]~a]~a]~`\~`\~a]~`\~`\~a]~a]~a]~`\~`\~a]~a]~a]~`\~`\~`\~a]~`\~`\~`\~`\~`\~`\~a]~a]~a]~a]~a]~a]~a^~a]~a]~a]~b^~a]~b^~b^~b^~c_~b^~c_~c_~c_~c_~c_~d`~c_~d`~d`~d`~d`~e`~e`~ea~ea~ea~fb~fa~ea~ea~ea~fb~fb~fb~fb~fc~fb~fc~fc~gc~hc~gc~fc~fc~he~hc~hc~hc~ie~ie~ie~ie~ie~ie~ie~ie~ie~ie~kg~jf~jf~jf~lh~lh~lh~mi~mi~mi~mi~ni~mi~mi~mi~mi~nj~mi~ok~nj~nj~ok~ok~ok~nj~ok~ok~pk~pk~ok~pl~pl~pl~858586868686868686858686868686868686868686969796:797979797:79797:7:7:7:7:7:7:8:8:8:8:8:8;9;9:8:8;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9<:;9;9;9;9;9;9;9<9;9<:<:<9<9<9;9;9;9<:;9;9;9;9<9;9<:;9;9;9;9<9<9<9<9<9<9<9<9<9<9<9;9;9;9;9;9;9;9;8;9;8;8;8;8;8;8;8;8;8;8:7:8:7:8:7:7:8:8:7:8:7:7979797979797979786968686979686868686868685858585pk~pk~pl~86pk~pk~pk~oj~pk~pk~ok~ok~ok~nj~nj~mi~ni~ni~ni~mi~mi~mi~mi~mi~mi~mi~lh~kh~kh~kg~kg~kg~kg~jf~jf~kg~kg~kf~ie~jf~jf~ie~ie~ie~ie~ie~ie~ie~hc~hc~hc~hc~hc~gc~gc~gc~gc~gc~gc~ea~fb~fb~fb~fb~fb~fa~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~d`~d`~d`~d`~d_~c_~d_~c_~c_~c_~b^~c_~c_~b^~b^~b^~b^~b^~a]~a]~a]~a]~a]~`]~a]~a]~a]~`\~`\~`\~`\~`\~_\~_\~_[~`\~`\~_[~_\~`\~`\~_[~_[~_[~_\~`\~_[~_\~_[~_\~_[~_[~`\~_\~_\~_\~_[~_\~`\~_\~_\~_[~_[~_\~_\~`\~_\~`\~_\~_\~`\~_\~_\~_[~_[~`\~`\~`\~`\~`\~`\~`\~`\~`]~`]~`]~`]~`]~`]~`]~a]~a]~a^~b^~a]~a^~b^~a^~a^~b_~b^~b^~b^~b^~b^~b^~b^~c_~c_~c_~c_~c_~c_~d`~d`~d`~d`~ea~ea~ea~ea~eb~fb~fc~fb~gc~fb~gc~gc~hd~hd~he~ie~he~if~ie~if~jf~jf~jf~jg~kh~kh~jg~kh~li~li~mi~mi~nj~nj~nj~ok~pl~pl~pl~pl~qn~rn~rn~sp~sp~sp~sp~tq~tp~ur~vr~vr~wt~wt~xt~xt~<;<;<;=<=;=<=<><>=>=>=>=?>?>?>@>@?@?A?A?B@B@B@B@CACACBCBDBDCDCDCEDEDEDFEGEGEGEGEHFHFIGIGIGJHJHKIKIKILJLKLKMKMLNLNMOMOMOMPNPOQOQORPRPSQSQTRTRUSUSVTVTWUWUXVXVXVYWYWZX[Y[Y[Y\[]\]\]\_^_^_^`_a_a_a_b`cacacbcbdcdcededfefdfegfgfhgihihjhjhjhjikijikjljlklklkmkmknlomononpopoqprqsqrqsrtstrtstsututusutvtvtutwvxwxwxwxwxwxwxwywyxzxzyzyzyzyzy{y{yzx{z{z{y{y{z|z}{|z|z|z}{}{}|}{}{}{~|~|~|}~|~}~}}~~~~}~}~|}{}|}|~}}}}|}|}|}|}|}{{{|{||}|}}}{}{}{~|}||||{|{{z|{|{{z{zzyyxywxwxwwvwvwvvuvuvtvtvtuttstssrrprqqoqoqppoonnmnmmlmllklkljkjkijijiihihihhghggfgfgffegefdfddcdbcadbbab`a_a_`__^_^_^^\^\^\][\Z\Z[YZXZXZXYWXVXVWUWUVTUTUTUSTRTRTRSRSQRQRPRPQPQOQOPNPNPNPNOMOMNLNLNLMKMKLJLJLJKIKIJHJHJHIGIGIGHFHFGFGEGEGEFDFDECECECECECDBDBDBCACACABAA@A@A@@?@?@>@>@>@>?>?=?=><><><><=;=;=;=;<:=;<:<:;:;:vr~vr~vr~ur~tp~tp~sp~sp~rn~rn~rn~rn~pl~pl~pl~pl~ok~ol~nk~nk~nj~nj~nj~li~lh~lh~lh~jf~kg~jf~jf~jf~jf~jf~ie~ie~if~if~if~ie~he~hd~hd~hd~hd~hd~hc~gc~gc~gc~gb~gb~fb~fb~fb~fb~fb~ea~ea~ea~eb~ea~ea~ea~ea~d`~d`~d`~d`~d`~c`~c_~c_~c_~c_~c_~c_~c`~c_~c_~c_~b^~c_~b^~b^~c_~b^~b^~b^~b^~b^~a]~b^~a^~b^~a]~b^~a]~a^~b^~b^~b^~a]~a]~a]~a]~a]~a]~a]~a]~a]~a]~a]~b^~a]~b^~b^~a]~a]~a]~a]~a]~b^~b^~a]~a]~a]~b^~b^~b^~a]~a]~a]~a]~b^~a]~b^~b^~b^~c^~c^~c_~c_~c_~c_~c_~d`~c_~d`~d`~d`~d`~ea~d`~ea~ea~ea~fa~fa~ea~ea~ea~fb~fb~fb~ea~gc~fc~fc~fc~gc~gc~hc~gc~gc~hc~hc~he~he~he~he~ie~ie~ie~ie~ie~jf~jf~jf~kg~jf~jf~kg~kg~jf~kg~kg~lg~lh~mi~mi~mi~mi~ok~ni~ok~ok~ok~ok~ok~ok~ok~ok~ok~ok~pk~pk~85pk~8686pl~868596868696969696969686869797969797979697979797979797:7:7:7:7:7:7:8:8:8:7:8:8:8:8:8;8:8;8:8:8;8:8;9;9;9;9;9;9;9;9<9;9<:<:<:<:<:;9<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:=;=;<:<:<:<:<:<:=;<:<:<:<:<:<:<:<:<9<:<:<:<9<9<9<9<9<9<9<9<9;9;9;9;9;9;9;9;9;9;8;9;8;8;8:8:8:8:8;8;8:8:8;8:8:8:8:7:7:7:8:797979797979797979797978697868686868686868686868686868585pk~pk~ok~ok~ok~ok~ok~ok~ni~ok~ok~ok~mj~nj~ok~mi~mi~mi~mi~mi~lh~lh~lg~lh~kh~kg~lg~kg~kg~kg~kg~kg~jf~je~ie~je~je~ie~ie~je~ie~ie~ie~hc~hc~hc~gc~gc~gc~gc~gc~gb~gb~gc~gc~gc~fb~fb~fb~fb~fa~fb~fb~fb~ea~ea~ea~ea~ea~ea~e`~e`~d`~d_~d_~c_~c_~c_~c_~b^~b^~c_~c_~b^~b^~b^~b^~a]~a]~a]~a]~b^~a]~a]~a]~a]~`\~a]~a]~`\~`]~`]~`]~a]~a]~`]~`\~`]~`]~a]~a]~`]~`\~`\~`\~`\~`\~`\~a]~`\~`\~`\~`\~`\~`]~a]~`\~`\~`]~`]~`]~`]~`\~`\~`\~`]~`]~`]~a]~`]~`]~a]~`]~a^~a]~a^~a^~a^~a^~a^~a^~a^~a]~a^~a^~b^~b^~b^~b^~b^~c_~c_~c_~b_~c_~c_~c_~c_~c_~c_~c_~c`~c`~d`~d`~da~da~da~ea~ea~ea~fb~fb~fb~ea~fb~fb~fc~gc~gc~gc~hd~he~he~he~if~ie~jf~jf~if~jf~jf~kg~jg~jg~kh~li~li~mi~kh~mj~mj~nj~ok~ol~pl~pl~pl~pl~pm~ro~rn~ro~sp~tp~tp~uq~tq~ur~ur~ur~vs~xt~xt~xu~xu~=;=;=;=;=<=<=<=<>=>=>=>=?>@>?>@?A?A?A?B@B@B@BACACACBCBDBDCDCECECEDEDFDGEGEGEGEHFHFHFIGIGJHJHJHKIKILJKILJMKMLMLNLNLOMOMPNPNQOQPQPQPQPSQSQSRUSUSVTVTVTWUXVXVXVYWYXZXZY[Y[Y\Z\Z\[]\^]_^_^`_a_a`b`a`cbcacbdcdcededdcfefefefehfhgihihihjijhkikikjlklklkmlmlmlnmnmnmonpnpoqpqprpsqsrsrtsutvtututututvuvuwvwvxvxvywyxyxzy{y{z{y{z{yzyzy|{}|}{}{}{}{}{}|}{}{}||{~|}|}|~}~}}}}~~~~~~~~~~~~~~~~~~}~}~~~~~}~}}}}}}|}||||z{z{zzyzyyxxwxwxwwvwvwvwuvuututussrsrsqsqqpqoqopoonnmnmmlmlmllkkjlkkjjijijijhihighghfhfgefefeecececdccbcab`a_a`a_`^_^^\^\^\^\][\Z\Z[YZXZXYWYWYWXVXVWUVTVTVTUSTSTRSRSQSQSQSQRPRPRPQOPOPOPNPNOMOMOMNLNLMKMKLJLJKIKIKIKIJHJHJHJHIGIGHFHFHFGEGEFEFDEDECECECECDBDBCACACACAB@B@B@A@@?@?@>@>?>?>?=?=?=?=>=><><><=;=;=;<;<:<:;:;:;9;9vr~vr~ur~ur~tp~tp~rn~rn~rn~rn~rn~rn~pl~pl~ol~ol~ok~ol~nk~nk~mj~mi~li~li~li~lh~lh~kh~kh~jf~jf~jf~jf~jf~jf~jf~ie~ie~ie~ie~ie~ie~ie~ie~ie~hd~hd~hd~hd~gc~gc~gb~gb~gb~gb~fb~fb~fc~gc~fb~fb~ea~ea~ea~ea~ea~fb~da~da~d`~d`~d`~d`~d`~d`~d`~d`~c_~c_~c_~c_~d`~c_~c_~c_~c_~c_~c_~c_~c_~c_~b^~b^~c_~c_~c_~b^~c_~c_~b^~c_~c_~c_~b^~c_~c_~b^~b^~b^~b^~b^~c_~c_~c_~c_~b^~c_~b^~c_~b^~c_~b^~b^~b^~b^~c_~b^~b^~b^~c^~b^~c^~c^~c_~c_~c_~c_~c`~d`~d`~d`~d`~d`~e`~d`~d`~ea~ea~ea~ea~ea~ea~fb~fb~fb~gc~fb~fc~fc~gc~gc~hc~he~gc~he~he~hd~he~he~ie~ie~ie~ie~ie~if~ie~ie~if~ie~ie~jf~jf~jf~jf~jf~kg~kg~kg~lh~jf~lh~lh~lh~lh~mi~mi~nj~nj~nj~ok~ok~ok~ok~ok~ok~pk~pk~pl~pl~pl~pl~pl~8686969696969696969797979697979797979797979797:8:7:7:7:7:7:7:8:7:7:7:8:8:8;8:8:8:8:8:8;8;8:8;9;9;9;9;8;9;9;9;9<:<:<:<:<:<:<:<:<:<:=;<:<:=;=;<:<:<:=;=;=:=:=;=;=;=:=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=:=;=;=;>;>;=;=;=;=;=;=;>;=;=;=;=;=;=:=:=;=:=:=:=:<:=:=:<:<:<:<:<:<:<:<:<:<9<9<9;9<9<9<9<9;9;9;9<9;9;9;8;9;8;9:8;8;8;8;8;8:8:8;8:8:8:7:7:7:7:79797979797979797969797979797979696968696868686pk~pl~85pl~pk~pk~ok~ok~ok~ok~ok~ok~ok~ok~mi~mi~mi~mi~mj~nj~mi~mi~lh~mi~lg~lg~kg~kg~kg~kg~kg~kg~kg~je~je~je~ie~ie~ie~je~ie~ie~ie~ie~he~hc~hc~hc~gc~gc~hc~hc~gc~gc~gc~gc~gc~gc~gc~fc~fb~fb~ea~fa~ea~fb~fb~ea~ea~ea~da~ea~d`~ea~d`~d`~c_~d_~d`~c_~c_~c_~c_~c_~c_~c_~b^~b^~b^~b^~a]~b^~b^~b^~a]~a]~b^~a]~b^~b^~a]~b^~b^~a^~a^~a^~b^~a^~a^~a^~a^~a]~a]~a]~a^~a]~a]~a]~a^~b^~a]~b^~b^~a]~a]~a]~a]~a]~a^~a]~a]~a^~b^~a]~a]~a]~a]~b^~b^~b^~b^~a^~b^~b^~b^~b^~b_~b^~b^~b_~b^~b^~c_~c_~c_~c_~c_~c_~c_~d`~d`~c_~d`~c_~da~da~da~d`~ea~ea~ea~ea~ea~eb~fb~fb~fb~fb~gc~fc~fc~gc~gc~gd~gd~he~gd~he~hd~ie~ie~if~jf~jf~jf~kg~jg~kh~kh~kh~kh~kh~li~mi~mi~nj~nj~nj~nj~nj~pl~pl~pl~pm~qn~qn~ro~ro~sp~sp~sp~uq~uq~ur~vs~ws~wt~xt~xt~xu~=;=;=;=<=<=<>=><>=>=>=?=?=?>?>@>@?A?A?A?B@B@B@CACACACBCBDCDCDCECECECEDFEFDGEGEHFHFHFHFIGIGIGJHKIKIKIKJLJLKMKMKNLNLNMOMOMPNPOQORPQORPSQSQTRTRUSUSVTVTVUWUXVXVXWYWYWZXZYZY[Z[Z\[]\]\_]_]`^a_a`a`b`bacadcdcdcededfefefegfgfhghfhfigihihjikjkjljlkmkmkmlmlmlnmonononpoqoqprpsqsrsrtstsutusvuvuwvwvwvxwxwyxyxzxzxzxzy{z{z|z}{}{}{|{}|}|}|}|}|~}~}~~~~~~~~~}}|}}|{|{{zzyzyyyyxyxyxxwwvwuwvvuvuutsrtssrsqrpqpqopopoonomnmnmnmmlmllklkkjkjkjjijhigighfgfgefefdfdededdccacab`b`a_`_`^`^_]^]^\]\\[][\Z[Y[YZXZXYWYWXVXVWUWUVTUTUSTSTRTRTRSQSQRQRQRPQPQPQOQOPNPNPNOMOMNLNLNLMKLJLJLJLJKIKIKIJHJHIGIGIGIGHFHFGEGEFEFDEDECECECDCDCDBDBCACACAB@B@B@A?A?A?@?@?@>@>?=?>?=?=><><><><><=;=;=;<;<:<:<:;:;:vr~ur~tp~uq~sp~sp~sp~tp~rn~rn~rn~qm~rn~qn~pl~pl~pl~ok~ok~nj~mj~ol~mj~mj~mi~li~li~lh~lh~lh~kh~kg~kg~jf~jf~jf~jf~jf~if~ie~ie~ie~ie~ie~he~hd~hd~hd~hd~hd~hd~he~gd~gd~gd~gc~gc~gc~fb~gb~fb~fb~fc~fc~eb~fb~ea~ea~ea~da~da~ea~ea~ea~ea~ea~ea~d`~d`~d`~d`~d`~d`~d`~d`~d`~d`~d`~d`~d`~c_~c_~c_~d`~d`~d`~d`~d`~c_~d`~c_~c_~c_~d`~d`~d`~d`~d`~d`~d`~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~d`~d`~d`~d`~d`~ea~ea~ea~ea~d`~ea~ea~ea~eb~eb~eb~fb~fb~fb~fb~fb~fc~fc~fc~fc~fc~hc~gc~hc~hc~he~he~ie~ie~ie~ie~ie~ie~ie~ie~ie~jf~jf~kg~jf~jf~jf~jf~jf~kg~kg~kg~kg~lh~kg~lh~lh~lh~mi~mi~mi~mi~mi~mi~ok~mi~ok~pl~ok~ok~pl~pl~pl~pl~pl~868696969697979697979697979797979797:7:7:8:8:7:8:8:8;8;8;8;8;8;8:8:8;8;8;8:8:8:8:8:8:8:8;8;8;9;9;9;9;9;9<9;9<9<:<:<9<:<:<:<:<:=;<:<:=:=:=:=;=;=;=;=;>;>;=;=;=;=;=;=;>;>;><><><><>;>;>;>;><>;>;><>;>;><><>;>;>;>;>;><><><><>;>;><><><><><><>;>;>;>;>;>;>;>;>;>;>;=;>;=;=;=;=;=;=;=:=:=:=:=:=:<:<:=:<:<:<9<:<:;9<:<:<:;9;9<:;9;9;9;9;9;9;8;8;9;8;8:8;9:8:8:8:8:8:8:8:8:8:7:7:7:79797979797979797979797979796969696968686qm~pl~pl~pl~pk~ok~ok~ok~ok~ok~ok~ok~ok~ok~nj~nj~ok~nj~ok~mi~mi~mi~lh~mi~lh~lg~lg~kg~kg~kg~kg~kg~jf~kg~kg~ie~kg~je~ie~ie~ie~ie~he~he~he~he~ie~hc~gc~gc~hc~gc~gc~gc~gc~gc~gc~gc~fb~ea~ea~gc~fb~fb~fb~ea~ea~ea~d`~d`~ea~ea~e`~e`~d`~d`~d`~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~b^~c_~c_~b^~b^~c_~c_~c_~c_~b^~b^~c_~b_~b^~b^~c_~b^~b^~b^~b_~b^~c_~b^~b^~b^~b^~b^~b^~b_~b^~b^~b^~b^~b^~b^~b^~b^~b^~b^~b^~b^~c_~c_~c_~c_~c_~c_~c_~d`~c`~b_~d`~d`~d`~d`~d`~d`~d`~d`~ea~d`~d`~d`~ea~ea~ea~ea~ea~fb~ea~fb~eb~fb~fb~fb~gc~gc~gd~gc~gd~hd~hd~hd~hd~he~ie~if~if~jf~if~jf~jf~jf~jg~jg~jg~jg~kh~kh~kh~mj~mj~mj~nj~nj~nj~nj~nj~pl~pl~pl~pm~qn~qn~ro~sp~ro~sp~sp~sp~tq~ur~vr~ws~ws~wt~xt~xt~<:=;=;=;=;><><>=>=>=>=>=?=?>?>@?@?@?A?A?A?A@B@B@B@CADBCBCBDCDCDCECEDFDFDFDFEGEGEHFIGIGIGIGIGJHJHJHKIKJKJLKMKMKMKNLNLOMOMONPNPNQOQORPRPRPSQTRTRTRUSVTVTWUWUWUXVXWYWYXZXZY[Y\Z[Z\[]\^\^]^]_^`^a_b`bacbcbdcdbecedfefegfhfhfhfhghgihjhjhkijilklklklkmlmlmlonnmnmononpnqorprpsrtrtststsutvuvuvuxwxvxwyxyxzyzy{z{z{z|{|z|z|{}|}|~|~}~}~}~~~~~~}}~~}}~}|{|{{z{y{y{yzyywxvxvxvwuwuvtutvttssrsrsqsrsqrqqppnpnpoononnmnmmlmllklkkjjijijiihhghghfgffefefdedecdbcab`b`a`a``__^_^^]^\][][\Z\Z[YZYZXYXYWYWXVXVWUWUVTUTUSUSUSTRSRSRSRRPSQQPRPQPQOQOPOPNONOMOMNLNLNLMKMKLJLJKIKIKIJHJHIGJHIGIGHFHFHFGEGFFEGEFEEDECECDBDBDBDBDBCACACAB@B@B@A@A?A?@?@>@>?>?=?=?=?=><><><=<=;=;<;=;=;<:<:<:;9;9ur~uq~uq~uq~tp~sp~sp~sp~sp~qn~rn~qn~qn~qn~pl~pl~pl~ol~ol~nj~ok~mj~nj~mi~li~li~li~li~li~kh~kh~kh~jg~kg~kg~jf~jf~jf~jf~if~if~if~ie~ie~ie~ie~ie~hd~he~he~he~he~gd~gd~gc~gc~gc~hc~gc~gc~gc~fb~fb~fb~fc~fb~fb~fb~fb~fb~fb~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~d`~ea~ea~ea~d`~d`~d`~ea~ea~ea~ea~d`~d`~d`~ea~d`~d`~d`~d`~d`~d_~d`~d`~d`~d`~d`~d`~d`~d`~d`~d`~d`~d`~d`~d`~c`~d`~c_~c`~d`~d`~d`~d`~d`~d`~d`~d`~d`~ea~e`~fa~ea~ea~fb~fb~fb~fb~fb~fc~fc~fc~fc~fc~fc~fc~gd~gd~ie~ie~he~ie~ie~ie~ie~ie~ie~jf~ie~jf~kg~jf~jf~jf~jf~kg~kg~kg~kg~kg~kg~lh~lh~lh~lg~lh~lh~lh~lh~lh~mj~mj~nj~nj~nj~ok~ok~ok~ok~ok~pl~qm~86pl~96rn~97979797979797979797:797:7:7:7:8:7:8:8:8:8;8;8;8;8;8;8;9;8;8;9;9;9;9;9;9;9;8;9;8;9;9;9;9;9;9;9;9;9;9;9<:<:<:<:<:<:<:<:<:<:=:=:=;=;=;=;=;=;=;>;=;=;=;=;><><>;>;><><><><><><><><><><><><><><><?<?<><?<><?<><><><><><?<><?=?=><><?=?<><><?<?<?<><><><><><?<><?<>;><>;><><><><><><>;>;>;>;>;>;>;>;>;>;=;=;=;=;=:=:=:=:=:=:<:<:<:<:=:<:<:<:<:<9;9;9;9;9;9;9;9;9;9;9;8;8;8:8;8;8:8;8:8;8:8:8:8:8:8:8:7:8:8:8:8:7:79797979797979796979796969696pl~pl~pl~pl~pl~pl~ok~pl~ok~ok~ok~ok~ok~ni~ok~mi~mi~mi~mi~mi~lh~mi~lg~lh~lh~lh~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~ie~ie~ie~je~ie~je~je~ie~ie~ie~he~hc~he~gc~hd~gc~gc~gc~gc~gc~gc~gc~fb~fb~fb~fb~fb~ea~fb~fb~ea~ea~ea~ea~ea~e`~ea~d`~d`~d`~c_~d`~c_~c_~c_~c_~c_~c_~c_~c_~c_~c_~d`~d`~d`~d`~d`~c_~d`~c_~c_~d`~c_~c_~c_~c_~c_~c_~c_~c`~c_~c_~c`~c_~c_~c`~c_~c_~c`~c`~c_~c`~c_~c_~c_~c_~c`~d`~c_~c`~c`~d`~c`~d`~d`~d`~d`~d`~d`~d`~d`~d`~d`~da~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~eb~fb~eb~fb~fb~fb~fb~fb~fb~gc~gc~gc~gc~hd~hd~he~he~he~ie~ie~if~ie~if~if~jf~jg~jg~jg~jg~jg~kh~kh~lh~kh~kh~mj~mi~mi~nj~nj~nj~nj~pl~pl~pl~pm~qm~qm~qn~qn~sp~sp~sp~tp~tq~ur~vr~vs~vs~vs~xt~xt~<:<;=;=;=;=<><=<><>=>=?=?=?=?>?>@?@?A?A?A?B@B@B@B@CACACBCBDCDCDCDCECFDFDFDFEFEGEGEHFHFIGIGIGJHJHJIKIKIKILJLKMKMLNLNLNLOMOMPNPNPNQOQORPRPSQSQSQTRTRUSUTVTVUWVWVWVXWYWYWYXZY[Z[Z\Z\Z][^\_]_]`^`_`_a`a`bacbcbdcdcedfefegfgfhghgihjhjhjijikjlklklkmlmlmlnmnmononpnqoqprqrpsqsqsrtstsututvuvuwvxwxwyyzyzyzy{z|{|{|||{||}}~}}|~}~}~~~}~}}}|}||{{z{yzyzyyxyxyxxwxwxvwvvuvtututtstsrqsqrqrprpqopopoonomnmnmmllklklkljjijiihihihihhfgffefeecdbdbcacab`a`a_a_`^_^^]^\][][\Z[Z[Z[ZZYYWYWYWXWXWWVWUVTVTVTUSUSTSTRTRTRSRSQRPRPRPRPQOPNPOPNPNOMNLNLNLMKMKLJLJKIKIKIJHJHJHJHIGIGIGHFHFGEGEFEFDFDECECECECDBDBCACACACAB@B@B@B@A?A?A?@?@>@>?>?>?=>=>=>=><><><=;=;=;=;=;<:<:<:;9;9;9ur~uq~uq~sp~sp~sp~sp~rn~rn~rn~rn~rn~qm~pl~pl~pl~pl~pl~ok~ok~ok~mj~mj~mj~mj~mj~li~mi~li~li~lh~lh~lh~jf~jf~kh~jf~jf~jf~jf~jf~jf~jf~jf~if~jf~ie~if~hd~hd~ie~hd~hd~hd~hd~hd~gd~gc~hc~gc~gc~fc~fc~fc~gd~fc~fc~gc~fb~gb~fa~fb~fb~fb~fb~fb~ea~fb~fb~fb~fb~fa~ea~ea~fa~fa~fa~ea~ea~ea~ea~fa~ea~ea~fa~fa~fa~ea~ea~ea~ea~fb~ea~ea~ea~eb~ea~ea~ea~ea~ea~ea~ea~ea~d`~d`~ea~d`~ea~d`~ea~ea~ea~ea~ea~ea~eb~fb~fb~fb~fc~fc~fc~fc~gc~fc~gc~hc~hc~gc~ie~ie~ie~gd~ie~ie~ie~ie~ie~ie~ie~jf~jf~jf~kg~kg~kg~kg~kg~kg~jf~jf~lh~lh~lh~lh~lh~lg~mi~lh~mi~lh~mi~mj~mj~mj~nj~nj~ok~ok~ok~ok~pl~pl~pl~pl~pl~pl~969797979797979797:7:7:8:8:8:8:8:8:8:8:8;9:8;8;8;9;9;9;9;9;9;9;9;9;9;9;9<:;9;9;9;9<9;9;9<9<9<:;9;9<9<:<:<:<:<:<:<:<:=;<:=:=:=;=;=;=;=;=;=;=;>;><><><><>;><><><><><><><><?<><?=><?=?=?=?=><?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=?<?=?=?=?=?=?=?=?=?=?<?=?<?=?<?<><?<?<?<><><><><><><><><><>;><>;>;><>;>;>;>;>;>;=;=:=;=;=;=;=:=:=:=;<:<:<:<:<:<:<:<:<:;9;9;9;9;9;9;9;9;9;9;9;9;9;8;9;8;8;9;9;8;8;8:8:8:8:8:8:8:8:8:8:8:897979798979797979696968696qm~pl~qm~qm~qm~ok~ok~ok~ok~ok~ok~ok~ok~ok~ok~ok~nj~ni~mi~mi~lh~lh~lh~lg~lh~mi~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~je~je~je~ie~ie~je~je~ie~ie~he~he~he~he~he~hd~gc~gc~gc~fb~gc~gc~fb~gc~fa~ea~ea~ea~fb~fb~ea~ea~ea~ea~ea~d`~d`~ea~d`~d`~d`~d`~ea~d`~ea~ea~d`~d`~ea~ea~d`~ea~ea~d`~d`~d`~d`~d`~d`~d`~d`~d`~d`~d`~d`~da~ea~d`~d`~d`~d`~d`~d`~d`~d`~da~d`~d`~d`~d`~d`~d`~d`~d`~d`~da~da~d`~d`~da~d`~da~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~fb~fb~fb~fb~fb~gc~fb~fb~gc~gc~gc~gc~hd~hd~gc~hd~hd~hd~hd~hd~jf~ie~if~ie~je~ie~jg~jf~jf~jg~kh~jg~kg~kh~kh~kh~lh~lh~lh~nj~nj~nj~nj~nj~ol~nj~ol~ol~pl~qn~qn~qn~qn~qn~sp~so~tp~tq~sp~ur~vr~vr~ws~xt~xt~<:<:=;=;=;=;=<=<><><?=?=?=?=?>?>?>@>@>@?A?A?A@B@B@B@CACACBCBDCDCDCECEDEDFDFDGEGEGEHFHFHFIGIGIGJHJHJIKILJLJLJLJMKMKNLNLNLOMOMPNOMPNQOQORPRPRPSQSQTRTRTSUSVTVTVUWVWVXWXWYXZYZYZY[Y\Z\[]\^\^]_]`^`_a_a`babacbdbdcdcfdfdfegfgfhghgihihjikjkjlklklkmlmlmlnmononpopnonrprqsrsrsrtrtsusvtutvuwvxvxwyxyxzyzyzz|{|{|{}|}|~}~}~~~~~~~~~}}||{|{{z{z{zzyzyyxywxwxvxvvuvuutusustssrsrrqrqrprpqopoononnmnmmllklklkkjkjkjjiihhfhghegeededdcdbcbcbbab`a_`_`__]_]^]]\][\[\Z\Z[YZXYXYXXWXWXVWUWUWUVTVTVTUTUSTSTRTSSQSRRQRQRPQOPOPNPNPNOMOMOMOMNLMKMKMKLJLJKIKIJHJHJHJHJHIGHFHFHFGEGEFEFEFDEDECECECDBDBDBDBCACAB@B@B@B@A?A?A?A?@>@>@>?>?>?>?=><><><=<=<=;=;=;<:<:<:<:<:;9;9;9ur~ur~ur~tp~sp~sp~sp~rn~rn~qn~rn~qm~pl~pl~pl~pl~pl~pl~ol~nk~nj~ok~mj~nj~nj~mj~mi~li~mi~li~li~kh~kh~lh~kh~kh~kg~kg~jf~jf~jf~jf~jf~jf~jf~jf~jf~jf~if~ie~ie~ie~he~he~he~he~gd~gd~gc~gd~gd~he~gd~gd~gc~hc~gc~gc~gd~gc~fb~gc~gc~gc~gc~fb~gb~gb~gc~fb~fb~fb~fb~fb~fb~gc~fc~fb~fb~fb~fb~fb~fb~fb~ea~fb~fb~fb~fb~fb~fb~fb~fb~fb~fa~ea~fa~fa~fa~ea~eb~fb~fb~ea~fb~fb~ea~ea~fb~ea~fb~ea~fb~fc~fc~fc~fc~gc~gc~gc~gc~hd~hd~ie~ie~ie~ie~ie~ie~jf~jf~ie~ie~jf~jf~jf~kg~jf~jf~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~mi~lh~mi~mi~mi~nj~nj~nj~ok~ok~ok~ok~ok~ok~ok~ok~ok~pl~pl~pl~pl~pl~qm~qm~qm~rn~979797:7:7:8:8:8:8:8:8:8:8:8;9;9;9;9;9<9<9;9<9;9;9;9<9<9<:<:<:<:<:<:<:;9<:<:<:<:<:<:<:<:<:=:<:=:=:<:<:=:=;=:=:=;=;=;=;=;=;=;=;=;><=;><><?=><><><><><?=?=?=?=?=?=?=?=?=?=?=?=?=@>@>?=?=@>?=@>?=@>@>@>@>@>?=@>@=@>@>@>@>@>@>@=@>@>@>@>@>@=@>@>@>@>@>@=@>@>@>@=@>@>@>@>@>?=?=@=@=?=?=?=?=?=?=?=?=?=?=?=?<?<?=?=?=><><><><><><><><><><><><=;>;=;=;=;>;>;>;=;=;=;=;=:=:<:<:<:<:<:<9<:<:<:<:<:<:;9;9;9;9;9<9;9<9;9;9;9;9<9;8;9;9;9;9;9;9:8;9:8:8:8:8:8:8:797989797979796968697qm~qm~qm~qm~qm~ok~ok~ok~ok~ok~pl~ok~ok~ok~ok~ok~ni~nj~nj~mi~ni~mi~mi~mi~lh~kg~kg~kg~kg~kg~kg~kg~kg~kg~jf~kg~kg~kg~kg~kg~jf~ie~ie~ie~ie~ie~ie~hd~he~he~he~hc~gc~hd~gc~gc~fb~gc~gc~gc~fb~gc~fb~fb~ea~ea~fb~ea~ea~ea~ea~eb~eb~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~fb~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~ea~eb~fb~eb~ea~ea~fb~fb~fb~fb~fb~fb~fb~gc~fb~fb~fb~gc~gc~gc~gc~gc~gc~hd~gd~gd~hd~hd~hd~hd~ie~ie~ie~ie~ie~jf~jg~jg~if~jg~jg~jg~jg~jg~jg~kg~lh~kg~lh~lh~mi~mi~lh~mi~nj~nj~nj~ol~ol~pl~pl~ol~pm~pm~qn~qn~qn~sp~sp~sp~tp~tp~uq~ur~ur~vr~wt~xt~xt~<:<:<;<;=<=<=<=<>=>=>=>=?=?>?>@>@>@>A?A?A?A?B@B@BABACACACBDBDBDBDCEDFDFDFDGEGEGEHFHFHFHFHGIGIGJHJHJIKJKJLJLJMKMLMLNLNLOMOMPNPNPNQOQOQORPRPSQSQSQTRUSUSUSUTUTVUVUWVXWYXYXYXYXZY[Z\[]\]\^]^]_]_^`_`_a`b`cabacbdcedfdfdgegfgfhgigjijikikikjlklkmlnmnmnmononpoqoqprprqsrsrusututututvuwvwvxvxwyxzyzxzy{z{{|||{}}~}~}~~}~}}|}||||{{zzyzyyxyxyxxvwvwuwuwuvtutututsrsrsrrqrpqppoqopnonomnmlkmklkkjkjjijiihhghfgfgegefddcdcdbbabaa`a_`_`^_^^]^]]\]\][\Z\Z[ZZYYXYXYWYWXWXWXVWUVUUTVTUTUTUSUSTRTRSQRQRQQPQOQOPNPNPNOMOMNMNLMLMKMKLJLJLJKIKIJHJHJHJHIGIGHFHFHFGFGFFEFEFDEDEDEDDCDCDBCACACACACAB@B@B@B@A@A?A?A?@?@>?>?>?>?=>=><>=><><=;=;=;=;<:<:<:<:;:;9;9;9ur~uq~ur~tp~sp~sp~rn~so~rn~rn~rn~rn~qn~qn~pl~pl~pl~pl~ok~pl~pl~nj~nj~nj~nj~mj~mj~mj~lh~mi~li~mi~li~li~li~kh~lh~lh~jf~kh~jf~jf~jf~jf~jf~jf~jf~jf~jf~if~jf~ie~ie~ie~ie~ie~ie~he~he~hd~he~he~he~hd~gc~gd~gd~hd~gc~gc~hc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~hd~gc~fb~fb~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~fb~fb~fb~fb~fb~fb~fb~fb~fb~ea~fc~fc~fc~fc~fc~fc~fc~gc~gd~gc~hd~gc~ie~ie~ie~ie~ie~ie~ie~jf~jf~jf~jf~jf~kg~jf~jf~lg~kg~lg~lg~lh~kg~lh~lh~lh~lh~lh~mi~mi~mi~mi~ok~mi~ok~ok~ok~ok~ok~nj~pl~ok~ok~ok~pl~ok~pl~pl~pl~qm~pl~8696979696979797:8:8:8:8:8:8;9;9;9;9;9;9;9<9;9<9<9<:<9<:<:<:<:<:<:<:<:<:=;=;=;=;<:=;=;=:=;=:=:=:=;=;=;=;=:=:=;=;=;=;=;=;=;=;=;><><=;><=;><><><><><><><?=><?=?=?=?=?=?=?=?=@>?=?=@=?=@>@>@>@>@>@>@>@>@>@>@>@>A>@>A>A>A>A>@>A>A>A>A>A>A>A>A>A>A?A?A?A?A?A?A?A>A>A?A?A>A>A?A>A?A?A>A>A>A>A>A>A>@>A>@>A>@=@>@>@>@>@>@=@=@=?=?=?=?=?=?=?=?=?=?=?=?=><?=><><><><><><>;>;>;>;=;=;=;=;=;=;=;=;=;=:=:=;=;<:<:=;<:<:<:<:<:<:<:<:<:<:<:<:<:;:<:<:<:<:<:;:;9;9;9;9;9;9;9;9;9:8:8:8:8:8:8:897:8989797979797rn~qm~qm~qm~qm~qm~qm~qm~ok~qm~ok~ok~ok~ok~ok~ok~ok~ok~ni~ni~ni~ni~ni~ni~ni~mi~mi~lh~lh~lh~lh~kg~lh~kg~kg~kg~kg~kg~kg~kg~jf~jf~jf~if~jf~ie~ie~ie~ie~he~he~he~gc~hd~gc~gc~gc~gc~gc~gc~fc~gc~fb~ea~fb~fb~fb~fb~fb~fb~fc~eb~fb~fb~fc~fb~gb~fb~fb~fb~fb~fb~fb~fb~fb~gc~fb~fb~ea~fb~fb~fb~fb~fb~fb~fb~fc~fb~fb~gc~fb~fb~eb~eb~fb~fb~fb~fb~fb~fb~fb~fb~fb~fb~fb~fb~fb~fb~fb~gc~fb~gc~fb~fb~gc~gc~gc~gc~gc~gc~hd~hd~gd~gd~hd~hd~hd~hd~hd~hd~ie~ie~ie~ie~ie~ie~je~je~je~jg~jg~jg~jg~kg~kg~kg~kh~kh~kh~kh~lh~lh~lh~lj~mj~nj~nj~nj~nj~ol~ol~ol~pl~pl~qn~qn~pm~qn~rn~so~so~sp~tp~tp~tp~ur~vr~vr~wt~wt~<:<:<:<:<;<;=;=<=<=<>=>=>=?=?>?>@>@>@>@>A?A?A?A?A?B@CACACACBCBDCDCDBECECFDFDFDGEGEGEHFHFHFHFIGIGIGJHJHKILJKJLJMKMKMKMLNLNLOMPNOMPNPNQOQPRPRPRPRPSQTRTRUSUSUSVTVUVUWUWVXWXWYXZYZYZY\Z\[][][]\^]_^`^`_`_a`a`bacadbececedfegegehghfihjijikjlkkjkjmlnlnmnmnmononporprqrqsrsrsrtstsutvuwvxwxwxwxwyxyxzyzy{z|{|{|{||}}~~~~~}}}}|}||{{z{z{yzyyxyxxvwvxvwuwuwuututustsrqrqrqqpqoqopnpnonnmmlmkmklkkjkiihihigihhghfgffefedccbcbbab`a_`_`__^^]^]^\]\][][\Z[Z[YZYYXYXXWXWXWWVVUVUVUVUVTUTUSTSTRSRSRSQSQRPRPQOPOPNPNOMOMNMNLNLMKMKMKLJLJLJKIKIJHJHIGIGIGIGHFHFGEGEGEGEFDEDEDECDCDBDCDCCBCACACACAB@B@A?A?A?@?@?@?@>?>?>?=>=?=><><=<=<=<=;=;<;<:<:<:<:;9;9;9;9ur~;9;9uq~uq~tp~sp~so~so~so~so~rn~rn~pl~pl~pl~pl~pl~pl~pl~ok~ok~ok~ok~nj~nj~nj~nj~mj~mj~mi~mi~mi~li~li~li~li~lh~kh~kh~kh~kh~kh~lh~jf~jf~jf~jf~jf~jf~jf~jf~jf~jf~ie~jf~if~ie~ie~ie~ie~hd~hd~hd~hd~hd~ie~hd~gc~ie~ie~hd~ie~hd~ie~hd~hd~hd~hd~hd~hd~hd~hd~gc~gc~hd~gc~hd~hd~hd~gc~gc~gc~gc~hd~gc~gc~hd~gc~gc~gc~gc~gc~gc~fc~gc~gc~gc~gc~gc~hc~gc~hc~he~hc~he~he~ie~ie~ie~ie~ie~ie~ie~ie~jf~jf~je~jf~jf~jf~kg~kg~lh~kg~lh~mi~mi~lh~mi~mi~mi~mi~mi~mi~mi~nj~mj~nj~nj~nj~nj~ok~ok~ok~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~qm~qm~qm~rn~qn~979797979797:7:797:8:8:8;9;9;9;9;9;9;9<9<:<9<9<:<:<:<:<:<:=;=;=;=;=:=:<:=;=;=;=;=;=;=:>;><>;>;>;=;=;=;=;><=;=;=;>;><=;><><><><>;><><><><><?=><?=?=?=?=?=@=@=@=@>?=@>@>?=@>@>@>@>@>@>A>A>A>A?A?A?@>A?A?A?A?B?B?A?A?A?A?A?A?A?A?A?A?A?B?A?A?A?A?A?A?B@B@B@A?A?A?B?B?B?A?A?B?B?A?B?B?B?B?B?B?B?B?A?B?A?A?A>A>A>A>A>@>@>@>@>@>@>@>@>@>@>@=@=@=@=@=?=?=@=?=?=?=?=?=?=?=><><><><><><><>;><=;=;=;>;=;=;=;=;=;=;=;=;=;=;=:=:=;=;=;<:<:=;=;=;=;<:=;<:<:<:<:<:<:<:<:<:<:<9;9;9;9;9;9;9:8:8:8:8:8:8:8:8:8:7:8979797979697qm~9696qm~qm~qm~qm~qm~qm~ok~ok~ok~ok~ok~ok~ok~nj~nj~ni~ok~mi~mi~mi~ni~mi~ni~ni~lh~lh~lh~lh~lh~lh~kg~lh~kg~kg~kg~kg~kg~ie~je~ie~ie~if~ie~ie~he~he~he~he~he~hd~hd~hd~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gd~fc~gc~gc~gc~gc~gc~gc~gc~gc~gc~fc~fc~fc~gc~fc~gc~gc~gc~gc~gc~gc~gc~gc~gc~fc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~gc~hd~gc~gc~gc~fc~hd~hd~hd~gc~hd~hd~hd~hd~hd~hd~hd~gc~hd~ie~hd~hd~hd~ie~ie~hd~ie~je~ie~if~if~if~jg~jf~jg~kg~jg~jg~kh~kg~kh~kg~lh~lh~lh~lh~lh~mi~mi~mj~nj~nj~nj~nj~nj~pl~pl~pl~pl~pm~pm~pm~qn~qn~rn~sp~sp~tp~tp~tp~vr~vr~vr~vr~wt~xt~<:<:<;<;=;=;=;=<=<=<><>=>=?=?>@>@>@>@>A?A?A?A?B@B@BABACACACBDBDBDCDCECECEDFDFDGEGEGEGEHFHFHFIGIGIHJHJIKIKJLJLKMKMKMLNLNLOMNMOMPNPNQOQOQORPRPSQSQSQSRTRTRTSVTVUVUWUWVWVXWXWYXYXZY[Y\Z\Z][]\^]^]^]_^`_`_a`b`cacacbdcecfdfefegehghgihjijikjlklkmlmlnmnmonpnpnpoqprpsqsqtstsutuuutvuwvwvxvxwyxyxzyzy{z{z|{}|}|}|~}~~~~~~~}~}}|}||{|{{zzyzyyxzyywywxwwvwuvuutussrtssrsrsqrqqppopoonnmnmmlmllklkljljkjjhihihhggffeedcbdccab`a`a_`__^_]_]_]^\][][\[[Z\Z[YZYZXZXYWXVXWXVWUWUWUVUVUVTUSUSTRTRTRRQRPRPQPQOPOPOONONOMNLNLNLMKMKMKLJLJLJKIJHJHJHIGIGIGHFHFHFHFGEGEFDFDFDECECECECDBDBDBCACACAB@B@B@B@A?A?A?A?@>@>@>?>@>?=?=>=><><><=;=;=;<;<;<;<:<:<:<:;9;9;9;9;9ur~ur~tp~tp~tp~so~so~so~rn~qn~qn~qn~pl~pl~qn~pl~pl~pl~pl~pl~pl~ol~nj~nj~nj~nj~nj~ok~mj~mj~mj~mj~li~li~li~kh~kh~li~lh~kh~kh~kh~kg~lh~jf~jf~jf~jf~jf~jf~jf~jf~jf~jf~jf~jf~jf~hd~jf~jf~jf~ie~ie~ie~ie~ie~ie~ie~ie~ie~ie~ie~ie~hd~ie~hd~hd~hd~hd~hd~hd~he~hc~he~he~he~hd~hd~hd~hd~he~hc~he~he~he~he~he~he~he~he~ie~he~he~ie~hc~hc~ie~ie~ie~ie~ie~ie~ie~ie~ie~jf~jf~jf~jf~jf~jf~lh~kg~kg~kh~lh~lh~lh~mi~mi~mi~mi~mi~mi~mi~nj~nj~nj~ok~nj~nj~ok~ok~ok~ok~ok~ok~pl~ok~pl~pl~pl~pl~qm~qm~pl~qm~qm~9787979797979797:7:7:8:8:8:8:8:8:8;9<9;9;9<:<:<:<:<:<:<:<:<:<:=;=;=;=;=;=;=;=;=;>;=;><><><><><><><><><><><><><><><><><><><><><><><><><><?=?=?=?=?=?=?=@=?=?=@>@>@>@>@>@>@>@>@>@>A>A?@>A>A?A?A?A?A?A?A?B?B?A?A?A?B@A?B?B@B?B@B@B@A?B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B?B?B?B@A?A?B?A?B?A?A?A?A?A?A?A?A?A?A>A>A>A>A>@>@>@>@=@>@=@>@=@=@>@=@=?=?=?=?=?=?=?<?<?=?=><><><><><><><=;=;=;=;=;=;=;=;>;=;=;=;=;=;=;=;=;=;=;=;=;=;=;<:=;<;<:<:<:<:<:<:<:<:<:;9;9;9;9;9;9;9;9:8:8:8:8:8:897:7979797979797rn~rn~qm~qm~qm~qm~qm~qm~qm~qm~qm~ok~ok~ok~ok~pk~ok~ok~ok~ok~ok~ok~ni~ni~ni~ni~ni~ni~mi~lh~lh~lh~lh~lh~kg~kg~kg~kg~kg~kg~kg~kg~kg~if~jg~if~ie~he~je~ie~he~he~hd~hd~hd~hd~hd~hd~hd~hd~hd~hd~hd~hd~hd~hd~hd~hd~hd~gc~gc~gc~gd~he~gd~he~gc~hd~hd~hd~hd~hd~hd~he~hd~hd~hd~hd~hd~hd~hd~hd~hd~hd~hc~hd~hd~he~hd~hd~hd~hd~hd~hd~hd~hd~hd~hd~ie~hd~hd~hd~ie~ie~ie~hd~hd~ie~ie~ie~ie~ie~ie~je~je~jf~jf~jf~jf~jg~jg~jg~jg~jg~kg~kg~kg~lh~kh~lh~lh~lh~lh~mi~mi~nj~nj~nj~nj~nj~nj~nj~nj~mj~nk~ol~ol~ol~qn~qn~qm~qn~qn~qn~sp~sp~tp~tp~tp~ur~ur~vr~vr~wt~wt~wt~<:<;<;=;<;=;=<><><><>=>=>=>=?>@>@>@>@>@>A?A?A?A@B@B@BACACACADBDBECDCECECFDFDFDGEGEGEHFHFHFIGIGIGJHJHJIKIKIKILJMKMKMLNLNLOMONONPNQOQOQOQOQORPSQSQSQSRTRTSUTUSVTVUVUWVWVXVYWYXZYZY[Y[Z\[][\[]\^]_^_^`_a`a`babacbdcececedfefegfhgihjijijikjlklkmlnmnmompnqoqpqprprpsrsrtsususutvuwvxvxwxwyxyx{yzz{{{{|{}|}|~}~}~~}~}~}~|}||{|{|z{z{zzyzxyxxwxwwuvuwuututtststsrqrqqppoonononomnlnmmkmkljljkiihihhggffeeddcdbdbcabab`a_`_`_`^_\_]^\]\][\[[Z\Z\Z[YZXYWYWXWXWXVWVWUVUVUVTVTUSUSTRTRTRSQRPRPRPQOQOPNPNONOMNMNLNLNLMKLKLJLJKIKIJHJHJHIGIGIGHFHFHFGEGEFDFDFDFDFDEDECECECDBDBCBCACAB@B@A?B@B@A?A?@>@>@>@>@>?=?=?=>=><><><=<=<=;=;=;<;<;<:;:;:;9<:;9;9;9;9:8tp~tp~tp~tp~so~sp~so~so~so~rn~rn~rn~qn~qn~pl~pl~pl~pl~ol~ol~ol~pl~ok~ok~ok~nj~nj~nj~nj~mi~mi~mj~mi~li~li~li~li~li~li~lh~lh~kh~kh~lh~lh~jg~kh~jf~jf~kg~kh~jf~jf~jg~jg~jf~jf~jf~jf~jf~jf~jf~jf~jf~jf~jf~jf~ie~je~jf~ie~jf~ie~he~jf~jf~ie~ie~he~he~he~ie~ie~jf~if~je~je~ie~he~ie~ie~if~ie~ie~ie~ie~ie~ie~ie~ie~ie~ie~ie~ie~ie~jf~jf~jf~jf~jf~jf~jf~lg~kg~lg~lh~lh~lh~mi~mi~mi~nj~nj~nj~nj~nj~nj~ok~nj~ok~nj~nj~nj~ok~pl~ok~pl~pl~pl~pl~pl~pk~pl~pl~qm~pl~97869787868797979797979797:7:8:8:8:8:8:8:8:8:8;9;9<:<:<:<:<:<:<:=;=:<:=;=:=;=;=;=;=;=;>;=;><><><><><><?<?<><?=?=?=?<><><?<><?=?=?=?=?=?=?=?=?=?=?=?=?=?=@=@>@=@>@>@>@>@>@>@>@>@>@>@>@>@>A?A?A?A>A?A?A?A?A?B@B@B@B@B@B@B@B@B@B@B@CAB@B@CAB@CACACACACACACACACACACACACACACACACACACACACACACACACACACACACACACACACACACACACAB@B@CACAB@B@B@B@B@B@B@B@B@B@B@B@B@B@B?B@A?A?A?A?A?A?A?A?@>A?A>A>A>@>@>@>@>A>@>@>@>@>@=@=@=@>?=?=?=?=?=?=><><><><><><><><><><><><><><><>;><><><><><=;=;=;=;=;=;=;=;=;=;=;=:<:<:<:<:<;<:<:<9<9<:<9;9;9;9;9:8:8:8:8:8:8:8:8:7:7:7:7:797979797rn~9796qm~qm~qm~qm~ok~pk~pl~pl~pl~pl~ok~pk~ok~ok~ok~ok~ok~ok~ok~ok~nj~nj~ni~mi~ni~lh~lh~lh~lh~lh~lh~lh~kg~kg~kg~kg~kg~kg~kg~jg~kg~ie~ie~ie~he~he~if~hd~ie~hd~hd~hd~hd~hd~hd~hd~hd~hd~ie~ie~ie~ie~ie~ie~hd~ie~ie~ie~ie~ie~ie~ie~hd~ie~hd~ie~ie~ie~ie~ie~he~ie~he~he~he~he~he~he~he~he~ie~hd~ie~ie~ie~ie~ie~ie~ie~ie~ie~ie~id~ie~if~if~if~jf~jf~ie~ie~je~jg~jf~jg~jg~jg~jg~jg~kg~jg~kg~kg~kg~lh~kh~kh~lh~lh~mi~lh~lh~mi~mi~mi~nj~nj~nj~nj~nj~nj~ol~ol~ol~pm~pm~ol~pm~qn~qn~qn~rn~rn~rn~ro~tp~tp~tp~ur~tp~ur~vr~vs~wt~wt~wt~<:<;<;<;=;=;=<=<=<><>=>=?=?=?=@>@>A?@>A?A?A?B@B@B@BACACBCBCBCBDBDCECECECFDECFDGEGEGEHFHFIGIGIGJHJHJIJIKJKIKJLJLJMKMLMLNMNMOMPNPNPNQOQORPRPRQSQSQSRTRTRTSUTUTVTVTWUWVXVXWYXYXZX[Y[Z[Z\[][]\^]^]_^_^`_a_a`b`cacbdcdcdcfdfegehfhgigihjikjkjlkmlnlnmononpoqorprqrqsrtrtstsusvuvuvuwvxvywyxzxzxzyzz|{{{}|}|~}~}~~~~}~}}|}{|{{z{zzyxwyxxwxwwvwvwuvtvtuttssrrqrqrppopnpopopnomnlmllkljkijijhihhggffeececdbcbdbcab`b`a`a_`^_]_]^]]\][][\Z\Z[Y[YZYZYYXYXYWXWWVWUWUWUVTVTUSUSTSTRSQSQSQRPQOQOQOPNPNOMOMNMNLNLMKMKMKLJLJLJKIJHJHJHIGIGIGHFHFHFGEGEGEFDFDFDFDECECECDCDCDBCACAB@B@B@B@B@A?A?A?@>@>@>@>@>?=?=?=?=><><><=<><=<<;=;<;<;<:<:<:<:;:;9;9;9;9;9:8uq~uq~tp~tp~tp~sp~so~so~so~so~rn~qn~qn~qn~qm~pl~pl~pl~pl~pl~pl~ok~pl~nj~ok~ok~ok~nj~nj~nj~nj~mi~mj~mi~mi~mi~mi~mi~lh~kh~li~li~kh~kh~li~li~kh~lh~kh~kh~kh~kh~kh~jg~jg~jf~jg~jf~kh~jg~jf~jg~jg~jg~jg~jg~jg~jf~jg~jg~jf~jf~jg~jg~if~kg~jg~kg~jf~kg~jg~if~kg~kg~jf~jf~kg~jf~jf~jf~kg~kg~jf~jf~kg~kg~kg~kg~jf~jf~kg~jf~jf~lh~kg~kg~kg~kg~kg~mi~mi~lh~lh~mj~nj~mi~mi~mi~nj~nj~nj~ok~pl~ok~ok~pk~pk~pl~pl~pl~pl~pl~pl~86pl~969797979797979797979797:8:8:898:8:8:8:8:8:8:8;9;9;9;9;9;9;9;9<9<9<:<:=;=;=;=;=;=;=;=;=;=;><=;><?<><><><><><><><?=?=?=?=?=?=?=@>?=?=?=?=?=?=@=@>?=@=@=@=@>@=@=@>@=@>@=@>@>@>@>@>@>@>@>A?@>A?A?A?A?A?A?A?B@B@B@B@B@B@B@B@B@B@CACACACACACACACACACACACACACACACACACACACADBDBCADBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDADADBCADBCADBDBDBCACACACACACACACACACACACACACACACAC@B@CAB@B@B@B@B@B?A?B@B@A?A?A?A?A?A?A?A?A?A?A>@>@>@>@>@>@>@>@>@=@=@=@=@=?=?=?=?=?=?=?=?<?=><?<?<?<><?<?<><><?=><><><><><><><><><=;=;=;=;=;=;=;=;=;<:=;=;<:<:<:<:<:<:<9;9<9<9;9;9;9;9:8:8:8:8:8:8:8:8:8:7:8:7:7979797979796rn~rn~qm~qm~qm~qm~pl~qm~ok~qm~ok~ok~ok~ok~ok~ok~ok~ok~ok~ok~ok~ok~nj~nj~mi~lh~lh~lh~lh~lh~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~jg~ie~je~jg~je~je~ie~jf~jf~ie~jf~ie~jf~jf~jf~ie~jf~hd~ie~ie~ie~if~jf~if~if~ie~ie~jf~if~jf~if~ie~ie~ie~ie~if~if~ie~ie~ie~if~ie~ie~if~if~if~if~ie~if~je~je~ie~jg~ie~jg~jg~jf~jg~jg~je~jg~jg~jg~jg~jg~jg~kg~kh~kg~kg~kg~kg~kg~kh~kh~lh~lh~mi~lh~lh~mi~lh~mj~nj~nj~nj~nj~nj~nk~nk~pl~nk~ol~ol~ol~pm~qm~ol~qm~qn~qn~qn~qn~rn~ro~sp~rn~tp~tp~tp~tp~ur~ur~vr~vr~wt~wt~wt~<:<:<;<;<;=<=<><><><>=?=?=?=@>@>@>@>A?A?A?A?A?B@BAB@BACACACBDBDBDCECECECFDFDFDGEGEGEHFHFHGIGIGJHJHJHKIKIKILJLJLKLKMLMLNLOMOMPNPNPNQOQORPRPRPSQSQTRTRTSTSTSUTVTVUVUWVXVYWXWYXZYZY[Y\Z\[]\]\^]^]_]`_`_`_a`bacacbdbecededgfgfhghgihihjikiljlkmkmlnmompnqoqprqsqsrsrtsusvtvtwuwvxwxwyxyxzxzxzy{z|z|{}|}|~}~~~LKKKLKLKLKKJKJKK~~}~}~|~|}{|{{z{yzyyxyxywxwxvwvwuvutstrtrsqrqrqrprqqponomnlnlmklkljkijhihhghggffdfdecdbdbcacabab`a``_`^_^_^^]]\]\][\Z\Z[Y[Y[YZXZXYWYWXWXVWUWUWUVTVTUSUSTRTRSRSQRPRPRPQOQOPNPNOMOMNMNLNLNLMKLJLJLJLJKIKIJHJHIGIGHFIGHFHFGEGEFDFDFDFDFDECECECECDCCBCBCBCAB@BAA@A@A?A?A?A?@>@>@>@>@>@>?=?=?=><><><=<=<=<=;<;<;<;<;;:<;;:;9;9;9;9;9;9:8:8:8tp~tp~tp~tp~so~so~sp~so~so~so~so~rn~rn~rn~qm~qm~pl~qn~pl~pl~pl~pl~ok~nj~nj~nj~nj~nj~nj~nj~nj~nj~nj~nj~mi~mi~mj~mi~mi~mi~mi~mi~mi~li~li~li~li~kh~lh~kh~kh~kh~kh~kh~kh~kh~kh~kh~kh~kh~jg~jg~kg~jg~jg~jg~jg~jg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~jf~lh~lh~kg~kg~kg~kg~kg~kg~kg~kg~kg~lh~kg~kg~lg~lg~lh~mi~mi~mi~mi~mi~mi~nj~nj~nj~nj~nj~nj~ok~ok~pk~oj~pl~pl~pl~pl~pl~8686pl~qn~8797879797979797979797:89898:8:8:8:8:8:8:8;9:8;9;9;9;9;9;9;9;9;9<:<:<:<:<:<:<:=;=;=;><=;=;><><><><><><><?<?<?=?<?<?=?=?<?=?=?=@=?=@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>A>A>A>@>@>@>A>@>@>A>@>A?A?A?A?A?A?A?A?A?A?B@B@B@B@B@B@B@B@B@CACAB@CACACACACACACACACADBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBEBEBDBDBEBECDBECDBECECECECECECDBEBECECECECDBECEBDBDBDBDBDBDBDBCACADADBCADBDBDBDADADACACACACACACACACACAB@CAB@B@B@B@B@B?B@A?B@B@A?B@A?A?A?A?A?A?A?A>A>A?A>@>@>@>@>@>@>@>@>@>@=@>@>?=@=?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=><><?<?<><><><><><=;=;=;=;=;=;=;=;=;=;<;<;<:<:<:<:<9<:<:;9;9;9;9;9:8:8:8:8:8:8:8:8:7:7:7:7:8:7:7:7979797979796qm~qm~96pl~qm~pl~pl~pk~qm~qm~pl~ok~pl~ok~ok~ok~ok~nj~mj~mj~mj~mi~mi~mi~lh~mi~lh~lh~lh~kg~lh~kg~kh~kg~kg~kg~kh~jg~kg~kg~kg~kg~kg~jg~jg~jg~jg~jg~jf~jf~jg~jf~jf~jf~jf~jf~jf~jf~jf~jf~jf~jf~jf~jf~jf~jf~kg~kg~kg~jf~jf~jf~kg~jf~jf~jf~jf~kg~kg~jg~jg~kg~kg~jg~kg~jg~jg~jg~jg~jg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~kg~lh~lh~mi~mi~lh~lh~lh~lh~nj~mi~nj~nj~nj~nj~nj~nk~nk~nk~ol~ol~ol~ol~pm~pm~ol~qn~qm~rn~qn~rn~rn~rn~rn~tp~tp~tp~tp~tp~tp~ur~vr~vr~wt~wt~wt~wt~;:;:<:<;=;=;=;><><><><>=?=?=?=@>@>@>@>@>A?A?A@B@B@BABABACBCBCBDBDBECECECEDFDFDFEGEGEGEHFHFHFIGIGIHJHJIKIKIKILJLJMKMKMKNLNLOMOMPNPNPOQOQORPRPRQSQSQTRTRTSTSUSUTUTVUVUWVWVYWYWYXZY[Y[Z[Z[Z][]\]\^]_^`_a_a`bacabacbdcecedfefegfhgihihihkikjkiljmknlnmonpopoqprqrqtrsrutvtvtwuxvxvxwyxyxzxzy{z{z|{|{}|~}~}~}~~LLMLMLLLLLLLLLLLMMMLMLMLMLMLMMMLMLMMMMMLMLLLLLMMMLLLMLMLMLLLLLLLLLLLLLMMMMMMML~}~}~}}||{|{{z{zzyzxyxxwxvwvvuvuustssrsqtrsrrpqpqopoomnmnlmkljkjkijijhighfgefefdececdcdbcabaa`a`a_`__^_]_]^\^\][\[\Z[Z[YZXZXZXYWYWXVWVXVWUVUVUVTUSUSTSTRTRSQSQRPRPQOQOPNPNPNOMOMNLNLMKMKMKLJLJKIKIKIJHJHIGIGIGHFHFGEGEGEGEFDFDFDECFDECECDCDBDBCBCABABABAA@B@A@A@A?@>A?@>@>@>@>?=?=?=><><><><><><><=;=;=;<;<;<;<:<:;:;:;:;9;9;9;9;9;9:8;9:8:8tp~tp~tp~sp~so~so~so~so~qn~qn~qn~qn~rn~qn~pl~pl~pl~pl~ol~pl~pl~pl~pl~ok~ok~nj~nj~ok~nj~nj~nj~nj~nj~nj~nj~nj~mj~mj~mj~mj~nj~mi~mi~lh~mj~mi~mi~mi~mi~lh~mi~mi~lh~mi~mi~mi~lh~lh~kh~kg~kh~kg~kg~lh~lh~lh~lh~kg~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lg~mi~lh~mi~lg~lg~lh~lh~mi~mi~mi~mi~mi~mi~mi~nj~mi~ni~nj~nj~nj~nj~ol~pl~pk~pk~868686878696969797979797979798979798:8:8:8:7:8:8:8:8;9:8:8:8;9;9;9;9;9;9;9;9<:<:<:<:<:<:<:<:<:=;=;=;=;=;><><><><><?=?=?<?=?=?=?=@=@=?=?=?=?=@>@>?=@>@>@>@>A?@>A>A>@>@>@>@>@>@>A?A>A?A?A?A?B?A?A?A?A?A?A?A?A?A?A?B@B@B@B@B@B@B@B@B@B@CAB@CACACADACACACADBDBCADBDBDBDBDBDBDBDBDBDBECECDBECEBEBECECDBECECECECECECECFCECECECECECECECECECECECECECECFCFCFCECECECECECECECECECECECECDBECEBDBDBDBDBEBDBEBDBDBEBDBDBDBDBDBDBDBDBDBDBCACACACACACAC@CACACACAB@B@B@B@B@B@B@B@A?A?A?A?A?A?A?@>@>@>A?A>A?@>A>A>@>@>@>@>@>A>@>@>@>@>@>@>@>@>?=?=?=?=?=?=?=?=?<?=?=><><><><><><><><><><=;=;=;=;<;=;<:<:=:<:<:<:<:<:<:;9;9;9;9;9;9;9:9;8:8:8:8:8:8:8:8:8:8:7:7:7:79797979797969696979696pl~pl~qm~qm~pl~pl~pl~pl~ok~ok~ok~ok~ni~ni~ni~nj~mj~lh~mi~mi~lh~lh~lh~lh~lh~lh~lh~lh~lh~kg~lh~kg~kg~kg~kg~kg~kg~kh~kh~kh~kg~kg~lg~jg~kh~kh~jg~kh~jg~jg~kg~jg~jf~jf~jf~kg~kg~lh~jf~lh~jf~jf~kg~lh~jf~jf~jf~jf~kg~kg~kh~kh~lh~lh~kg~kg~kg~kh~lh~kh~kh~kh~kh~mi~lh~lh~kh~lh~mi~lh~lh~mi~mi~mi~mi~mi~nj~nj~nj~nj~nj~nk~nk~nk~nk~nk~ok~ol~ol~pl~pl~pl~pl~qm~qm~qm~rn~rn~rn~rn~rn~tp~tp~tp~tp~tp~tp~ur~ur~vr~vr~vr~wt~wt~wt~<:<:<;<;=;=;=<><><><><><?=?=?=?>?>@>@>@?@?A?A@A@A@B@B@BACACBCBCBDBECDBECECFDFDFDFDGEGEGEHFHFIGIGIGIHJHJHKILJLJLJLJLJMKMKMKNLNLOMPNPNPNQOQORPRPRPSQTRTRTRTRTSUTUTVTWUWVWVXVXWXWYXZXZY[Y[Z\[]\]\^]^]_^_^`_a`b`cacbcbdcdcedfdgfgfhghgjhjhjikjlkmkmknmnmonpoqpqprqsqtrtsusvtwuwuwvxwywzyzyzy{z|{|{}|}|~}}~~LLLLLLMMMMLLLLMLMMNNNNMMMMMMMMMMMMMMMMNNNNNNNMNNONONONOOOOONONONNMNMONONONNNNMNMNMNMNMNMNMNNNNNNMMMMLLMMMMMMMMNNNNNNNNNNMM~~~~}|{|{{z{zzyzxyxywxvxvwvvuvtusuttrtrsqrpqppopnonomnlmlmllkkjjhigighfgffefeeddcdbcbbababaa`a_`_`^_]^\^\]\\[\[\Z[ZZYZXZXYXYXYWYWXVWUWVWUVTVTUTUSTSTRTRSQSQRPRPQOQOPNPNOMOMNLNLMLMKMKMKLJLJLJKIJHJHJHIGIGHFHFHFGEGEGEGEGEFDFDFDECECECDBDBDBDBCACACAB@B@B@B@A@A@A?A?@>@>@>@>@>?=?=?=?=?=><><><=<=<=<=;<;<;<;<:<:<:<:<:<:;9;9;9;9;9;9;9:8;9:8:8:8tp~tp~tp~so~sp~ro~sp~so~rn~so~rn~qn~qn~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~ok~nj~ol~nj~nj~pl~ok~nj~nj~nj~nj~nj~nj~nj~nj~nj~nj~mi~nj~mi~nj~nj~mi~mi~mi~mi~nj~mi~lh~mi~mi~mi~lh~mi~lh~mi~mi~mi~mi~lh~mi~mi~lh~lh~lh~lh~lh~ni~lh~lh~mi~mi~mi~ni~mi~ok~mi~mi~mi~nj~nj~nj~nj~nj~nj~nj~nj~ok~ok~ok~ok~pl~pl~pl~pl~86868686979797979797979798:8:8:8:8:8:8:8:8:8:8:8:8;9:8;9;9;9;9;9;9;9;9<:<9<9<:<:<:<;=:=:<;<:=;=;=;=;=;=;=;=;><><?=?=?=?=?=?=?=?=?=@>@>@>@>@>@>@>@>@>@>@>A>A>A?A?A?A?A?B@B@A?A?A?A?B@A?B?A?B@B@B@B@B@B@A?B@B@B@B@B@B@B@CAC@B@CACACACACACACACACADACADADADBDBDBDBDBDBEBEBEBECEBECECFCECECFCFCFCFDFCFDFDFCFDFDFDFCFCFCFDFDFDFDFDFDFDFDFDFDFDFDGDFDFCFDFDFDFDFDFDFDFDGDFDFDFDFCFCFDFDECECECFDECECECFCECECECFCECECECEBECEBEBEBDBDBDBDBDBDBDBDBDBCADBCADBCACACACACACACACAB@B@B@B@B@B@B@B@B@A?A?A?A?A?A?A?A?A?A?A?A?A?A?A>A?A?A>@>A?A?@>@>@>@>@>@>@>@>@>@>@>?=?=?=?=?=?=?=?=?<><><><><><><><=;=;=;=;=;=;=;<:=;=:<:<:<:<:<:;9;9;9;9;9;9:8;8;8;8;9;9:8:8:8:8:8:8:8:8:8:8989797979797979797969696pl~pl~pl~ok~ok~qm~ok~ok~ok~ok~ok~ok~nj~nj~nj~mi~mj~mj~lh~mi~mi~mi~lh~mi~mi~lh~mi~mi~lh~lh~mi~mi~mi~mi~mi~li~mi~mi~li~li~li~li~li~lh~lh~li~li~li~lh~lh~li~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~lh~mi~mi~lh~mj~lj~mi~mi~mi~mi~nj~nj~nj~nj~nj~nk~nj~ol~nk~ol~pm~pk~ol~ol~ol~qm~qm~qm~qm~rn~qm~rn~rn~rn~so~rn~rn~so~tp~tp~tp~tp~uq~tp~ur~ur~ur~wt~wt~;:;:<:<:<;<;=;<;=;=;><><><><?=?=?=?>?>@>@>A?A?@?A?A@A@A@B@BABACACACBCBDBDBDBECECFDFDFDGEGEGEGEHFHFHGIGIGIHJHJHJHKILJLJLKMKMKMKNLNLNLOMPNPNQOQOQORPRPRQSQTRUSTRTSUSVTVTVUWUWUXVXWYWYWYXZXZY[Z[Z\[\[]\^]^]_^_^`_a_a`bacadbdcdcedfefegfhgihihjikikjljmkmlnmonpopoqoqprprqsrtrutvuwuwvwvxvyxyxzy{z|{}|}|~}~}~LKLKLLLLMLMLMMNNOOONNNNNNNNNOOOOONONOOPOPOONONOOOOPOPOOOPPQQQPQQQPQPQPQPPOPOPOPOPOPOOOOOONONONONONOOOOOOOONNNNNNONONONNNNNOOPPPQPOONNNNNNN~~~}~|}{|{{z{zzxzxyxxwwvwuvuvuvttstrsqsqrqrppoqoonomnlmkmkljkijhhghghghfgefdeddcdccbcabab`a_`_`__^_]_]^\]\]\\[\[[ZZYZYZYYXYWYWYWYWXVWVWUVUVUUTTSTSTRTRSQRQRPRPQOQOPNPNOMOMOMNLMKMKMKMKLJLJKIKIKIKIIGIGIGHFHFHFGEGEFDGEGEFDFDECECECECDBDBDBDBDBCACABACAB@B@A@A?A?A?@?@?@>@>@>?=?=?=?=?=?=><><><=;=;=;=;=;=;<;<;<;;:;:;:<:;:;:;9;9;9;9;9;9:9:8:8:8:8tp~tp~tp~so~so~so~ro~rn~rn~qn~rn~qn~qn~qn~qn~qn~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~ol~pl~nj~nj~ol~pl~nj~nj~nj~nj~nj~nj~nj~nj~nj~nj~nj~nj~mi~mi~mi~mi~mi~ni~mi~nj~nj~nj~nj~nj~nj~ok~ni~nj~mj~mj~mj~nj~ok~nj~ok~ok~ok~ok~nj~nj~ok~ok~ok~ok~ok~ok~ok~ok~pl~pk~pk~pk~pk~pl~8697868696979797979797:8:8:8:8:8:8:8:8:8:8;9:8:8:8;9;9;9;9;9;9;9;9<9<:<:<:<:<:<:<:=:<:<:=;=;=;=;=;=;><><=;=;><><><><?=?=?=?=?=@>?=?=@>@>@>@>@>A?A?A?@>A?A?A?A>A?A>A?B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@CAB@B@CAB@C@CACACACACADADADADACADBDBDBDBDBDBDBEBECDBECECECECECECECECECFCFCFCFDFDFDFDFDFDFDFDFDFDFDGDGEGEFDGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEFDFDFDFDFDGDFDFDFDFDFDFDFDFDFDFCFDECECECECECECECECECECECEBDBDBDBDBDBDBDBDADADBDBDBDADACACACACACAB@B@B@B@B@B@B@B@A?B@A?A?A?B@B?A?B?B?A?A?A?A?A?A?A?A?A?A?A?A?A?A?A?@>@>@>@>@>@>?=?=?=?=?=?=?=?=?=?=><><><><=;><=;=;=<=;=;=;=;=;=;=;<:<:<:<9;9;9;9;9;9;9;9;9;9;9;9;9;9:8:8;9:8:8:8:8:8:8:8:8:8979797979697979796qm~qm~pl~pl~pl~qm~ok~ok~ok~ok~ok~ok~ok~nj~nj~ni~ni~nj~nj~nj~mi~mi~nj~nj~nj~mi~mi~nj~nj~nj~nj~nj~nj~mi~mi~mi~mj~mj~mj~li~mj~mj~li~mi~li~li~li~ni~ni~li~mj~mj~mj~lh~li~li~mj~mj~mj~nj~lh~nj~li~nj~nj~lh~mi~nj~mi~nj~mi~mi~lh~nj~nj~nj~nj~nj~mj~mj~mi~nj~nj~nj~ok~ok~nk~pm~ol~pm~pm~qm~qm~qm~qm~qm~qm~qm~rn~rn~rn~rn~rn~rn~so~so~tp~tp~tp~tp~tp~uq~ur~ur~vr~vr~vr~;:;:;:;:<:<:<;<;=;=;=;><><><><><?=?=?>?>?>@>@?@?A?A?A@A@A@A@B@B@B@CACBDBDBDBDBECECECECFDFDGEGEGEGEHFHFIGIGIGJHJHKIJIKIKIKILJLKMKNLNLNLNLOMOMPNPOQOQOQPRPSQSQSRSRTRUSUSUTVTWUWUXVXVXWYWZXZXZX[Z[Z[Z\[]\]\]\^]_]_^`^a_a`a`bacadbededfegfgfhgihihjikjkjlkmlnlnlonpopoporprqrqsqtstsutvtwvxwxwyxyx{y{y{z|{~}~}~LKLLLLMMMMLLMLNMNNNMNMNNOOPOPPPPPOPPPOQPQPPPPPPPPPPPPPPPPPQQPPQQQQQQRRSRSSSRRRRRRRRQRQQPQPQQQQPPPPPPPPPOPPPOPPPPPPPPPPPPPPPOOOOOPPOOPPQQQQQQQQPPOOPPOONNNNMLML~~~}}|}||z{zzyzyyxyxywxvvuwuvuvtuststrtrsqrpqopnomomnmlkkjkijiihhghghggffefdfddcdccbbabab`a``__^_]_]^]^]^]^\][\[\[[Z[YZYZYZYZXYWXVXVWUWUVUUTUTUSTRTRSQRQRPRPQPQPQOPNPNPNOMOMNLNLMKLJLJLJLJKIKIJHJHJHIGHFHFHFHFGEGEGEGEGEFDFDFDECECECECECDBDBDBCACACABABAB@B@A@A?A?A?@?@>@>?>?>@=?=?=>=?=><><><><><><=;=;=;=;=;=;<:<:<;<:<:<:<:;9;9;9;9;9;9;9;9:8:8:8:8:8:8tp~tp~tp~so~so~so~rn~qn~rn~rn~so~qn~rn~qn~qn~qn~qn~pl~qn~pl~pl~pl~pl~pl~pl~ol~pl~pl~pl~ok~pl~pl~pl~nj~pk~ok~ok~ok~ok~ol~ok~nj~nj~nj~ol~nj~nj~nj~nj~nj~ok~ok~ok~ol~ok~ok~ok~ol~ol~ok~ok~pl~ok~pl~ok~ok~pl~pl~pl~pl~pl~ok~pl~pl~pl~qm~qm~pl~8696969686979697969797:8:8:8:8:8:8:8:8:8:8;9;9;9;9;9;9;9;9;9;9<9<9<:<:<:<:<:<:<:=:=;=;<:=;=;=;=;=;><=;=;><><><><><><><?=?=?=><?=@>@>@>@>@>A?A?A?A?A?A?A?A?B?B?B?B@B?B@B@B?B@B@B@B@B@B@B@B@B@CACACACACACACACACACACACADBCACADBDADADADBDBDBDBDBDBDBEBEBEBEBEBECECECECECECECECFDFDFDFDFDFDFDFDFDFDGEFDGEGEGEGEGEGEGEGEHEGEGEHEGEGEHEHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEFDFDFDFDFDFDFDECFCECECECECECEBECECECECDBDBDBDBDBDBDBDBDBDBDADADACACACACACAB@B@CACACAB@B@B@CAB@B@B@B@B@B@B@B@B@B?B@B?A?A?A?A?A?A?A?A?A?A?A?@>@>@>@>@>?=?=?=?=?=?=?=?=?=><><><?=><><><=;=;=;=;=;=:=;=:=:=:<:<:<:<:<:<:<:;9;9<9;9;9;9;9;9;9:8;9:8:8:8:8:8:8:8:8:8:897:7:79797979797qm~rn~rn~qm~qm~qm~qm~pl~ok~ok~ok~ok~ok~ok~ok~nj~nj~nj~nj~nj~nj~nj~nj~nj~nj~nj~nj~nj~ok~nj~nj~nj~ok~ok~ok~nj~nj~nj~nj~ok~nk~mj~nk~nk~nk~nk~mj~nk~nk~nk~nk~mj~nk~nj~mj~mj~nk~nk~nk~nk~nk~nj~nj~nj~nj~nj~nj~nj~nk~nj~nk~nk~nk~nk~nj~nj~nj~ok~nk~nk~nk~ok~ol~qm~qm~qm~qm~qm~qm~qm~rn~qm~rn~rn~rn~rn~tp~so~tp~tp~tp~tp~tp~tp~ur~tp~ur~ur~ur~ur~vr~;:;:;:;:<:<;<;<;=;=;=;=;=;><><><>=>=>=?=?>?>@>@?@?A?@?A?B@A@B@B@BACACACADBDBDBDBECECECECFDFDFDGEGEHFHFHFHGIGIGIGJHJHKIKIKJLJLJMKMKNLNLNLNLOMOMOMPNPNQPQOQPRPSQSRTRTRUSUSVTVTWUWVXVXVYWYWYXYXZY[Z\[\[\[][]\^]^]_^`^`_`_a`b`bacbdcdcedfdfegfhgigihjikiljljmknmnmonpopoqpqprprqsqtststsvtvuwvywzxzyzy{y|z}{}|~|~}~LKLKLKLKMLMMNMNNNNNNNNNNOOPOPOOOPOPPQQRQQQRRRQRQRQQQQQQQRRRRRRRRRRRRRRRRRRRRTSTSTSUTTSTSTTSSSSSRSRSRSRSRSRSRSRQQQQRQQPQPRQQQQQRRQQRRQQQQQQQQQPRRRRSSRRQQQPQQQQQQPPOOOONNMMLLLK~~~}}|}{|z|z{yzyzxyxxwxvywwvxwwuvuuttssqrqqpqoponmnlmklkkjkjjhihhghghfgefefdeddcdbcbcabaa`a`a_`__^`^_]^\^\^\][][[Z[Z[Y[YZXYXYXXWXWXWWVWUWUVTUSUSTSTRTRSQRQQPRPQOPNPNPNOMNLNLNLMKMKLKMKLJLJKIKIJHJHJHIGIGIGHFHFHFHFGEGEFDFDFDECECECECECDBDBDBCACACACACACACAB@B@A@A@A?A?@>@>@>@>@>?=?=>=?=?=?=><><><><><=;><=;=;=;=;<;<:<;<;<;<:<:<:;:;:;9;9;9;9;9;9;9:9:8:8:8:8tp~tp~tp~tp~tp~so~rn~rn~so~sp~qn~qn~qn~rn~qn~qn~qn~qn~qn~qn~qn~qm~qm~qm~pl~pl~pl~pl~pl~pl~pl~qm~pl~pl~pl~pl~pl~pl~ok~ol~ok~ol~ol~ol~ok~qm~qm~pl~pl~pl~pl~qm~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~96qm~qm~97pl~9696879797979797:8:89797:8:8:8;8;8;8;8;9;9;9;9;9;9;9;9;9<9<9<:<:<:<:<:<:<:<:=;=;<:=;=;=;=;=;=;=;><><><><><><><><><?<?<><?=?=?=@>@>@=@=@>A?A?A?A?A?A?A?B?B?B@B@B@B@B@B@B@B@B@B@CACACACACACACACACACACADBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBEBEBEBECECECECECECECECFDECECECECFDFDFDFDFDGEGEGEGEGEGEGEGEGEGEGEGEHFHFHFHFHEHFHFHFHFHFHFHFIFHFHFHFHFHFIGIGIGIFIGIGIGIGIGIFIFIFIGIGIGIGIGIFIFIFIFIGIFIGIGIGHFIGHFHFHFHFHFHFHFHFHFGEHFHFHFHFHFHEGEGEGEHEGEGEGEGEGEGEGDGEGEGDFDGEFDFDFDFDFDFDECFCECECECECECDBDBDBDBDBDBDBDBDBDBDBCACACACACACACACACACACACACACACAB@CACAB@B@B@B@B@B@B@B@B@B@B?B?A?A?A?A?A?A?A?A?A?@>@>@>@>@>@>?=?=?=?=?=?=?=><><><><><><><>;=;=;=;=;=;=;=:=:=:=:=;<:<:<:<:<:<:<:<:;9;9;9;9;9;9;9;9;9;9;9;9:8:8:8:8:8:8:897979797979797rn~rn~qm~qm~qm~qm~pl~qm~pl~qm~ol~ol~ol~ol~ol~ol~pl~ok~ok~pl~pk~ok~ok~pl~pl~pl~ok~ok~ok~pl~pl~pl~pl~pl~ol~ok~ok~pl~pl~ol~ok~ol~ol~ol~ol~ok~ok~ok~ol~ok~ol~ok~ol~ok~ok~ok~ol~nk~nk~ok~pk~pk~ok~pk~pk~nk~ol~ol~pm~nk~ok~pk~pk~pm~ol~ol~qm~qm~qm~rn~rn~rn~rn~rn~rn~rn~rn~rn~tp~tp~tp~tp~tp~tp~tp~uq~uq~vr~vr~ur~ur~;:;9;:;:<:<:<:<:=;=;=;=;=;><><><><><?=>=?=?=?=?>?>@>@?@?A?A?A?B@A@BAB@CACACADBDBDBDBDBECECECECFDFDFDGEGFGFHFIGHGIGIHJHJHJHKIKILJLJLJMKMKMKMLNLOMOMOMPNPNPNQORPRPRQRQSRSRSRTSUSUSVTVTWUWVXVXWYWYXZYZY[Y[Z[Z\[]\]\^]^]_^_^`_`_a`a`babacbdcededfegegfhgigjikjljlkmlnmnmononqoqprpsqsrsrtsututvuwuwuxvywyx{y|{|{|{}|~}}}LKLKLKLLMLNMMMMMNMNMONONONOOOOOOPOPPQPQQQPQQQPRQRQRRSSSSSSSRSRSSSRSSTTTSTSTSTSSSTTTSTSUUUUVUUUVUVUUUUUUTUUTTTTTTTTTTSSSSSSSSSRSRSRSSSSSSSSSSTSTTTTTSSRSSTTUUUUUUTSRRRSRRQRQQQQPPOONNMMMLLLLLLLLLLKKKKK~~|~||{|{{zzy{yzyzyzxyxywxvwuvtvttssrsrrqqoononnmmllklkkikijijiihhggfgffeededececdbcbbab`a``_`_`^_^`^^]^\]\\[][\Z[Z[ZZYZYYXYXXWXWXVWUVTVTUSUSTRTRTRSQRQRPQOQOQOPNPNPNOMOMNLNLMKMKLKMKLJKIKIJHJHJHIGIHIGIGIGIGHFHFHFGEFDFDFDFDECECECECDBDBDBDBDBCACAB@B@B@CAB@A?A?B@A?A?A?@>@>@>?>?>?=?=?=?=>=><><><><><><><=;><=;=;=;<;<;<:<:<;<:<:;:;:;:;:;:;9;9;9;9:8:8;9:8:8:8tp~:8:8:8:8tp~tp~tp~sp~tp~sp~so~ro~rn~so~rn~rn~so~rn~rn~qn~qn~qm~qn~qn~qn~rn~rn~rn~qn~qm~qm~qn~qn~qm~qm~qm~qm~qm~qm~qm~qm~qm~qm~qm~qm~qm~qm~qm~pl~pl~qm~qm~qm~pl~rn~rn~9796869797978797979797979797:8:8:8:8:8:8:8;8:8;8;8;9;9;9;9;9;:;9<:<:<:<:<:<:<:<:<:=;=;=;=;=;=;=;=;=;=;=;><><><><><><?=><?=?=?=?<?=?=@=@=@=@=@>@>@>@>@>@>@>A?B@B@B@B@B@B@B@B@C@B@B@CACACACACACACACACACADBDBDBDBDBDBDBDBDBDBDBDBECDBDBEBECECECECEBECECECECECECECECFDFDFDFDFDFDFDFDFDGDFDGDFDGDGDGEGEGEGEGEGEGEHEHEHEHFHFHFHFHFIFIFIFIFIGIGIFIFIFIFJGJGIGIGIGIGJGIGJHJGJGJGJGJGJGJGJHJGJGJGJHJGJGJGJGJGJGJGJGJGIGIGJGJGJGJHJGIGIGIGIGIGIGIGHFIGIGIGIGIFHFIGHFHFHFHFHFHFHFHFHEHFHFGEGEGEGEGEGEGEGEGDGDGEGEFDFDFDFDFDFDFDECFDECECECECECEBEBECDBDBDBDBDBCADBDBDBDBDBDBDBDBCACACADBCACACACACACACACAB@B@B@C@C@B@B@B@B@B@B@A?A?A?A?A?A?A?A>@>@>@>@>@>@>@=?=?=?=?=?=><><><><><><><=;=;=;=;=;>;=;=;=;=:=:=:<:<:<:<:<:<:<:<:<:<:;9<:<:<:<:;9;9:8;9;9;9:8:8:8:8:8:898:8:7979797rn~rn~97rn~rn~qm~rn~rn~rn~qm~qm~qm~qm~qm~qm~qm~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~pl~qm~qm~qm~qm~qm~pm~ol~qm~qm~qm~qm~qm~qm~qm~qm~qn~qn~qm~pm~qm~qm~qm~qn~pl~rn~qm~rn~rn~rn~rn~sp~tp~tp~tp~tp~tp~tp~tp~ur~ur~ur~ur~;9vr~;9;9;:;:;:<:<:<:<:=;=;=;=;=;=;=<><><><><>=>=?=?=?>?>?>@>@>@?@?@?A?A?B@B@B@B@CACACADBDBDBDBDBECECFDFDFDFDGEFEGFHFHFHFHGIGIHJHJHJIKIKIKILJLJMKMKNLNLNMNMOMPNPNPNQOPOQPQPRQRQSQSRSRTSUSVTVTVUWVWVWVXWYWYXYXZYZY[Z\[][][^\^\_]_]_^`_a`a_b`cabadcdcdcedfegegfhfigihjiljlkmkmlnlnmonpnqorprqsrtstsutvtvtwvxvxwyxyx{y{z}{}|~}}~KKLKLKMLMMMMNMNNONONONONPOPOPOQPQPQPQPQQQQRRSSSSSRSRSRSRSSTTTTTTTTTTTTUTTTUUUUUUUUUUUUVUVVVVVVWWXWWWWWWWWWWVWWWWVVVVUUVVVVVVVVUUUUUTUTUTVUUTTTUUUUVUVVVVUTTSUTUUWWWWWWVVVVUUTTSSSSSRQQQPPOOONNONNNNNNNMMMMLLKKKJ~~~~}~}}|}{|{{zzyzy{yzxxwxwwvvuuttstrrqrpqopoonomnlmlmlkjjikjjiigighggfgefeedeceddccbcbcab`a_a_`_`^`^_]^]]\]\]\][\[\Z[ZZYZXYXYWXWXWXUWUVUVTUSUSUSTRTRSRRPRPQPQOQOPNPNPNOMOMNLMKMKMKMKMKLJKIKIKIKIJHJHJHIGIGIGIGHFGEGEGEGEFDFDFDFDECECECDBDBDBDBDBCACACACAB@B@B@B@B@B@A?A?A?A?@>?>@??>?>?>?>?=>=?=><><><><><><><=;=;=;=;=;=;<;=;=;<:<;<:<:<:;:;:;:;:;9;9;9;9;9;9;9;9;9;9:8:8:8:8:8:8tp~tp~tp~tp~tp~so~tp~so~so~so~so~so~so~rn~rn~rn~so~so~so~so~so~so~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~979797979797979797979797979797989897:8:8:8:8:8:8:8:8:8:8;9;9;9;9;9;9;9<:<:<:<:<:<:<:=;=;=;=;=;=;=;=;=;=;=;><><><><><><><><><><?=?=?=?=?=?=?=?=?=?=@>@>@>@>@>@>@>A?@>A?A?A?A?B@B@C@C@CACAB@CACACACACACACADBDBDBDBDBDBDBDBDBDBECECECDBDBEBECECECECECECECFDECECFDFCFDFDECFDFDFDFDFDFDFDFDFDGDFDGDGDGDGEGEGEGEHEHEGEGEHFHFHEHFHEIFIFIGIGIFIGIFIGJGIGJGIGJGJGJGJGJHJHJHJGJHJHJHJGJHJHKHJHKIKHKIKHKIKHKIKHKIJHKIKIKIKIKIKIKHJHJHJHKHJHJHJHJHKHJHJHJHJHJHJHJHJHJGJHJHJHJGJGJGIGIGIGIGIGIGIFIFIFIFIFIGHFHFHFHFHEHEHFHEHEGEGEHEGEGEGEGEGEGEGEGDFDFDFDFDFDFDFDFDFCECECEBEBECEBEBDBEBDBDBDBDBECDBDBDBDBDBECDBDBDBDBCACADBCACACACACACACAB@B@C@C@B@B@B@B@B@A?A?B@A?A?A?A?@>@>@>@>@>@>@>?=?=?=?=?=><><><><><><><><><><><><><=;=;=;=;=;=;=;=;=;=:<:=;<:<:<:<:<:<:<:<:;9;9;9;9;9;9;9:8:8:8:8:8:8:8:89797:7979797rn~97rn~rn~rn~rn~rn~so~so~rn~qn~qm~qn~qm~qm~rn~qn~qn~qn~qn~qn~qn~qn~qn~rn~qm~qn~pl~pl~pl~rn~qm~rn~qm~qm~rn~rn~qn~qn~pl~qn~rn~qm~rn~rn~qm~rn~qm~rn~rn~rn~rn~qm~rn~rn~rn~qm~qm~rn~rn~rn~rn~qn~rn~rn~rn~rn~rn~rn~tp~tp~sp~tp~tp~tp~tp~tp~uq~uq~ur~vr~ur~;9;9;:;:;:<:<:<:<:<:<:<:=;=;=;=;=;=;><><>=><>=?=>=?>?>?>?>@>@>@?@?A?A?A?B@B@B@B@CACACACACADBDBDBECECECECFDFDFDGEGEGEHFHFHFIGIGJHJHKIKIKIKIKILJLJLJMKMLNLNLOMOMONPNPOPOQPQPQPRQSQSQTRTRTSUTVTVTVTVUWVXWXWYWYXZY[Y[Y[Z\[][]\]\^]^]_^_^`^`_a`babacacbdcdcfdfefegfhfhgihjikikjlkmlnlomompnporprqsrsrtsutvuwvwvxvxwyxzy{y{z|{}|~}~}~KJKKLLMLMLNMNMNMONONOOPOPOQPQPQPQPQPRQRRRQRQSRSRSSTTUTTSUTTTTTUUVVVVVVVVVVVVWVVVWVWWWWWWXXWWXXWWXWXXYXYYYYYYYYXXXXXXXXXXXXXXWWWWXWWWWWWWWWVVWWVVVUUUVVWWXWXWXWVVWVWVXWYYZYYYXXWWVVUUUUUUTTRRRRQPPPPPPPOOOOOOOOOONNMMLLKKKJ~~~~}}|}{|{{y{zzxzxyxxwwvwuvtustrsqrqrpqopnonomnlnllklkkjjijhigighfgegfgefdeddcdccbcbcbb`b`a``_a`_^_^_^^]]\]\][\[\[[YZYYXZXYWXWWVWUWUVTVTUTUSUSTRSQSQSQRPQOQOPOPNPNPNOMOMNLNLNLMKMKLJLJLJLJKIKIJHJHJHIGIGIGHFHFHFHFGEGEGEGEFDFDECECECECDBDBDBDBCACACACACAB@B@B@B@B@A?B@A?A?A?A?@>@??>?>?>?>?>?=?=?=>=><><><><><><><=;=;=;><=;=;=;=;<;<;<:<:<:;:;:;:;:;:<:;9;9;9;9;9;9;9:9;9:8:8:8:8:8:8:8:8:8:8tp~tp~tp~tp~tp~so~tp~so~so~tp~tp~so~:897:8so~tp~so~97979797979797979797989797979898:898:8:897:8:8:8:8:8:8:8:8;9:8;9;9:8:9;9;9;9;:;9;9<:<:<:<:<:<:<:<:<:=;=;=;=;=;=;=;><=;><=;><><><><><><><><?=?=?=?=?=?=?=?=@>?=@>@>@>@>@>A?A?@>A?A?A?A?A?A?A?B@B?B?C@CACACACACACACACADBDBDBDBDBECECECECECECECFCECECECECECECECFCFCFCFDFDFDFDFDFDGEGDFDGEGEGEFDFDGDGEGEGDGDGDGEHEGEHEHEHEHEHEHFHFHFHFHFHFIGIGIGIGIGIGIGIGJHJHJHJHJHJHJHJHJHKHKIKIKIJHKIKIKIKIKHKIKIKIKIKIKIKIKIKIKIKIKIKIKILJLJLJKIKILJLILJLJKIKILIKIKIKIKIKIKIKIKIKIKIKIKIKILIKIKIKIKIKIKIKHKIJHJHJHJHKHJHJHJGJGJGIGIGJGJGJGIFIGIGIGHFHFHFHFHFHFHFHEHEHFGEGEHEGEGEGEGEGEGEFDFDFDFDFDFDFDECFCFCFCFCECECECECECECECECECECECECDBECDBECDBDBDBDBDBDBDBDACACACACACACACACACAC@B@B@B@B@B@A?B@B@A?A?A?A?A?@>@>@>@>@>@>@>@=?=?=?=?=?=><?=><?=><><><><><><><=;><=;=;=;=;=;=;=;=;=;<:=;<;<:=:<:<:<:<:;9;9;9;9;9;9:8:9:8:8:8:8:8:8:897:7979797:8so~97so~so~so~so~so~so~rn~rn~so~so~so~so~rn~rn~so~qn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~so~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~rn~sp~rn~rn~rn~rn~rn~rn~rn~tp~sp~rn~tp~tp~tp~tp~tp~ur~ur~ur~ur~vr~vr~;9;:;:<:<:<:<:<:<:=;<;=;=;=;=;=<=<><><><>=>=>=>=>=?=?=?>@>@>@>@>@?A?A?A?A?A?B@B@B@B@CACACADBDBDBDBDCECEDFDFDFDFEGEGFGFGFHFHFIGIGIGIGJHJHKIKIKILJLJMKMKMKMKNLNLOMOMPNPNPOQPQPRQRQRQSQTRTRTSUSUTVTWUWUWUWVXWYXYXYXZY[Z[Z\[\[]\^]^\_]_^_^`_a`a`b`bacbcbdbdcedfegegehfigihjijikjljlkmknmompnpoporqsrsrtsutvuvuwvxwyxyxzyzy|z}{}|}}~~KKLKLLMLMLNMNMONONOOPOPOPOQPQPQQRQRQRQRQSRTSSSSSSSSSTTTTUTVUUUUUVUVUWVWWXWWWWWWWXWXWXXXXXXXXXXYYYYXXXXYYYYZZZZ[[[[[ZZZZZYYZZZZZYYYXXXXXXXXXXXXXXYXXXXXWWWWXWYXYXYXYYXXXWXWZYZZ[[[[ZZYYWWVVWWWVUUTTSSRRRRQQQQQPPPQQQQPQOOOOOOLMKKKKKKKKKK~~~}~||{|{|zzyzxyxxwxvwuwuvtustrsrsrrpqoqopnnmnmmlmllkkjkjjiihighfhfhffefdedecdcdbdbcacab`a`a_`_`^_^^]_]^\^\][\[\Z\Z[YZXYWYWXWWVXVXVWUVTUTUSUSTRTRSQRQRPRPQOPOPNPNOMOMOMNLNLNLMKMKMKLJKIKIKIKIJHJHIHIGIGHGIGHFHFHFGEGEGEGEGEFDFDFDECECECDBDBDBDBCACACACACAB@B@B@B@A?A?B@A?A?A?A?A?@?@?@??>?>?>?=?=?=?=>=><><><><><><><><=;=;=;=;=;=;=;<;<:;:<;;:;:;:;:;:;:;:;9;:;9;9;9;9;9;9;9;9;9;9:8:8:8;9:8;9:8:8:8:8:8:8:8:8:8:8:8:8:8:8:8:8:8:8:8:8:898:8:8:8:8:8:8:8:8:8:8:8:8:8:8:8;9;9;9;9;9;9;9;9;9;9;9;9;9;9<:<:<:<:<:<:<:<:=;=;=;=;=;=;=;=;><>;><>;><><><><><><><><?=?=?=?=?=?=?=@>@=@>@>@>@>@>A?A?A?A?A?A?A?A?B@B@B?B@B@B@B@B@B@B@B@CADBCADBDBDBDBECDBDBECECECFDECECFDFCECECFDFDFDFDFDFCFDGDGDGEGEGEGEGEGEGEGEGEGEGEGEHEGEHEHEHEHEHEHFHEHEHFHFHEHFHFHFHFIGIGIGIGIGIGIGIGJGJGJGJGJGJHJHJHJHKHKHKIKHKIKIKIKIKIKILILIKILILILIKILILJLILJLJLJLJLJMJLJLILJLJLJMJLJMJMJLJLJMJLJLJLJLJLJLJLJLJLJLJLJLJLJLILJLJLJLJLJLJLJLILILIKIKIKIKIKIKIKIKIKIJHJHKIKIKIKIJHJHJHJHJHJHJHJHJGJGJGIGIGIGIGIFIGIGHFHFHFHFHEHEHEGEHEHEGEGEGEGEGEGEGDGEGEFDFDFDFDFDGDFDFDFDFDFCFCECFDECECECECECECECECECECECECDBDBDBDBDBDBDBCACACACACACACAC@C@B@B@B@B@B@B@A?A?A?A?A?A?A?A?@>A>@>@>@>?=?=?=?=?=?=?=?=?=><><?=><><><><><><><=;=;=;=;=;=;=;=;=;<:=;=;<:<:<:<:<:<:;9;9;9;9;9;9;9:8:9:8:8:8:8:8:8:8:8:8:89797:8:8tp~tp~tp~tp~tp~tp~tp~tp~tp~tp~tp~so~sp~sp~sp~sp~sp~tp~sp~to~to~to~tp~tp~so~tp~tp~tp~sp~rn~sp~tp~tp~so~tp~tp~sp~rn~tp~tp~sp~sp~sp~sp~tp~tp~sp~tp~tp~tp~tp~tp~tp~tp~tp~tp~vr~uq~vr~vr~vr~vr~;9wt~vs~<:<:<:;:<:<:<:<:<;=;=;=;=;><><><><>=><><><>=>=?=?>?=?=?>@>@>A?A?@>A?A?A?B@B@B@B@B@B@CACACADBDBDBDBDCECECFDFEFEGEGEGEHFHFHFHGIGIHIGJHJHKIKIKILJLJLJMKMKMLNLNLNLOMPNPNQOQOQOQPRPRQSQSQTRTRUSUTUTVTVUWUWVWVXVXWYXYXZX[Z\Z\[\[]\]\^\_]_^`__^a_a`b`bacadbdbdcdcedfdgfgfhgihihjikiljlkmlnmomonpoqprprqtrtrusvtvuwvxwxwzx{y{y|{|{}|~}~}~KKKKKKLKLLMLMLMLNMNMONONOOOOOOPOPPQPRQRQRQSRSRSRSRTSUTTTUTUTUUUUUUVVVVWWWWWWWWWWXWYXYXYYYYYYYYYYZZZZZZZZZZZZ[[ZZZZZZ[[[[\\\\\\\\[[\\[[[[[[ZZZZZZZZZZZZZZYYZZZZ[ZYYYYYYZY[Z[[[[[[[ZZYYY[[\\]\]\]][[ZZYYYYXXVVUUTSSRSRSRRRRRSSSSRRQQQQPPONNNLLLLLLLLLKLKKKKKKJ~~~}~|}{|z{yzyyxywxvxvwvwuvtustrtrsrrpqpqopnomnmnllklkkjkikijhigighfgffefdeddcdcdcdbdbbabaa``_`__^`^`^_]^]^\][\Z\Z[YZXZXYXYWXWXWWVWUWUVTUSUSTRTRTRSQRPRPQPQPQOPNPNONONOMNLOMNLMKMKLJLJLJLJKIKIJIJHIHIGIGHFIGHFHFHFHFHFGEGEGEGEFDFDFDFDECECECECDBDBCACACACAB@B@B@B@B@B@B@A?A?A?A?A?@>@>@>A?@>@>?>?>?>?=>=>=>=>=><><><><><=;=;><><=;=;=;=;=;<:<:<;<;<:<:<:<:;:;:;:<9<:;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;8:9:9:9:9;9;9;9:8;8;8:8:9:8:8:8:8:8:9:8:8:8:8:8:8:8:8;9;9;9;9;9;9;9;9;9;9;9<:;:<:<:<:<:<:<:<:<:<:=;=;=;=;=;=;><><=;><><><><><><><?=?=><?=?=?=?=?=?=@>?=@>@>@>@>@>A?A?A?A?A?A?A?A?A?B@B@A?B@B@B@B@B@B@C@C@CAB@CACACACADBDBECECECECECECECECFDFDFDFDFDFDGDFDGDGDGEGDGEGEGEGEGEGEGEGEGEGEGEHFGEHEHFHFHFHFIFIFHFHFHFIFHFIFHFIGIGIGIGIGIGIGIGIGJGIGIGJGJHJGJGJGJHKHJHKHKHKHKHKIKIKILILILJLILJLJLJLJLJMJMJMJMJMJMJLJMJMJMKMJMJMKMJMJMKMKMKMJNKNKMKNKMKMKNKMJMKMJMJMKNKNKNKMJMKMKMKMKMJMJMJMKMJMKMJMKMJMJMJMJMJLJLJMJLJLILJLILJKILILJLIKIKIKIKIKIKIKIKIKIKIJHJHJHJHJHJHJHJGIGJGJGJHIGIGIGIGIFIFHFIFHFHFHFHFHFHEGEGEGEGEGEGEGEGEGEGEGEGDGEGDGDGDFDGDFDFDFDFDFDFDFDECECECECECECECECECECDBECDBDBDBDBDBDBCACACACAC@B@B@B@B@B@B@B@B@B@B@B@A?A?A?A?A?A?@>@>@>@>?=?=?=?=?=?=?=?=?=?=?=?=?<?<?<><><><><><><><><><=;=;=;=;=;=;=:=:<:<:<:<:<:;9;9;9;9;9;9;9;9:8;9:8:8;9:8:8:8:8:8:8:8:8:8:8:8:8:8:8tp~:8tp~uq~uq~uq~uq~uq~uq~uq~uq~uq~uq~uq~uq~tp~uq~ur~uq~tp~uq~ur~tp~tp~tp~ur~ur~tp~sp~tp~tp~tp~tp~tp~tp~ur~ur~tp~tp~vr~ur~ur~ur~ur~ur~ur~vr~;9;9;9;9;9;9;:;:;:;:<:<:<:=;=;=;=;=;=;=<><><><><><><>=>=>=?=?=?>?=@>@>@>@>@>@>A?A?A?A?A?B@B@B@B@B@BACACACBDBDBDCDCECECEDFDFDFDGEGEGEHFHFIGIGIGIHJHJHJHJHKIKIKILJLJMKMKNLNLNLOMOMOMPNPOPOQOQPRPRQRQSRTRTRUSUTUSUTVUVUWVXVXWXWYXYXZYZY[Z\Z\[\[]\^\^]_^`_a`a`a`a`babacacbecededgefdgfhghgihjhjikjljlknlnlomonpoqprqsqtsututvuwvxvywyxzy{z{z|{}|~~}~LKLLLLMLMLMLNMNMONONONPOPOPPPPPPQQRQRRRRSRSRTSTSTSTSUTUTUUVUVUWVVVWWWWXWWWWWXXXXXXXXYYYY[[ZZ[[[[[[\\\\\\\\\\[\\]\\[[\\\\\\\\]]^^^^^^^^]]]]\\\\\\[[\\\[\\[[[[[[\\\\\\\[[[[[\[\\]]\\]\]\\[[[]\^]^]^^^^^^]]\\[[YYWWVVUUUTUTUTUUUUUTTSSSRRQQPPPOOONMMMMMMMMLMLLLLLLKLKKJ~~}~|}{}{|z{z{yzxyxxwwvwvvtvttrtrsqsqqpqopnonnmnlmkmkkjkjkijhjhihighfgegefdecedecdcdbcbbaa`b`a_`_`^_^_]^]^\]\][][\Z[Y[YZXZWYXXWWVWVWUVUVTUSUSTRTRSQSQSQRPRPQOQOPOPNPNOMOMOMNLNLMKMKMKLKLJLJKIKIKIJHJHIGIGIGIGHFHFHFHFHFHFGEGEFDFDFDECFDECECECECECDBDBCACACACACAB@B@B@B@A?A?A?A?A?A?A?A?@>A?@?@?@>@>@>@>?>?>?=?=?=>=><><><><><><><><><=;=;=;=;=;=;=;=;=;<:<;<;<:<:<:;:;:;:<:;:;:;9<:;9;:;9;9;9;9;9;9;9;:;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9<:;9;9<9<:<:<:<:<:<:<:<:<:=;<:<:=;<:=;=;=;=;=;><=;><><><><><><><?=?=?=?=?=?=@>@>@>@>@>@>@>@>@>@>A?A?A?A?A?A?A?A?A?A?A?B@B@B@B@B@B@B@B@CAB@CACACACACACADBDBDBDBDBECECECECECFDECFDFDFDFDGEGEGEGDGEGEGEGEHFGEHEHFGEHFHFHFHFGEHFHFHFIFHFIFIFIFIGIFIGIGIGIGIGJGJHIGIGJHJHIGJHJHJHJGJHJHKHJHKHJHKHKHKHKILILILILILILILILILILILJMJMJMJMJMJMJMJMJMJMKMKMKNKNKMKMKMKNKNKNLNKNKNKNKNLNKNKNLNKNLNLNKOLNKOLOLNLNKNLNKNKOLNKNLNLNKNLNKNLNKNKNKNKNKNLNKNKNKNKNKNKNKMKNKMJMJMKMJMJMJMJMJLJLILIMJLJLJLILILJKILILILIKILIKHKHKHKHJHJHJHKHJHJHIGJHIGIGJGJGIGIGIFIFIFHFHFHFHFHFHFHFHEHFGEHFGEGEGEGEHFHFGEGEGEGEGDGEGDFDFDFDFDFDFDFDFDFDECFDECECECECECECECDBDBDBCACACACACACACAB@CACACACAB@B@B@B@B@B@B@A?A?A?A>A?@>A?@>@>@>@>@>@>@>?=?=@>@>@=?=?=?=?=?=?=?=?=><?=><><><><=;><=;=;=;=;<;=;=;<:<:<:<:<:<:<:;9;9;9<:;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9:9:9;9:8;9;9;9:8;9;9:8;9;9;9;9;9;9;9;9;9vq~ur~ur~ur~ur~ur~ur~ur~ur~tp~vr~ur~ur~ur~ur~ur~vr~vr~vr~;9;9wt~;:;9;:;:;:;:<:;9<:<:<:<:<:=;=;<:=;=;=;=<=<=<=<=<><><><>=>=>=>=?=?=?>?=?=@>@>@>@>@>A?@>A?A?A?A?A@B@BACABACACACADBCBDCDCECECFDEDFDFDFDGEGEHFHFHFHFHFIGIGIHJHJIKIKIKILJLJMKMKMKMKNLNLOMOMPNPNPNPOPOQPRPSQSQSQTRTSUSUSUTVTVUVUWVXVXWXWYXZX[YZYZY[Z\Z\[\[]\^]_]_^`_`_a`bababacbdcdcecfdfdfehfhfhgihjhjikjljmlmlnlonpoporpsqtrtrusutvuwvxvxwyxzy{z|z|{}|~|}~KKLKLLLLMLNMNMNMNMNMNNOOPOPOQPQPQQRQRQRQSRSSSSSSTSTSUTUTVUVUUTVUWVWVWWXWXWXXXXXXYYXXYYYYZZ[ZZZZZ\\\\\\\\]]]]]]]]^]]]]]]]^^^^_^^^^^^^^^____``____^]^]^^^^]]^^^]^]^]]]]]]]^^^^^^]]]]^^]]^^^^_^^^]]^^^^^^`_``_`^^]]]\ZZZZYYXXXXWWWWVVWWWWVVUTTTTTSSQQQQPPONNNONNNNNNMMMMLMLLLLKLKKJ~~~}}|}{|{|z{yzxyxywxvwvvuuststrsrrqqpqppoononnlmllkljkikjjiihihhggfgegefefdedecdccbcacabaa`a_`_`^_^_]^]^\][][\Z\Z[YZX[YZXYWYWXVWUVUVTVTUTTSTRTRSQSQRPRPQPQPQOQOPNPNPNOMNLNLNLNLMKMKLKLJLJKIKIKIJHJHJHJHIGIGIGHFHFHFHFHFGEGEGEFDFDFDFDFDECECECECDBDBDBDBCACACACACAB@B@B@B@B@A?A?A?A?A?A?A?A?A?@>@>@>@>@>?>?=?>>=?>>=><><><><><><><><><=;><=;=;=;=;=;=;=;=;=;=;=;=;<;<;<;<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:;9;9;9<:<:;9;9;9<:<:<:<:<:<:<:<:<:<:<:<:<:<;<;<;<;<;=;=;=;=;=;=;=;=;><=;><><><><><><><?=><?=?=?=?=@>@>@>@>@>@>@>@>@>@>A?A?A?A?A?A?A?A?A?B?B@B?B@B@B@B@B@B@CACACACACACACADBDBCADBDBDBDBECDBECDBECECFDFDFDFDFDFDFDGDGDGEGEGEHEHFGEHFHFHFHFHFIFIGHFIFIGIFIGIGIGJGIGIFIGJGIGIGIGJHJHJHJHJHJHJHJHJHJHKHJHJHJHKHKIKIKIKHKHKILIKHLILILILILJLJLJLJMJMJMJMKLJLJMKMKMKNKNLMKNLNLNLOLNLOLNLOLOLOMOMOLOLOMOMOLOLOMOMOMOMOMPMOMOMOMOMOMOMOMOMOMOMPMOMOMOMOMOMOMOMOMOMOMOLPMPMOLOMOMOMOLOMOMOMNLOLNLNLNLNKNLNKNKMKMKMKMKMKMKMKMKMKMJMJMJMJMJMJLJMJLILJLILILILIKILIKIKIKIKIKHKHJHJHJHJHJHJHIGIGIGIGIGIGIGIFIFIFIFIFIFIFIFIFHFHFHFHFHFHFHFHFHFHFHFHEHEGEGEGDGDGEFDFDFDFDFDFDFDFDECECECECECECECECECDBDBDBDBDBCACACACACACACACACACAB@B@B@B@B@A?A?A?A?A?A?A?A?@>A>@>A?A?@>@>@>@>@>@>@>@>?=?=?=?=?=?=?<><><><><><=;><=;=;=;=;=;<;<;<:<:<:<:<:<:<:<:<:<:<:;:;9<9;:;:;:;:;:;:;:;9;9;9<:;9<:;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;9;:;9;9;9;9;9;9;9;9;9;9;:;:;:<:;9;:;:;9<:<:<:<:<:<:<:<:<:<:<:<:<:<:=;=;=;=;=;=;=;=<=<><=<><>=>=><?=?=?=?=?=?=@>@>@>@>@>@>@>A?A?A?A?A?A@B@B@B@BACACACBCADBDBDBECECECECFDFDFDFDFEGEGEGEHFHFHFIGIGIGJHJHJHJHKIKJLJLJMKMKMKNLNLNLOMOMPNPNPOPOQOQPRPRPSQSQTRUSUSUTVTVTVUVUWVWVXWYWYXZXZX[Y[Z\Z\Z\Z][^\^]^]_^`_`_a`a`bacacbdbecededfegegfhfihhgjhkikikjmkmlnmnmonpoqprpsqtrusutvuxvwvxvyxzy{z{z}{}|~}~~LKMLMLMMMLNMNMNNONONONOOPOPPQQQQRQSRRRRRSSSSSSTTUUUTVUVUVVVVWWWWXWWWXXYYXXYXYYZZZZZZZZZZZZ[Z[[[[\\\\^]^^^^^^_^__^^________^^__``__````````aaa`a```______________________^^^^__``___^`____^__``````____````aaaa``__^^]]\\\\\\[[ZZYYYYXXXXWWVVVVUUTTSSRRRRQQPPOOPOPOOOOOONNNNNMMMLLLLKLKKKKJJJJJ~~}~|}{|{{zzyzxyxywxvwuvtuttstrsqrqrpqopoonomnmmlmklkkikijijhihighghfgefdfdfdeddcdbcbcabaa`a`a_`^_]^]^]^\][\[\[[Z[Y[YZXYXYWXVXVWUWUVTUSUTTSTSSRSQSQRQRPRPRPQOQOPNOMOMOMNLNLNLMKMKMKMKLJLJLJLJKIKIKIJHJHIGIGIGIGHFHFHFGEGEGEGEGEFDFDFDFDFDECECECECDBDBDBDBDBCACACAB@B@B@B@B@A?B@B@A?A?A?A?A?A?A?A?A?@>A?@>@>@>?=?>?=?=?=?=><>=><><><><><><><><><><=;><=;=;=;=;=;=;=;=;=;=;=;=;=;=;<;<:<:<:<:<:<:<:<:<:<:<:<:<:=;<:<:<:<:<:<:<:<:<:<:<:<:<:<:<;=;=;=;=;=;<;=;=;=;=;=;=;=;><><><><><><><><><><?=?=?=?=?=?=?=?=@>@>@>@>@>A?A?A?A?A?A?A?A?A?A?B@B@A?A?A?B@B@B@B@B@B@B@CACACACACACADBDBDBDBDBDBECECECECECECECECECECFDFDGEGEGEGEGEHEHFHFHFHFHFHFIGIGIGIGHFIGJGIGJGIGIGIGJGJGJGJHJHJHJHJHJHJHJHJHKIKHKHKHKIKIKIKILIKIKIKIKHLILILILIKILILILJLJLJLJLJMJMJMJMJMJMKNKNKNKNKNKNKNLNLOLOLOLOLOMOMPMOMOMOMPMPMPMPMPMPNPNPMPMPNQNQNQNPNPNOMPNPNQNQNQNQOQNQNPNQNQNPNPNPNQNQOQNPNQNPNQNPNPNPNQNQNQNPNPMPNPMPMPMPNPMPMPMOMOMOMOMOLOLOLOLOLNLNLNLNLNLNLNKNLNKNKMKNKNKMJMKMJMJMKLJLJLJLJLJLILIKILIKIKIKIKIKHKIJHJHJHJHJHJHJHJHJGJGIGJHJGJGJGJGIGJGJGIFIGIGIGIGHFIGHFHFHFHFHFHFHFGEGEHEGEGEGEGEGDGEFDFDFDFDFDECECFDECECECECECDBECDBDBDBDBDBDBCACADADACAB@CAB@B@B@B@B@B@B@B@B@B@A?A?A?A?A?A?A?A?A?@>@>@>@>@>@>@>@>?=?=?=?=?=?=><><><><><><=<=<=<=;=<=;=;<:<:=;<:<:<:=;=;<:<:<:<:<:<:<:<:;:<:<:;:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:;:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:<:=;=;=;=;=;=;=;=;=;=;=;=;=;=<=<=<=<><=<><>=>=?=?=?=?=?=@>@>@>@>@>@>@>A?A?@>@>A?A?A?B@A@B@B@B@BABACACADBDBDBDBDBECECECECFDFDFDFDGEGEGEHFHFHFIGIGIGJHJHJHJHKIKIKILJLJLKMKMKNLNLNLNMOMONONPOPOQPRPQPRQSQSRTRTRUSUTUSVTVUWUWUXVXWXWYWZXZY[Y[Y[Z[Z\[]\^]^]_^_^`_`_a`a`babacadbededfefdgehfhgihihjhjikjljmkmlnmononpoqprqsqsrttutvtwuxvxwywyxzy{y|{}|~}~~KJKJKJMLMLMLNMNMONNNNNONOOOOPPPPQPQQQQRRRRSRSSSSTTTTUTUUVUWVWVWWWWXXXXXXZYYYYYZZZZZZ[Z[Z[[\\\\\\\\]]]]]]]]^^^^____`_````````aaaaaaa`a`bbbbbbbbbbaabbbbbbbbaaa`aaa`aabaaaaaaabaa```````aabbaaaaaaaaaaaabaccbbbbbaccbbccbbbb````^^^^____]][[ZZ[[ZZZYYYXXWWWWUUTTSSSSQQQQQQQPQPPPPPPOPOOOONNMNMMLMLMLLKLKKJJJJI~~~}}|}{|{{z{zzyzxywxvxvvtuststrrqrqqppopopnomnlnllklkkikijiihihhghfgfgefefeedecdcdbcacabab`a_a_a__^_]^\]\][\[\Z[Y[YZYYXYWXVXVWUWUVTVTVTUSTSTRTRSQSQRQRPRPRPQOQOPNPNOMOMOMOMNLNLMLMLLJLJLJLJLJKIKIJHJHJHJHIGIGHFHFHFHFGEGEGEGEGEFDFDFDFDFDECECECECDBECDBDBDBCADBDBCACACAB@CAB@B@B@B@B@A?A?A?A?A?A?A?A?@>@>@>@>@>?>?>@>?>?=?=?=?=?=>=>=><><><><><><><><><=;><><><=;=;><=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=:<:=;=:=:=;=;=;=;=;=;=;><><><><><><><><><><><><><><><?=?=?=?=?=?=@>?=?=?=?=@>@>@>@>@>@>@>@>A?A?A?A?A?A?A?A?A?B@B@B@B@B@B@B@B@B@CACACACADBCADBDBDBDBDBDBECECECECECECECECFDFDFDFDFDFDFDGDFDHEHEHEHFHFHFHFHFHFIGIGIGIGIGJGJGJGJGJGJHJGKHJGKHJHKIKHJHKIKIKIKIKILILILILILILIKILILILILILILILILIMJLJLJLJMKMJMJMJMJMKMKMKMKMKMKNKNLNLNLOLOLOLOLNLOMOMOMOMPMPMPMPMPNPMPNPNPNPNPNPNQOROROROQOQNQNROROQNQOROQOROROQOQORORORPRPROROROQOQOROROQOROROROQNROROROROROQOROROQNQNQOQOQNQOQNQNQNPNPNQNPMPMPNPMPMPMPMPMOMOMPMPMOLOLOLOLOLNLOLNLNLNKNKNKNKNKMKMKMJMJLJLJLJLJMJLILILILILILILIKIKHKHKHKHKIJHJHJHJHKIJHKHKHJHJHJGJHJGJGJGJGJGJHJGIGIGIFHFHFIGHFHFHFHFHFHFHFHEHEHEGEGEGEFDGEGEFDFDFDFDFDFDFDECECECECECDBDBDBDBDBDBDADACACACAB@B@B@CAB@B@B@B@B@B@B@B@B@B@A?B@A?A?A?A?A?A?A?A?@>@>@>@>@>@>?=?=?=?=?=?=><?=><><><><><><=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;<;=;<;<;<;<;<;=:<:<:<:=;<:<:<:=;=;<:<:<:<:<;<;<:=;=;<:<;<:=;=;=;=;=;<:=;=;=;=;=;=;=;=;=;=;=;=;=<=<=;=;=;=;=<><><><><>=>=>=>=>=?=?=?=?=@>@>@>@>@>A?@>@>A?A?A?A@A?B@A@A@B@B@B@B@BACACACACADBDBDBECDBECECECEDFDFDFEGEGEHFGEHFHFIGIGIGJHJHJHKIKIKJLJLJMKMKMKMLMLNLNLNMONONONPOPOQORPRQRQSQSRSRSRTSUTUTVTVUVUWUWVXWXWZXZXZY[Y\Z\Z\[\[][]\^]_^_^`^`_a`a`b`cacadcdcfdfdfegfgfhghgihjijhljljlklkmlompnporprqsqsrtsutvuwvxvxvyxyxzy{z|z|{~}~~KJKJLKMLMLLLMLNMNNONPOPOPOPOOOPPQQQQQQQQRRRRSSSSTTUTUTUUVVWVWVWWXXYXYYZZZZZZZZ[[[[[[\[\\\\]]]]]]^^^^____^^_^__`_```_`aaaaabbbbbbbbcbcbccccbbddddddddddccccccccccccbbbbbbbbbbccccddcccbcbccbbccccddccddccddccccbbcbddccddeededdcccca`````bb``__]]\\\\[[[[ZZYYXXWWVVUUTTTSSSSSSRRRRQRQRQQQPPPPOOOONNNNNMNMMLMLLKKKKJJJJIJI~~}}|}|}||z{zzxywywxvvuvuuttstrrqrqqpqppnonomomnlmllkljkijijiihhggfgffegefdfdecdcdccbbab`b`a`a_`^`^_]^\]\][\[\Z[ZZYZXZXYWYWXVWUWUVUVTUSUSTRTRTRTRSQSQRPSQQOQPQOPNPNOMOMOMNLNLNLMLMKMKLJMKLJLJLJKIKIJHJHJHJHIGIGIGIGHFHFGEGEGEGEGEGEFEFDFDFDECECECECECDBDBECDBDBDBDBDBDBCACAB@CAB@B@B@B@B@A?A?A?A?A?A?A?@>@>@>@>@>@>@>@>@>?>?>?>?=?=?=>=?=?=><><>=><><><><><><><><><><><><><><><=;=;=;=;=;=;=;=<=<=<=;=<=<=<=;=;=;=;=<><=;><><><><><><><><><><><><><><><?=><?=?=?=?=@>?=?=@>?=@>@>@>@>@>@>@>@>@>@>@>A?A?A?A?A?A?A?A?A?B@B@B@B@CACACACACACACACADBDBDBDBDBECDBECECECECECECECFDFDFDFDFDFDFDGDGEFDGDGDGDHEHEIGIGIGIGIGIGIGIFIGJGIGJGJHJHJHKHKHKHKHKIKIKIKIKIKIKIKIKILJLJLJLILILILJMJLJMJLJLJLJMJMJMJLJMJMJMJMKMKMKNKNKMKNKNKNKNLNLOLNLOLOLNLOMOMOMOMOMOMOMOMPNPNPNQNPNPNPNQOQOQOQOQOQOQORORORORORPRORPRPRPRPSPSPRPSQSPSQSPSPSPSPSPSPSPRPSPSPSPSPSPRPRPRPRPRPSPSPSPSPSPSPSPRPRPROSPRORPRORPROROROROROQOROQOROQOQOQNQNPNPNPNQNQNPNPMOMPMPMPMPMOMOMOMOMOLOLOLOLOLNLNLNLNKMKMKMJMKMKMJMJLJLILJLILILILILILILILILIKIKIKIKIKIKIKIKIKHKHJHKHKHJHKHKHJGJGJGJGJGIGIGIGIGIGIGIGIGIGHFHFHEHEHEHEGEGEGEGEGEGDGDFDFDFDFDFDFDFDFDECECECECDBDBDBDBDBCACADBCACACACACACACACACAB@B@B@B@B@B@B@B@B@A?A?A?A?A?A?A?A?A?A??=?=?=?=?=?=?=?=?=?=><?=?=><><>=><><><=<=<=<=;=;=;><><=;=;=;=;=;=;=;=;=<=<=;=;=;<;=;<;<;=;=;<;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=;=<=;=;><=<=<><><><><><><><>=>=><>=>=>=>=>=?=?=?=?=@>?=?=@>@>@>@>A?A?A?A?A?A@A@A@B@B@B@B@BABACACBDBCACADBDBDBDBECECECECFDFDFDGEGEGEGEGEHFHFIGIGIGJHJHJHJIKIKILJLJLJMKMKMKNLNLNLNMNMOMONONPOPOQORPRPSQSQSRSRTRTSUTUTUTVUWUWUWVXWYWYXZXZY[Y\Z\Z\[][^\]\^]^]_^`^`_a_babacacbdbdcecfdgegfgfgfigjhjhkikiljlklknlnmonqoqorprqtstsutvtxvwvywywzxzy{z|{}|~|~}~KJKJLKMLMLNMNMNMONONOOPOQPQPQPQPQPQPRQRRRRSRTSTSUTUUUUVVVVWWWWXXXXYYYYZZZZ[[[[\[\[\\]]]]]]^]^^_^_^__`_````aaa`aabababbbbbbbbccccccddddddccddeeeeeeefffefeeefefeeeeeeededdddddcdddddeffeeddededddeeededfeeeddeedddcccccddeefffffeeeeeddccbbddddcc``____]]\\[[[[[[ZZXXWWVVUUUUUTTTSSSSSSSRRRRRQQQPQPPPPOOOONONNMMMMLMLLKKKKJKJJIJI~~}~|~|}{|z{zyxywxvwvvuututtstrsqrprpqopoonomnmmlmllkkjkijijhihhgighfgegefeececdcdbcbbabab`a_`__^_]_]^\]\\[\Z[Z[ZZYZXYXYWXVXVWVWUWUVTUSUSUSUSTRTRSQSQRPRPQPQOQOQOPNPNOMOMOMOMNLNLNLMKMKLKLKLJLJLJKIJIJHJHJHIGIGIGIGIGHFHFHFGEGEGEGEGEFDFDECECECECECECECECECDCDBDBDBDBDBCACACACACAB@B@B@B@B@B@A?A?A?A?A?A?A?A?A?A?@>@>@>@>?>@>?>?>@>?=?=?=?=?=>=?=>=>=>=>=>=><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><?=><?=?=?=?=><><><?=?=?=?=?=?=?=@>@>@>@>@>@>@>@>@>@>@>@>@>A?A?A?A?A?B@B@B@B@B@B@CACACACACACACACACADBDBDBDBDBDBDBDBECECECDBECECECECECECECFDFDFDFDFDFDGEGEHFGEHFHFHFHFHFHFIFIGJHIGIGJHJGJHJHKHKHKHJHKIKIKIKIKILJLJLJLJLILILJLJLJLJMJLJMJMJMJMJMKMJMKNKMJMKMKNKNLNLNLNLNKNLNLOLOLOLOLOLOMOLOMOMOMPMPMPNPMPNPNPNPNPMPNPNQNPNQOQOQOQOROQORORPRORPRPRORPRPRPSQSQSQSPSPSQSQSQSQSQSQSQSQSQSQTRSQTQTQTRTQTRTQTRTRTRTRTQTRSQSQTQSQTQTRSQTQSQSQSQSQSQSQSQSQSQSQSQSQSQSQSQSPSPSPRPRPSPRPRPQORPRPRPQORPQOQOQNQNQNQNQNPNPNPMPMPNOMOMPMPMOMOLOMOLOLOLOLOLNKNKNKMKMKMKMKMKMKMJMJMJMJMJMJMJMJLJLJLJLJLILILIKILJKIKIKIKIKIKIKIKIKHKHJGKHJHJHJGJGIGJHJGJHIGIGIGHFIFIFIFHFHFHEHEGEHEGEGEFDFDFDFDFDFDFDFDFDFDFDFDECECECDBDBDBDBDBDBDBDBCACADBCACACAB@B@B@B@B@B@B@B@B@B@B@B@B@A?A?A?A?A?A?@>@>@>?=@>@>?=?=?=?=?=?=?=?=?=><>=>=>=>=><><><><><><><><><><><><><><><><><=;><><=<=<=;=;=<=<=<=;=<=<=<=<=;=<=;=<><><><><=<><=<><=<=<><><><><><><><><><>=>=>=>=>=>=>=>=>=?=?=?=?>@>@>@>@>@>@>@>@>@>@>A?A?@?A?A@A@B@B@BABACACACACACACADBDBDBDBECDBECECECFDFDFDFDFDGEGEGEGEGEIGHFIGIGIGJHJHJHJHKIKJKJLJLJLJMKMLNLNLNMNMOMOMPNPNPNPOQPQPRPSQSRSRTRTRTSUSUTUTVUWVWVXVXVYWYXYXZY[Y[Y[Z\Z\[]\^\^]_^_^`^`_a_a`bacacbdbecedfefegfhghgigjhjijikilklkmlmlnmompnqoqprqsrtstsvuvuwvyxyxzyzy{y|{}|}|~}}~KJLKLKLKMLNMNMONONONONPOPOQPQQQPRQRQRQRRRRRRTSTTTTUUUUUUVVVVWWWWXXYYYYZZZZ[Z\[\\\\\\^]]]_^_^____````````aababacbbbbbcbccccdcccccddddeeefffffffffgghhhhhihhhhhhgghhhhghggggggffffeeffffeeggghgggggggfgfgfgfggffffffffffffffffefeefffffeffffeeeedeffffddbbaa___^]]\\[[[[ZZYYWWWWWWWWVVVVUUUUUTTSTTSSSRRRRQRQRQQPPOPOOOONNMNMMLMLLKKJKJKJJIJIIH~}~|}{|{{zzyzxyxywwvwvwvvtutussrsrrqrpqopoomonnlmllkljkikijhihighfgffefefdececdcdcdbcbb`a`a_`__^_^^]^]^\\[\[[Z[Z[YZXYWXWXWWVWUWUWUVTVTUSUSTRTRTRSQSQSQRPRPQPQOQOPNPNPNOMOMOMOMNLNLMKMKMKLJLJLJKIKIJIJIJIJHJHJHIGIGIGHFHFHFHFGEGEGEGEGEGEFDFDFDFDECECECECECECECECDBDBDBDBDBCACADBCACACAB@B@B@A?A?A?A?A?B@A?A?A?A?@>A?@>@>A?@>A?@>@>@>@>@>@>@>@>?>?=@>?=?=?=?=?=?=?=?=>=?=><?=?=?=?=?=?=?=?=?=?=?=><?=?=?=?=?=?=?=?=?=?=?=?=?=@>?=?=?=@>@>?=@>@>@>@>@>@>A?@>A?A?A?A?A?A?A?A?A?A?B@B@B@CACACACACACACADBCADBDBDBDBDBDBDBDBDBECECECECECECECECFDFDECECECFDFDFDFDGDGEGEGEGEHEHEHEHFHFHFHFHFIGIGIGIGIGJHKHKHKHKHKHKHKHKILJKIKILJLJLJLJLJLJLJMKMJMKMJMKMKMKNLNLNLNLNKNLOLOLNLNLNLNLOLOLOMOLOLOMPMPMOMPMOMPMPMPMPMPMPNPNPNPNPNPNQNQNQNPNQOQORPRPRORPRPROSPSPSPSQSPSQTQSQSQSQSQTQSQSQTRTRTRTQTRTRTRTRTRTRURURURURURUSUSURURURUSUSURURUSVSUSUSURUSUSUSUSURUSURURUSURURURURURTRURURURUSTRURTRTRTQTRTRSQSQSQTQSQSQSQSQSQSQSPRPRPRPRORPQORPQOQNQOQNQNQNPNPNPNPMPMQNPMPMPMPMOMOMOLOLOMOLNLNLNLNKNKMKNKMKMKMKMKMKMKMKMJMJMJMJMKLJLJLJLJLJLJLJLJLJLJLJLILILILIKHKHKHKHJHJHJHJHIGJHJHIGJHJGIGIGHFHFIFHFHFHFHEGEGEGEGEGEGEGEGEGEFDFDFDFDFDFDECECECECECDBDBDBECDBECDBDBDBDBCACACACACACACACACAB@B@B@B@B@B@B@B@A?A?A?A?@>@>@>@>@>@>@>@>@>@>?=?=?=?=?=?=?>?=?=?=>=>=>=>=>=>=>=?=?=><><><><><><><><><><><><><>=>=><=<><><><><><><><><><><><><><><>=>=>=>=>=>=>=?=?=>=?=?=?=?=?>?=?>?>?=?>@>@>@>@>@>@>A?A?A?A?A?A?A?A?A?A@A@B@B@B@B@CACACACACACACADBDBDBDBDBDBECECECECFDFDGEGEFDGEHFHFHFHFHFIGIGIGJHJHJHKIKIKIKJKJLKMKMKMKNLNMNMNMOMPNPNPOQOQOQPRPRQRQSQSRSRTSTSUTUTUTVUWUWVXVYWYWYXZXZXZY[Y[Z\Z]\]\^]^]_^`_`_`_a`a`bacbdbdbecedfegegfgfihihihjhkikjkjmkmknlnlonpnpoqpqpsrtsutvuvtwvxwyxyy{y{z|z}{}|~}~KJLKLKLKMMMMNMONOOPOPOONPORQQPRQRQRRSRSRSRTSTTUTUTUUVUVVWVWWWWXXXXYYZZZZZZ[[\[\\]\^^_^^^____``a`aaa`aaaabbbbcbccdcdddddddcddedeeeefggghhghhhhhiiiiiijjkkkkjjkkjjjjjjjjjiiiiijiiihhhhiiiiiiijijiiiiiijijjiiiiiihhihiihhhhihihhihiggihhghhggggfggggghhhihighefccaa__^^]]\\[ZZZYYYYZYZYXXWWWWWVVVVVUUUUTTSSSSSSSRRQRQQQQPPPPOONONNMNMMMMMMLLKLKKJKJJIIH~}}|}|}|{zzxzyxwxwxvxvwuvuutustssrrqqppopnonomnlmlmkljkjjijijhihhggfgfgfgefeeddcdcdbcab`a``^`^_^^]^\]\]\][][\Z[ZZYZXYXXWXVXVXVWVWUVUVUVTUSUSTRSQSQSRSQRQRPQOQOQOPOPNPNPNPNOMNLNLNLNLMKMKLJLJLJLJKJKIKIKIKIJHJHIHIGIGIGHFHFHFHFHFHFGEGEGEGEFDFDFDFDFDFDECECECECECECDCDBDBDBDBDBDBCACACACAB@B@B@B@B@B@B@A?A?A?A?A?A?A?A?A?A?A?@>@>@>@>@>@>@>@>@>@>@>@>@>@>?=?=?=@>?=?=?=?=?=?=@>?=@>?=?=?=?=?=?=?=?=@>?=?=?=?=@>@>@>@>@>@>@>@>@>@>@>A?A?A?@>A?A?A?A?A?A?A?A?B@B@B@A?B@B@B@CACACACACACACACADBDBDBDBDBDBDBECDBDBECECECECECECFDECFDFDFDFDFDFDFDGDFDGEGEHEHEHEHFHFHFIFIFHFHFIGIGIGIGIGIGJHIGJHJHKHKIKIKIKIKILJLJLJLJMJMKMJMKMKMKNKNKNKNKNLNLNKNKNLNLNLOLOLOMOLOMOMPMOMPMPMPMPNPMPMPNPNPNPMPNPNPNPNQNQNPNQNQOQOQOQOQOQOQORPRPRPRPSPRPSPSPSQTQSPTQTQTRTQTQTRTRTRTRURUSUSTSUSUSUSUSUSUSUSUSUTVSVSVSVSVSVSVTVSVTVSVTVTVTVSVTVSVTVTVSWTWTWTVTVTVTWTWTWTVTVTVTVSVSVSVSVTVSVSVSUSVSVSVSVSVSVSVSUSUSUSUSURURTRTRTRTQTRTQTQSQTQTQSQSQRPRPRPRPRPRPRPRPQOQOQOQOQOQOPNQOPNPNPNPNPNPNPNPMPMOLOMOMOLNLNLNLNLNLOLOMNLNLNKNKNKNKNKMKNKMKMKMKMKLJMKLJLJLJLJLJLJLJLIKIKILIKIKIKIKHKHJHJHJHJHJHJHIGIGIGIGIGIGIFIFIFHFHFHEGEGEGEGEGEGEGEGEGEFDFDFDFDFDFDFDFDECECECECECDBDBDBDBDBDBDBDBDBCACACACACACAB@B@B@B@B@B@B@B@A?A?A?A?A?@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>?=?>?>?>?=?=>=?=?=?=?=?=>=?=><?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=?=>=>=>=>=>=>=>=?=?=?=?=?=?=@>@>@>@>@>@>@>@>@>@>@>@>A?A?A?A?A?A?A?A?A?A?A@A@A@B@B@B@BACABACACACACACADBDBDBDBDBDBECECECECECECFDFDGEGEGEGEGEHFHFHFHGIGIGIGIGIHJIKIJHKIKILJKJLKLKLKMLMLMLNMNMONONPNPOPOQPRPRPRQRQSQSRTSTSTSUSUTUTVTWUWVXVXVYWYWZXZY[Y[Z\[\[\[]\^]^]_^`_`_a_a`b`bacbcbdbdcedfefegfhfhfigjhjhkikjlklkmlnlompnpoqorprqsqsrtsutvuwvxvywzxzy{z|{}|}|~}~~KJKJKKLLMLMLNMNNPOPOQPQPQQQPQPRQQQRRSRSSTSTSUTUUVUVUVVWVWVWWXXXXXXXXZZZZ[Z[[\\\\]]^^^^__````a`babbbbcccbccccccedfegfffffgfgfffgfhghghgiiiiiijijjjjkkkllmlmmmmnmnmnmmllllllllllllllkkkkkkllllllllmnmnmmllllnooonommlllkllklkkjjkkklkkjjkjjjjjiiiihhiikllmmnkkkkiighdeaa`_^^^^]][[ZZ[[[[ZZZZYYYYYXXWWWVVVVUUUUTTUUTTSSRRRRRQQQPPPOPOOOOONNNMNMMLMLLKLKKJKJJIIHIH~~}~}~|}{{zzyzyzxyxxwwvwuvtvtustrsrsqrpqopnpnomnmnlmllkljkijijhhghghghghfgegeeddcdbcbcaa`a_`__^_]^]^]^\][][\[[Z[YZXZXYXYWXWXVWVWUVUVUVTVTUSUSTSTRTRSQSQSQRPRPQOQOQOQNQNPNPNOMOMNMNLNLMKMKMKLJLJLJLJLJKIKIKIKIJHJHJHIGIGIGIGHGHFHFHFHFHFGEGEGEGEFDFDFEFEFDECECECECECECDCDCDBDBDBDBDBDBCACACACAB@CAB@B@B@B@B@B@B@B@B@B@B@A?A?A?A?A?A?@>A?A?@>@>@>A?@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>A?@>@>@>A?A?A?A?A?A?A?B@A?A?A?A?A?A?A?A?B@B@B@B@B@B@CACACACACADBDBDBCADBDBDBDBDBDBDBECECECECECECFDFDFDFDFDGDFDGDGDGDGEGEGEGEHFHFHFHFHFHFIFIGIGIGIGIGIGIGJHJHJHJHKHKHKHJHKHKILJLJLJLJLJLJLJMKMKNKNKNKNKNLNLNKNKOLNLOLOLOLOMOMOMOMPNPMQNPMPNPNPNQOQNQNQOPNQOQOQNQOQOQOQOQOQOQOQOQORPRPRPRPSPSPSPSPSQSQSQSQSQSQSQTQTQTRTRTRTRTRTRTSUSUSUSVTUSUSVTVTVTVTVSVTVTVTWUWUWUWUWUWTXUWUWUWUWUWUWUWUWUWUWUWUWUWUWUWUXUWUWUWUWTXUXUWUXUXUXUWUWUWUWUWUWTWUWUWUWUWTWTWTWTWTWTWTWTWTVTVTVTVTVSVSVSUSUSUSUSUSURURURTQURTQTQTQTQSQSQSQTQSQSQSPSPRPRPRPQORPRPQOQOQOQOQOPNQOQOPNPNPNPMPNPMPMPMOMPMPMOMOMOMOLOMOMOLOLOLOLOLNLNKNKNKNKNKMKMKMKMKLJLJMJMJLJLJLILILILILILIKIKIKHJHJHJHJHJHJHJHJHJHJGIGIGIGIFIFIFIFHEHEGEGEGEGEGEFDFDFDFDFDFDFDFDFDECECECECECECECECECECDBDBDBDBDBCACACACACACACACAB@CACAB@B@B@B@A?B@B@A?A?A?A?A?@>@>@>@>A?@>@>@>@>@>@>@>@>?>?>?=?>?>?>?>?=?=?=?=?=@>?=@>?=?=?=?=?=?=?=?=?=?=?=@>?=?=?=?>?=@>?>@>?>@>?>@>@>@>@>@>@>@>@>@>A?A?A?A?@>A?A?A?A?A?A@A@A@A@A@BABAB@BABAB@CACACACACADBDBDBDBDBDBDBECDBECECECECFDFDFDFDFDGEGEGEGFGFHFHFHFIGIGJHJHJHJHJIKIKJKJLJLJMKMKMKMLNMNMNMONONPNPOPOPOQOQPQPSQSQSQSRSRTSTSUSVTVUVUWUWUXVXVYWYXZXZY[Y[Z\[\[\[]\^]^]_]_^`_`_a_a`b`cacadbdcedfdgegfhgigihjhjhkikjlklkmlnlnmnmonpoqprqrqsrtsutvtwvxwyxzxzyzy|{}|}|~~~}KJKJLKLKMLMLMMNMONOOPPRQRQSRSSSSSRTSSRTSTSTTTTUUUUWWWWXXXXXXYYYYYYZZ[Z[Z[[\\\\]]]]]]^^__`_a`babbbbccdcdddceeeeeeffgfgghhihhhiiiiihhhiijjjjkkkkkkllmlllmmnnnnooooppqqpqppooopoppppqppppppoonnnnnonnooppqqqpqqqqqrssssppppppnonooonnnonnmmmllllkklkkjjkkllnononomnlmjkjjhheebb```_^]\[\[\\\\\[\[[[[[ZZYYYYXXXWWVVVVVUUUUUTTSSSRRRRQQQQPPPPPPPOONONNNNMMLMLLKLKKJJIJIJIIHIH~~}}||{|{|z{zzyzxxwxvwuvuvtustrsqsqrpqopnomonommllkmkljkjjhjhjhjhhghghfgefeeddcdbcabab`a_`^`^_^_]^\^\]\\[\Z\Z[YZYZXZXYXYWXWXVWUVUVTVTVTUTTSUSTRTRSQSQSQRPRPRPQOQOPOPOONONONOMNMNLNLNLMKMKMKMKLKLJLJLJKIKIKIKIJHJHJHIGIGIGIGIGHFHFGFHFHFGFGFGEGFGEFDFDFDFDECFDFDECECECDCECECDBECDBDBDBDBCADBCACAB@B@B@CAB@B@B@B@B@B@A?B@B@B@B@A?A?A?A?A?A?A?A?A?@>@>A?A?A?A?@>@>@>A?@>A>A?A?A>A?@>A?@>@>A?A?A?A?A?A?A?A?A?A?B@A?A?B@B@B@B@B@A?A?A?B@B@B@B@B@CACACACACACACACADBDBDBDBDBDBDBDBDBEBECECECFCFCFCFDFDFDFDGDGDGDGEGEGEGEGEGFGFHFGEHFHFHFHFHFHFIGHFIGIGIGJHJHJHKHKHKHKHKHKHKHLILILILILILJMKMKNKMKNKNKNLNKNKNKOLNLNKOLOLOLOMOMOMPNPMPNPNQNPNQNQNQNQNQNRORORORORORORPRPRPRPRPRPRPRPSPRPRPSQSPSPSQSPTQSQSQSQTQTQTRTRTRTRUSTRTRTRUSUSUSUSUSVTUSVTVTVTVTVTWUWUWUWUWUWUWUXVWUXVXUYVXUXVXUYVYVYVXUXVXVXVXVXVXVYVXVXVYWYWYVYWYWYWYVYVXVYVYVXVXVYVXVXVXVXVYWXVXVXVXVYVYVXVXVXUXVXUXUXUXVXUWUWUWUWUWUWUWTWTVTWUVTVTVTVSUSUSVSUSUSUSUSTRTRTRTRTRTQTQTRTQTQSRSQSQSQSQSQRPRPQOQOQOQOQOQOQOPNQOQOQNQNQNPNQNQNQNPNPNPMPMOMPNOMOMOMOMOMOLOLOLOLOLOLNKNKNKMKMKMKMKMKMKLJMJLJLJLILJLJLIKILIKIKIKHKHKHKIJHJHJHIGIGIGIGIGIGIFIGIFIGIFHFIFHEHEHEHFGEGEGEGEFDFDFDFDFDFDFDECECECECECECECECECECECDBDBDBCACACACACACACACACACACAB@B@B@B@B@A?A?A?A?A?A?A?A?A?A?@>A?A?A?@>@>@>@?@?@>@?@>@>@?@>@>@>@>@>?=?>@>?=?=?=@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>@>A?@>@>@>@>A?A?A?A?A?A?A?A?A?A?A?B@A?B@A@B@A@B@B@BACACADBCACACADBDBDBCADBDBDBDBDBDBECECECFDFDFDFDFDFDGEGEGEHFHFHFHFHFHGHGHGIGJHJHJIJIJIKIKJKJLJLJMKMKMLNLNLNMOMOMPNPNPOPOQOQPQPQPRPSQSRTRTRTSTSUTVTVTWUWUWUXVXVYWYWYXZYZY[Z\[\[]\]\^\^]_]`^`_`_a`b`babacbdcecedfdfdfehfhgihihkikilklklkmlnlnmononqoqprqsrtrtrtsutwvwvywywzy{y|z}{}|~}~JIJJJJKJLKLKLLMLNMNMNMONPOQPRRSRSSTTTTUUUUUUUTUUUUUUVUVVWWXXYYYYYYZZ[Z[[\[\\\\]\]]]]^^_^____``a`bbbbccccddeeeeeefeffgghihhhhiijjjjkjjkkkllkkkjllmmmmmmmmoooonnnooppppqrrssssssstssssssttsssrssstttrrqqqrrrrsssuuuuuvttttvwvwuussssssqrqrpqpqppooonnmmlmlmmmlonqprrrrqrqroommkkjjhhccaaa`__^^^^^^^]^]]]]]]]\\]]]]ZZYYXXXXWWVVVVVVUUTTTTSSSRRRRRQQQQPPPOPOONONNMNMMLMLLKKJKJKJJJJIIIIHHG~~~}}|}{|{{z{yzxyxxwxvwuwuvttstssqsqrpqoqopnnmnlmllklkljkjkjjhjhhghghggffeedeccbcabab`b`a_`_`^_^_]^\^\][]\\[[Z[Y[YZXZXZXYWXWXVWVWUWUVUVTVTUTUSTRTRTRSQSQSQRPRPQPQPPOPOPNPNONOMOMNLNLNLMLMKMKMKLKLJLJLJKIKIKIKIKIKIKIJIJHJHIGJHIGIGIGIGHGHFGFHFHFGEGEGEGEFDFDFDFDFDFDECECECECECECDBECDBDBDBDBDBCACACACACACACAB@B@B@B@B@B@B@B@A?B@B@B@B@A?A?A?B@B@A?A?A?A?A?A?A?A?A?A?A?A?A?A?A?A?A?B@A?B@B@B@B@A?B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@CACACACADBDBDBDBDBDBDBDBDBDBDBECECECECECECFCFDFDGDGDGEGEGEGEGEGEGEHFHFHFHFGEHFHFHFHFHFHFHFIGIGIGJHIGJHJHKHJHJHJHKIKILILILILILILILILJLJLJMKMKMKNLNLOLOLOLOLOLOLOLOMOMOMPMPMPMPNPNQNQNQNQOQOQOQOQORORORPRORORPRPRPSPRPSQSPSQSQSQSQSQTRTQTQTQTQTQTRTRTRTRTRUSTRUSUSUSUSUSUSUSUSVSVTVTWTWTVTWUWUWUWUWUXVXVXVWVXVXVXVXVXVXVXVYVYWYWYWYVYWYWYWYWYWYWYWYWYWZWZWYWZXZXZXZXZXZXZXZXZXZXZWZXZWZWZWZWZXZXZXYWYW[XZWZWYWYWZXZWZXZWZWYWYWXWYWYVYWYVZWYVYVXVYVXVXVXVXUXUXUWUWUWUWUWUVTVSWTVTWTVTUSUSVSUSUSUSUSTRTRTRTRTRTQTRSQTQTQSQSQSQRPRPRPRPRPRPRPRPQORPRPQOQOQOQOQOROQOQOQOQNQNPNPNPNPNPNPMPMOMOMOLPMOLOLOLOLNLNLNLNLNKNKMKMKMJMJMJLJLJLILILILIKIKIKIKIJHJHJHJHJHJHJHJHJHJHJHJHIGIGIGIGIFIFIFHFHFHEHEHEGEGEFDFDGEFDFDFDFDFDFDFDFDFDECECECFDECDBDBDBDBDBCACACACACACACAB@B@B@B@CAB@B@B@B@B@B@A?A?B@A?A?A?A?A?A?A?A?A?A?A?A?A?A?A?A?A?A?@?@?@?@?@>@?@?@>@>A?@>@>A?@>A?@>@>A?A?A?A?A?@>A?A?@>A?A?A?A?A?A?B@A?A@A?B@B@B@B@BAB@CACACACACACACBDBDBDBDBDBDBDBDBDBDBDBECECECECFDFDFDFDFDFDGEGEGEGEGFHFHFHFHFHFHFIGIGIGJHJHJHJIKIKJKJLJLJLKMKMLNLNLNMOMOMONONPNPOQOPOQPQPQPRQRQSQSRTRTSTSUTVTVTWUWVWVWVXVYWYXZYZY[Y[Z\[\[\[]\^]_]_]_^`_`_a`a`bacbdbdcdcedfdfegegfhfigjhjikikjlkmlnmnmomomonpoqprqsqtrtsususvtwvxwyxzy{z|{}{}|~}JJJJKJKKLLMLMLNMNMONONPOPPQQRRSSTTUTUTVUWVWVWWVVVUVVWWWWXWXXYYYY[Z[[\\\\]]]]]]^^_^__`_`_``aaaaaabbccddeeefffggggggghhhiijijjjjkklkmmmmmmnmmnnnnmonpopooopprrrrqqqqssssrsttvvvvvw€wx€uwvv€wwÀwx€ww€vuww€xxÀwxÀuv€uu€vv€uvvvwwyyÀzyÀzz€xxwxyzyzyyxxvvuuuuttttrsrrrrqppoooooooposrutuvttrsrsrrppmmlljjffdcbba```````````___^`_````]][[ZZZZZYYYXXWWWWWVVUUUUTTTSSSSSSRRQQQQPPPOOOOOONNMNMMMMLLKKKKKKJJIJIJIIHIHIH~}~}~|~|}{|z{y{yyxywxvxvwuvtustrtrsrrpqopoonomnlmkmkmkljkjkjjiihighgfegefeecdcdccbcab`b`a_`^`^_]_]^\]\]\][\Z[Z[Z[Y[YZXZXYWXVXVXVWVVUVUVUUTUTUSTSTSTRSRSQSQRPRPRPRPQOQOPOPNPNOMOMOMOMNLNLMLMLMLMKLJLJLKLJLJKIKIKIKIKIJHJHJHJHJHJHIGIGIGIGHFHFHFHFHFHFGEGEGEGEGEFEFDFDFDECECECECECECECECDCDBDBDBDBDBDBDBDBDBDBCACACACACACACAB@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@CACACACACACACACACACACACACACADBDBDBDBDBDBDBDBDBECDBECECECFCFCFCFDFDFDFDGDGEGEGEGEHFGEGEGEHFHFHFHFHFIGHFIGHFIGIGIGIGIGJHJHJHJHJHKHKHKHKHKHKIKILILILJLJLJLJMKMKMKLJMKMKNKNLMKNLOMOMOMOMOMPMPMPMPNPNPNPNPNQNQOQORORORORPRPRPRPRPSPSPSQSQSQSQSQSQTRSQTQTRTRTRTRTSTSTRUSTRURURUSUSUSUSUSVSUSVTVTVTWTWTWTVTVUWUWUWUWUWUWUWVWVWVXVXVXVYVYVXVYWYWYWYWYWYWYWZXZXZXZXZXZXZXZXZXZX[X[YZX[Y[XZX[X[X[Y[Y[Y[Y[Y[Y[Y[Y[Y\Y[Y[Y[Y[Y[Y[Y[Y[X[Y[X[Y[Y[Y[Y[YZX[X[X[X[Y[X[X[YZXZX[X[YZXZXZXZWYWZWYWYWYWYWYWYWYVYVXVXWXVXVXUXVXVWUWUWUXUXUWUVTVTVTVTVTUTVTUSUSUSUSUSUSTRTRTRTRTRTQSRSRTQSQSQSQSQSQSQSQSQRQSQRPRPRPRPROROROROQOQOQOQOPNPNPNPNPNPMPMPMPMOMOMPMOLOLNLOMNLNLMKNLMKMKMKMKMJMJLJLJLJLJLILIKILIKIKIKHKIKIKIJHJHJHJHJHJHJHIGIGIGIGIFIFIFIFHFHEHEHEGEGEGEFDFDGDFDFDFDFDFDFDFDFDFDFDECECECECDBDBDBDBCACACACACACAB@CACACACACACAB@B@B@B@B@B@B@B@B@B@B@B@B@B@A?B@B@B@B@B@B@B@A?A?A@A?A@A?A?A?A?A@A?A?A?A?A?A?A?A?A?A?A?A?A?A?A@A?A@A?B@B@B@B@B@B@BABABABACACACACACACACADBDBDBDBDBDBDBECECECECECECECECFDFDFDFDFDGEFDGEGEGEGEHFHFHFHFIGIGIGIGIGJHIGJHJHJHKIKIKILJLJLJLKMKMKNLNMNMNMOMONONPNPOQOQPQPRQRQRQSRSRTSTSUSUSUTVTVUVUWVWVXVXWYWYXYXZY[Y[Z\[\[]\]\^]_]_^_^`_a`a_bab`bacbdcdcedfdgegegfhfhgihjijiljlkmlmlnmompnpnqoqprqsqtrtrtsutwuwvxvyxzx{y{z|{~|~}~IIIIJJKJKKLKLKMLMLNMNNONOOPOPOQPRQSSTTTTVUVVWVWWXWXXXXXXWWXXYYXXYYZY[Z[[\\]]^^^^^^____``a`a`aaaabbcccbcbccddfegghhiiiiihiijjkjkjlkllllmlnmnnppooopppqrqqrrrrrrssssttuuuuuv€ww€vw€vwÀwwÀyyĀzzǀzzǀ{|ǀz{ŀyzĀzzƀzzƀyyŀzzƀ|}ƀ}~Ȁ||Ȁzzǀyzŀy{ŀzzŀzzƀ{{ƀ}~ǀ~~ƀǀ}~ƀ|}ŀ~~ŀ}}Ā~~Ā}}À{{Àyywwvwuvuuvvvuttssrrrrqqssxwxxvuuuuuttssrqqponllhhfeddccccccccccbbaabbdccc``^]]]\\[[[[ZZYYXXXXXXWWVVVUUTTTSSSSSRRRRQQPQQPPPPPOOOONONNNMMMLLKLKKKJJJJJIJIIHIHHGHG~~~~}~|}{|zzyzyzyywxvwuwuuststrsqrpqopopoonomomnmnmlklklkkikijhighffefeedecdcdbdbcabab`a`a_`^_]^]^\]\]\\[\[\Z\Z[Z[YZXZXYWYWXVXVXVVUVUVTVTUTUTTSUSTRTRTRSQRQRQRQRPRPQOQOQOPNPNONPNPNOMOMNLNLNLMKMKMKLJLJLJLJLJKJKIKIKIJIJHJHJHJHJHJHIHIGIGIGHFHFHFHFHFHFGEGEGEGEGEFDFDFDFDECFDECECECECECDBECDBDBDBDBDBDBDBDBDBDBDBDBCACACACACACACACACACACACACACACACACACACACACAB@CACACACACACACACACACACACACACACACACACADBDBCADBDBDBDBDBECDBDBDBECECECECECECECFCFDGDGDFDFDFDGEGEGEGEGEGEGEHFHFHFHFHFHFHFIGIGIGIGIGJGJGJGJGJHJHKHKHKIKIKIKIKILILILILJLJLJLJLJMJMKMKMKMKMKNKNKNKNKNKNLOLOLOLPMPNPNPNPNPNQOQOQOQORORORORORPRPRPSPSPSQSQSQSQSQSQTRTRTRURTRUSUSUSUSUSUSUSUSUSUSVTVTVTVTVTWTWTWTWTWUWTWTWUWUWUWUWUXVXVXVWUXVXVXVXVXVYWYWYWZXYWZXYWZXZXZXZX[X[X[Y[Y[X[Y[Y\Y[Y[Y\Y\Z\Y\Y[Y\Z\Z\Z\Z]Z\Z\Z\Y\Z\Z]Z\Z]Z\Z]Z]Z][][][][\Z]Z\Z\Z\Z\Z]Z]Z\Z\Z\Z\Z\Z\Z\Z]Z\Z\Z\Z\Z\Z\Z\Y\Y\Y[Y[Y[Y[Y\Y[Y[Y[YZXZXZXZXZXZXZXYWZWYWYWYWYWYWYWXVYVYVYVXVXVWUXVWUWUWUWUWTVTVTVTVTUSVTUSUSUSUSUSUSURTRURURTRTRTRTRTRTRSQSQSQSQSQSQSQSQRQRPRPRPRPRPRPQOQOQOQOQOQNQOPNPOQNPNPNPNOMPMOMOMOMNLNLNLNLNLNKMKMKMKMKMKMJMJMJMJMJLJLILJLIKIKIKIKIKIKIJHJHJHJHJHJHJGJGIGJHIGIGIGIGHFHFHEHFHFHEHEHEHEGEGEGEGEFDFDFDFDFDFDFDFDECECDBDBECDBDBDBDBDBCACACACACACADBCACACACACACAB@CAB@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@A@B@B@B@B@B@A@B@B@B@BABABABABACBCACACACADBDBDBDBDBDBDBDBDBDBDBDBDBECECECECFDECFDFDFDFDGEGEGEGEGEHFGEHFHFHFHFHFHFIGIGIGJHJHJHJHJIKIKIKIKJLJLJLJLKLKLKMLMLNLNLONONONONPOPOPOPOQPRQSRSRSRSRTSTSTSUTUTVTVTVUXVWVXWXWYXYXZYZY[Z[Z\[][]\]\^]_^_^_^`_`_a`b`bacacbcbdbecedfegegfhfhgigjikjlklkmlmlomnmonpopoqprpsqsrtsutvtwuxvxvywyx{z{z{z}|~|}~IIIIJJKKLKLKLKMLNMNMNNONOOPOPPQQQQRQTSUTVUWVWVWVXXXXYYYYYYZZZYZYZY[[[[\\]\]]^]^^__a`aaaaaabbbbccccccddededfefegfhgiijjjjjjjjkjkkklmmnmnmnmnnoooppqqrrstuuuuuttttuuvwÀvw€wwĀxxŀyyŀyyƀyzƀyzƀyyƀz{ǀ||Ȁ}}ɀ}}ʀ}~ʀ}~ʀ|}ɀ}}ʀ~~ˀ~~ʀ~ʀ̀̀̀~ˀ~~ɀ~ʀ~~ɀ~}ɀ~ʀˀʀ̀ˀɀɀǀȀƀ~ŀ}|ŀzzĀwxwxwxxwvuwvuuuuttssvv{{yyxxxxxxxwvuutrrppmmjihhggffffffffededddffffccbba`__^^]]]\\[[[ZYZZYYYXXXWWVVVVUUUUTTTSSRRRRRQQQQQPPPOOOONNNNNMMMMMMLLKLKKJKJJJJIIIIHHG~~~}~}~||{|z{yzyyxxwxwvuvtvttstrsqrprpqpqopnpnomomnmmllkljkijhihighffefefdedecdbcbcacab`a``_`^_^_]^]^\]\][\[[Z[Z[Z[ZZYZXZXYWXWYWWVWVWUVUVTVTUSUSUSTRSRSRSRSQRQRPRPQPQPQOQOPNPOPNPNONOMOMOMNLNLNLNLMLMKMKLKLKLJLJLJKJKJKJKIKIKIJHJHJHJHJHJHIGIGIGIGHFHFHFHFHFHFGEGEGEFDGEGEFDFDFDFDFDFDEDECECECECECECDBDBDBDBDBDBDBDBDBDBDBDBDBDBCACACADBDBDBCACADBCACACACADBDBDBDBCADBDBCACACACACADBDBDBDBDBECDBDBECECECECECECECECECECECECFDFDFDGDFDGDGDGEGEGEGEGEGEGEHFHFHFHFHFIGIGIGIGIGIGJGJGJHJGKHJHJHJHKHJHKIKIKIKIKILJLJLJLJLJLJMJLJMKMKMKMKNKNKNKNLNLNLOLOLOLOMOMOLOMPMPMOMQOQOQOQORORPRPSPRPSPSPSPSPSQSQSQTQTRTRTRTRTRUSUSUSUSUSUSVSVTVTVTVTWTWTWTVTVTWUWUWUWUWUXVXUXUXUXVXVXVXUXVXVXVYVXVYWYWYWYWYWZXZXZXZXZXZXZX[X[XZYZY[Y[Y\Y\Z\Z\Z\Z\Z\Z\Z\Z\Z][\Z][\[][][][][][][][][][^\][^\^\][^\^\^[^[_\^\^\^\^\^\^\^\^[^\^[^[^[^\][^\]\][][][][^\][][][][][][^[]Z]Z]Z]Z]Z][][\Z\Z\Z\Z[Z\Z\Z\Y\Y[Y\Y[X[YZX[YZXZXZXZWZXYWZXYWYWYWYWYWYVXVXVXVXVWUXVWUWUWUWUWUVTVTVTVTVTVSVTVTUSUSUSUSUSVSUSUSUSUSURTRTRTRTRTRSQSQSQTQSQSPSPRPSPRPRPRPQOQOQOQOQOQNQNPNPNPNPNPMPMNLOLOMNLNLNLNLNLNLMKMKNKMKMKMKMKMJMJMJLJLILILILIKIKIKHKIKIKHJHJHJHJHJHIGIGIGIGIGIGIGIFIFIGIGHFHFHFHFHEGEGEGEGEGEGEFDFDFDFDECFDECECECECDBECDBDBDBDBDBDBDBDBDBCADBDBDBDBCACACACACACACACAB@CACAB@CACACACACACACACACAB@B@CACACACABABACACACACAB@CAB@CABABABABACACACADBDBDBDBDBDBDBDBDBDBECDBDBDBDBDBECECECECFDFDFDFDFDFDFDGEFDGEGEGEGEHFHFHFHGIGIGIGIGIGIGJHJHJHJHJIKIKIKIKJKJLKLKLKMKLKMLMLNMNMNMONONONPOPOQOPOQPQPRPSQSQRQSRTRTSTSUTUTUTUTWUWVWVXVXWYXZYZYZY[Z[Z\[]\]\]\^]_]_^`_`_a_a`a`bacbcbdcecfefdfdgegehfhgihihjikjljmkmlnlomonpnpoqpsqsqtstrutvuvuxvywywyx{y{z|{}|}|~}~IIIIJIKJKKLKLLMMMMMLMMNNONOOPPQPRQRQRQSRSSUTVVVVWWXXYXYXYYZZ[Z[Z[[[[[[\[]\]]^^^^__````aaaabbccccccddededfeffgghhhhihiijjkkllllllmlnmnnnoooppppppppqqrrssssttuuvv€vvvvwwĀxyŀyzŀyzƀyzǀ{{Ȁ|}ɀ|}ɀ}~ʀ}~ʀ||ˀ}}̀~ˀ̀̀΀̀̀΀ππ΀ЀрЀπ΀̀̀̀̀΀π΀̀̀̀ˀˀɀȀɀ}~ǀz{ŀyyĀyyĀxxÀwwÀxx€xx€vwvvwvyx||€}}ŀ}~ƀ|}À}~€{zyyxwvvsronkkjjihhhiihihgffffhihhfeddccbbaa___^^^]]]]\\\[[[[ZZYXXXWWWWWVVVVUUTTTSSSSSSRRQRQQPPPPPPPNNNNNNNNMLMLMLLKKKKKKJJIIIIHIHHGGF~}~|~||z|z{zzxywxwwvwuwuutustrsqsqrpqpqopopnomomnlmklkkjjiihighggegefeededdcdbcbbabaa`a_a_`_`__]_]^]^\]\]\\[\[\Z[Y[YZXYXZXYWYWXVXVWVWUWUVTUSUSUSTSTRTRSRSRSQRQRQRQQPQOQOPOQOPNPNPNPNOMOMOMNMNMNLNLMLMKMKMKMKMKLJLKKJLJKJKIKIKIKIJHJHJHJHJHIGIGIGIGHGHGHGHFHFHFHFGFGFGEFDGEGEGEFDFDFDFDFDFDFDFDECECECECECECECDCDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBECECECECECECECECECECECECECECFDFDFDFDFDFDGDGDGEGEGEGEGEGEHFGEGEHFHFHFHFIFIGIGIGIGIGJGJGJHJHKHJHJHKIKIKIKIKIKIKJLJKJLJLJLJMJMJMKMKMKMKMKNKNLNLOLOLOLOLOMOMOMOMPMOMPMPNPMPMPNPNQNQNRPRPSPSPSQSQSQTQSQSQTRTRTRTRTRTRURUSUSUSUSUSVTVTVTVTVTWTWUWUWUWUWUWUXUXVXVXVXVYVXVXVYWYVYWYWYWYWYWYWYWYWZXZXZXZXZXZXZXZY[Y[Y[Y[Y[Y[Y[Z[Z\Z\Z\Z][\[][][][][][][][][^\^\^\^\^\^\^\^\^\_]_]_]_]_]_]_]_]^\_]_]_]_]_]_]_]_]_]`^_]`]_]_]_]_]_]_\_]_]_]_]_]_]_]_]_]^\_]_]^\^\_]_]^\^\_\^\^\^\^]^\^\][^\][^\][^\][][][\Z\Z\Z\Z\Z\Z\Z[Y\Z[Y[Y[Y[Y[YZXZXZXZXYWZXZXYWYWXVYWXWXVWVXWXVWUXVXVXVWUWUWUWUWUWUWUVUVUVUVUVTVTVTVSVTUSUSUSUSUSTRTRTRTRTRTRTRTQSQSQSQSQSQSPRPRPQOQORPQOQOQOQOQNQNPMPMPMOLOMOMOMNLOMOMNLNLNLNLMKNLNKMKMKMKMJMJMJLJLJLJLILIKIKIKIKIKIKIJHJHJHJHJHIGJHIGJHJHJHIGIGIGIGHGHFHFHFHFGEGEGEGEGEGEFDFDFDFDFDFDFDECECECECECDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBCADBCADBCACACACACACACACACACACACACACADBCADBDBCACACACACADBCADBCBCACBDBDBDBDBDBDCDCDBDBDBDBECECECECECECECECECECECFDFDFDFDFDGEGEGEGEHFGEHFHFHFHFHFHFIGIGIGIGJHJHJHJHJHJHKIKIKIKJLJLJLJLJLKMLMLMLNLNMNLNMNMONONPNPNQOPOQOQPRPRPRQRQSQTRSRTSUSUTVTUTVUWUWUWVXWXWYXYXZYZY[Z\[\[\[^\]\^]_^_^_^`_a`a`b`cacadcdcededfegegfgehfigihjijikiljlkmlmlnmononpoqpqprqsrtsusvuvuwvxwywyxzy{z{z|{}|~}~IHJIJJKJKJLKLLMLMMNMNNNNNNONPPQPQPQQRRSRSSTSTTVVWWXXYYYYZZ[Z[[\[\\]\]]]]]]^^^^____``a`babbccccddeeeeeefegfgghhiijikjllmmmmmmnnooooooooppqqqqqqrrssrrrrtstuvv€wwÀvvÀwwĀwwĀxxŀyzƀyzƀz{Ȁ{|ɀ~ˀ~ˀ~~̀̀̀̀΀рЀЀррррЀҀԀԀӀԀՀՀӀррЀЀЀрӀӀҀрЀ΀̀΀̀̀~ˀ}}ʀ{{ǀzzƀzzƀzzƀzzƀxyŀxxÀwwÀzz€|{Ā~~ĀǀȀǀƀ~ŀ}|Àzzxxuupomllkkkkkkkjjiiiiiiiihhffdccdbbbbbbaa`______^^^]\\\[ZZZYYYYYYWWWWVVVVUUUUTTTTSSSRRRRRQQPPPPOOOOOONNNMNMNMMLMLLKLLJJJIIHIHHGHGGG~}~|}||{{z{yzxywxwwvwuutusustrsqtrsqrqqpqopoonomnlmkkjjhjhihhghghggffefeedecdccbcabab`a`a`a_`^`^_^_]^\^\][\[\[[Z[Y[YZYZXYXYWYWXWXWXVWUWUWUVTVTUSUSUSTSTRSRSRSQRQRQRPQPQPQOQPQOPOPNPNPNONONONOMNMNLNLMLMLMLMKNLMKMKLJLJKJKJKJKIKIKIKIJIJHJHJHJHJHIHIGIGIGIGHGHFHFHFHFGEGEGEGEGEGEFDFDFDGEFDFDFDFDECECFDECECECECECECECECECECECDBECECECDBDBECECDBDBDBECECECECECECECECFDECECFDECFDFDECFDECFDFDFDFDFDFDFDFDGEGEGEGEHEGEGFHFHFHFHFHFHFIGIGIGIGIGIGIHJHJHJHJHJHJHJHKHKIKIKIKIKILJLJLJLJLJLJLJMKMKMKMKMKMKNLNLNLNLOMOLOLOMOMPMPMPMOMPNPNPNPNPNQNPNQOQOQOQOROROROSQSQSQTQTQTRTQURTRTRUSUSUSVSVTVTVTVTWUVUWUWUWTWUWUXVWUWUXVYWYWXVXVYWYWYWYWYWYWYXYWZXZXZXZXZXZXZXZX[YZX[Y[Y[X[Y\Z[Z\Z\Z\Z\Z\Z\Z\Z][\Z][][][^\][][^\^\^\^\^\_\^\^\_]_]_]_]_]_]_]`^`^`]`^`^`^`^`^`^`^`^`^`^a^`^`^`^`^a_a_a_a_a_a_a^a^a_a^a^a_a^a_`^`^`^`^`^`^`^`^`^`^`^`^`^`^`]`^`^`^`]`]_]_]_]_]_]_]_]_\^\^\^\^\^\^\][^\][][][][][\Z\Z\Z\Z\Y\Z\Z\Z[Y[Y[Y[YZYZXYWZXYWYWYWYWYWZXYWYWYWXVYWXVXVXVXVXVXVWUXVXVWUWUWUWUWUWUWUVTVTVTVTUSVSUSUSUSUSUSURTSTRTRTRTQTQTQSQSQSPSPSPRPRPQOROQOPNPNPNPMPNPNPMPMPMPMOMPMOLOMNLNLNLNLNLMKNLMKMKMKMKMKMJLJLJMJMJLILILILILIKIKIKHKHJHKIJHJHJHJHIGIGIGIGIGIGHFHFHFHFHFHFHFGEGEGEGEGEGEFDFDFDFDFDFDFDECECECECECECECECECECDCDCDBECECECECECECECDBDBDBDBDBDBDBDBCACACADBDBDBDBDBCADBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBECECECECECECECECECFDECECFDFDFDGEGEGEGEGEHFHFHFHFGFHFHFHFHFIGHGJHJHIHJHJHJIJIJIKIKIKIKJKJKJLJLKLKLKMKMKMKNLNLOMOMOMOMPNPNPNPOPOPOQPRPRPRQSQSQTRSRTSTSTSUSUTVTVUWUWVWVXWXWYXZXZYZY[Z\[\[][^\^]^]_]_^`^a_`_a`babacbcbdcecfdfdfegehfhfhgigjhkjkjljmkmknlomonpoqpqprprqsrtsututwuwuxwyxyxzy{y|{|{}|~}}IHIHIIJIKJKJLKLLLLMMMMNNOOOOPPQPPPQQRRRRSRSSSSTTUUVVWWYYYYZZ[[\\\\]]]]^^^]]]^^^^__a`a`aaaacbbbccdcdcfefegfgfhgihkjkjlklkmlnnooppqqpqqrrrrrrsrrttuuttttuu€uu€vvÀwwÀxxĀyyƀz{ǀ{|ǀz{ǀ{{ǀ{|Ȁ|}ɀ}}ʀ}~ˀ̀ππ΀ЀрӀӀՀՀրՀր׀؀րր؀؀؀ـــ؀׀րԀԀՀՀ׀ـրՀҀӀҀрррЀ΀~~ˀ}}ɀ}~ʀ||ɀ||ɀzzǀzzǀ{{ǀ~~ǀ~ȀɀɀʀˀɀȀƀ||À{z€wvqpnnmmnmnnmmkkkkllkkjjiiggefefddcccccccbbbaa````__^^]]\\[[[ZZYYXXXXXWWWWVVUUUUUTTSSRSRRRRQQQQQQPPPOOOOONONONNNMMMLLKLKKJJIJIIHHHHGGF~}}}{}{|z{yzxzxywxvwuvuvtvttssrsrsqrqrpqppnomnlmkljkikijijhighggfgefefdececdccbcbcbb`b`a_a_`^`^`^_^^\^\]\][\Z\Z[Z[ZZY[YZXYXYXXWXWXVXVWUWUVTVTVTUTUSUSTSTSTRSQSQSQRQRQRPRPQPQPPOPOPOPNONONONONOMNMOMNMNLNLMLMLMLMLMKMKLJLJLJLJKJKJKJKIKIJHJIJHJHJHJHIGIGIGIGIGIGIGHFHFHFHFHFHFGEHFGEGEGEGEFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDECFDECECECECECECFDFDFDECECFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDGEGEGEGEGEHEHEHEHEHFHFHFHFHFIGIGIGIGIGJGIGIGJHJHJHJHJHJIKIJIKIKIKILJKIKILJLJLJMKMKLJMKMKMKMKMKNLNLNLNKNLNLNLOMOMOMOMOMPMPNPNPNPNPNQOQOQOQOQOQOQORORORPRPRPSPSPSQSQTRTRUSUSUSUSUSVTVTVTVTVTVTWUWUWUWUWUXUXUXVXVYWYVYVYWYWYXYXYXYXYXZXZXZX[YZX[Y[Y[Y[Y[Y[Y\Y[Y\Z[Z\Z\Z\Z\[\Z\Z][\Z\Z][][^\^\][^\^\^\^\][^\^\_]_]^\_]_]`^`^`^`^`^`^`^a^a^a_`^a_a_a_a_a_a_b`a_a_b`b_b`b`b`b`b`b`b`b`cab`b`b`b`b`b`b`b`b`b_b`b`b`b`b`b`b`b`b`b`b`b`b_b_a_a_b`a_b`b_a^a_a_a_a^a_`^a_`^`^a_a^`^`^`^`]_]_]_]^\_]^\_]^\^\_\^\^\^\][][][][][][\Z\Z\Z\Z[Z[Z[Z[Y[Y[YZY[YZXZXZXZXZXYXYXZXYWYWYWYWYWYWYWXVXVXVXVXVXVXVXVWUWUXUWTWTWTVTVTVTVTVSUSUSUSUSUSURURTRTRSQSQTQTQTQSQRPRPRPQOQOQOQOQOQNQOQOPNPMPMPMPMPMPMOMOMOLOMOMNLNLMKNLNKNLMKMKMKMKMKLJLJLJLJLJLJLJLJLJLILIKIKIKIJHJHJHJHJHJHIGIGIGIGIGIFHFHFHFHFHFHFGEGEGEGEGEFEFDGEGEFDFDFDFDFDFDFDECECFDECECECECECECECECFDECECECECECECECDBDBDBDBDBDBDBDBDBDBDBECECECDBDBECECDBECECECECECECECECECECFDECECFDFDFDFDFDFDGEGEGEGEGEGEHFGEHFHFHFIGHGIGIGIGIGJHIGJHJHJIJIJIKIKIKIKJKJKJLJLKLKLKMKMKMLMLNLNMNMOMONOMPNPNPNPOPOPOQPQPRPRQSQRQSRTRTRTRTSUSUTUTVTWVWVXWXWYWYWYXZYZY[Z[Z[Z\[]\^\^]^]_]_^`^a_a_b`cacadbdbdcedfefegfgfgfhfigihjijikjlkmkmknlnmnmonqprprqrqtrtstsutvuvvxwxwyxzy{z|{}|~}~}~IHIHJIJIJJKJLKLLMLMMNNNNOOOOPOPPQPQQRRRRSSSSTTTTUUUUWWXXYYZZ[[\\]]]]^^^^__``_^^]__`_aabbcbcbccedfeedeefegfhghhihjilkmmnmnnooooppqrstttuvtuuvvwxxÀwx€ww€wwÀxxĀwwÀxxĀyyǀyyǀzzȀzzɀ}}ʀˀ~~ʀ}}ʀ~~ˀˀ̀̀πЀҀӀӀԀ׀ڀـڀڀڀۀۀۀۀ܀ۀހۀۀހ߀ހ܀ۀڀ؀րրր؀ۀـ؀׀׀׀րՀրՀӀπ΀΀̀~~̀}}ˀ~~ˀɀʀ̀̀ˀˀ̀̀ʀȀ}}ŀ{z€vuqqpooopoopnnmmmnmmmmjjjjiihhggggffeeeedcdccbbaba```___]]\\\\[[ZZZZYXYXXXWWWVWVVUUUUTTSSSSSRRRRRQRQQQQPQPPOOOOOONNMMLMLLKKJJJJIIIIHIHHGHG~~}}|}|{z{zzyzxywywwvwuvtvttstssrsqrpqopnnmnlmklkljkikijijhigiggfgffefdedececdbdbcababab`a_a^`^_]_]_]_]^\]\][\[\Z[ZZYZYZYYXYXYXXWXWXVWVWUWUVUVUVTUTVTUSUSTSTRTRSQSQSQSQRQRPQPQPQOQOPOPOPOONONONONOMOMNMNMNMNLNLNLMKMLMLMKMKLKLKLJLJKIKIKIKIKIKIKIJHJHJHJHIHIGIGIGIGIGIGHFHFHFHFHFGFHFGEHFGEGEGEGEGEGEGEFDGEFDGEGEGEFDGEFDGEFDFDFDGEGEGEGEGEGEGEGEFDGEGEGEGEFDFDGEGEGEGEGEHFHEHEHFHFHFHFHFHFHFIGIGIGIGJGJGIGIHJGJHJHJHJIJIKIJIKIKIKJKJLJLJLJLJMKLJLJMKMKMKMKMKMKMLNLNLNLNLNLOMNMOMOMOMOMPNPNPNPNPNPNQOQOQOQOQOQORPRPRPRPRPSPRPSQSQSQSQSQTRTRTRTRUSUSVTVTVTWUVTWUWUXUWUWVXVXVXVXVYWYWYWYWYWZXZXZXZXZXZXZX[Y[Y\Z[Y[Y\Z\Z\Z\Z\Z\Z\Z\Z\[][][][][][][^\][^\][^\^\^\^\^\_]^\_]^\_]_]_]_]_]_]_]`^`^a_`^`^a_a_b`a_a_b`b`b`b`b`b`c`b`b`cacab`cacacacacacacacacacacadbdbdbdadadadbdadbdbdbdadadbcacacacadbdbcacacacacacadac`cacacacab`b`cacacab`b`cab`b`b`b`b`b`a_a_a_a_a_a_`^`^`^`^`^`]`]`^_]`]_]^\^\^\^\^\^\][][][]\][\[\[\[\Z[Z[Z[Z[Y[Y[Z[Y[Y[Y[Y[Y[Y[YZYZXZXZXZXZXZXYWYWYWYWXVXVXWXVXWXVWVXUWUWUWUWUWUVTVTVTVTUSVTUSUSUSTSTRURTRTRTRTQSQSQSPSPRPRPRPROQOQOQNQNQOQNPNPNPNPMPNPNPNPMOMOMOMNLNLNLNLNLNLNLNLMKMKNLMKMKLJMKLJLJLJLJLJLILJKIKIKIKIKIJHJHKIJHJHIHIGIGIGIGIGIGIGHFHFHFHFGFGFHFGEGEGEGEGEGEGEGEGEFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDECFDECFDECECECECFDECECECECECECECECECECECECECECECECFDFDFDFDFDFDFDGEGEGEGEGEGEGEGEGEHFHFHFHFHFHFIGIGIGIGIGIHJHJHJHKIKIKIKIKJKJKJKJKJLJLJMKMKMLMLMLMLNMNMNMOMOMONONONONPOPNQOQOQPQPRQRQRQSQSRSRTSTSTSTSUSUTUTVUWVWVWVXVYWYWZXZYZY[Z\[\[\[]\]\^\_]_^_^_^a_a`a`babacbdcdcecedfegegfhfigihihihkjkjlkmlnlnlmlonpnqoqprprqsrtsutvuwvwvxwyxzx{z|z{{|{~}}~HHIHJIJIKJKJKKLKLLMMNNNNONPOPPQPRQRQRRRRSSSSTTTTUUUUVVWWXXZYZZ[[]\]]^^__`_`_``````````aacbccddedfegfffgghgihihjjkjkkllnmooppppqrrrssuuvvwwyywx€xyĀyzŀzzƀz{ǀzzǀzzǀzzǀzzǀzzǀ{{ɀ||ʀ}}ˀ}}ˀ~~̀΀̀π΀πЀҀԀր׀؀ـۀ݀߀߀ހ߀߀ހހ߀߀—‗䀘䀖—߀ހ܀ۀ܀݀܀ۀۀۀڀڀـــ؀؀րӀӀрЀπ΀̀΀Ҁπ̀΀̀̀ˀˀ~}ɀ{zƀuv€ssrrrrrrqqppoooonomnlmllkkiiiihhhhhhffffeeddddccbbaaa``_^^^]]\\\[[[ZYYYYYXXWWWWVVVVUUUTTTTTSSSSRRRRRRRRQQQQQPPOOONNMNMMLLLKKKJJJJIJIIHHHHGGGGF~~~}}||{{z{y{yzxxwxwwvwuwuustssrsqrpqopnonomnlnlmkmkljkijhihighghfgfgfgefdececdcdbcbcabaa`a`a_`__^_^^]^]^]]\^\][\Z[Z\Z[YZXZXYXYXXWXWXWXVWVWVWUVUVTUTUTUTTSUSTRTRTRSRSRSQSQRQRQQPRPQPQPQOQOQOPOPNPNONONOMNMNMNMNMNMMLNLNLNLMKMKMKLKLJLJKJKJKJKJKIKIJHJHJHJHJHJHJHJHIGIGIGIGIGHGHGHFHFHFHFHFHFHFHFGEHFHFHFHFHFHFGEGEGEHFGEGEGEGEGEHFHFGEHFHFGEGEGEGEGEHFHFHFGEGEHFHFHFHFHFHFIGHGIGHFHFIGIGIGIGJGJHJHJHJHJHKIKIKIKIKIKIKJLJLJLJLJLKLKMKMKMKMLMKNKNKNKNLNLNLNLOMNLNLOMOMOMOMOMPNPNPNPNQOQOQOQOQOQPRORPRPRPSPSPSQSQRQSRTQTRSRTRTRTRTRTRUSUSUSUSUSVTWUWUWUXVXVXWXVXWYWYWYWYWYWYWYXZXZXZX[YZX[Y[Y[Y[Y\Z\Z[Y\Z\Z\Z\[][][][][]\]\]\^\^\^\^\_]_]_\_\_\^\_]_]_]_]_]_]_]`^`^`^`^`^a_a_a_a_a_a_b`b`b`b`b`b`b`b`b`cacacacacacacadbcadbdbdbdbebdbdbdbdbececebecdbecebececececececebecececebebececececebdbececebebebecdbdbebecececdbdbdbdbdbdbdbdadbcab`cacacacacacacacacacab`b`b`b`b`a_a_a_a_`^a_`^`^`^_]`^_]_]_]^\_]^\^\^\^\^\^\][][][][][\[\Z][]Z\Z\Z\Z[Y\Z\Z\Z\Z\Z\Z[Y[Y[YZXZXZXZXZXZXYWZXYXYWXVYVXVXVXVXUXUWUWUWUWUVTWUVTVTVTUSUSUSUSURTRTRTRTRTQTQSQSQSQSQRPRPRPROROQOQOQOQOQOPOPNPNPNPMPMPMPMOMOMOMOMOMOLNLNLNLNLNLNLMKNLMKMKMKMKLJMJLJLILIKIKIKIKIKIJIKIJHJHJHJHJHIGIGIGIGIGHFIGHFHFHFHFHFHFHFHFGFGEGEGEGEGEGEGEGEGEGEFDGEFEFDGEFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDGEFDFDFDFDFDGEGEGEGEGEGEGEGEGEHFHFHFHFHFHFHFIGHFIGIGIGIGIGJHJHJHJHKIKIKIKIKILJLJKJLKLKLKMKMKMKMKMKMLMLNMNMOMNMOMOMONONONPOPOPOPOQPQPRPRQRQSQSRSRTSTSUTTSTSUSVUVUVUWUWVXWXWXWYXZXZYZY[Z\Z\[\[]\]\^]^]_^_^`_a`b`bababacbdcdcededfdfegehgigihjhjikikjlkmlnlomomonpoqpqprpsqtrtsutvtwvwvxwyxzx{y{z|{}|~}}~~IHIIIIJIJIKJKKKKLLMLNMNNOOPOPPQQQQRQRRRRSSTTTTTTUUVVVVWWXXXXYY[[\\\\^]^^__``aaa`a`aaaaaabbcbdcedfegfhghgihjikjkjkkkklkmmmmoppprrrrtuuvuvwwyyÀzzŀ{{ǀ{{ƀ{{ǀ|}Ȁ|}ʀ}}ˀ}}ˀ}}ˀ}}ˀ}}ˀ}}̀}}̀̀΀΀ЀрӀԀԀԀՀր؀ڀ܀܀܀߀ဖ䀖䀗䀙倘䀙〙䀗倖䀘倘䀛怜倜瀝逜耘倗怘瀖䀓】߀߀ဓ߀߀߀ހހ܀܀ۀۀ؀׀րԀҀрҀрҀҀрҀҀππ̀̀}~ʀyyǀwwŀuuÀuu€uu€ttssssqrppppppppnollkkjkijhiihhhhhgggffedddccbaaa``__^^^^]\\\[\[[ZZZYYYXXXXXWWVVVVUUTTTTTTTTSSSSSSRRRQRQPPPOOOONNMMMLLLKKKKKKJJIIHIHIHHGHG~}~|~|}{|z{z{yzxywxvxvwuututtssrrpqoqopnpnomnmnlmklkljkikijhihighghfgffefdedecdcdccbcabab`a`a_`_`__^_^^]^]]\]\][\[\[[Z[YZYZYZXYWYWYWXWXWXWXVWVWUWUVUVUUTUSTSUSUSUSSRTRSRSQSQRQRQRQRPQPQPQPQOQOPOPOPNONONOMONOMOMNMNMNMMLMLMLNLMLLKLKLKLKLKLKKJKIKIKIKIKIJHKIJHKIJHJHIGJHIGIGIGIGIGIGIGIGHFIGIGHFIGIGHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFHFIFIFIFIGIGIGIGIGIGIGJHJHJHIGJHJGJHJHJHJIKIKIKIKIKILJLJLJLJLJLKLKMKMKLKMKMKMLMKNLNLNLNMNLNLOMOMNLOMOMOMOMOMPNPNPNPNPNPNQOQOQORPRPRPRPSQRQSQSQSQSQSQSQTRTRTSTSTSUSUSUSUSUSUSVTVTVTWUVTVTXVXVXVYWYWYWYWZXZXZXZXZX[Y[Y[X[Y[Y[Y\Z\Z\Z\Z\Z][][][][][][^\^\^\^\_]^\_]_]_]_]_]_]_]_]_]_]`^`^`^a_a_`^`^`^`^a_a_a_a_a_a_a_b`b`b`c`b`cacadac`dacadacadbdbdbdbebebebebebecfcecfdfdfdfdfdfdfcfcfcfdfdfdfdfdfdfdfdgdgdgdgefdgdgegegdgdgdgegefdfdgdgefdfdfdfdfdfcfdfdfcfdfdfdfcfcfdecfcfcfcecebebebdbecebebebdbdbdbdacacacacacacab`b`b`c`b_b`a_a_a_a_a_a_`^`^`^`^`^`^`^_]_]_]_]^\_]_]^\^\^\^\^\]\][][][][][][][\Z\Z\[\Z\Z\Z\Z\Z[Y[Z[Y[Y[YZXZXZXZXYWYWYWYWYVYVXVXVWUWUWVWUWUWUWUVUVTUTUSVSUSUSUSUSURURTQTQTRSQSQSQRPSPRPRPRPRPQOQOQOQOQOQNPNPNQNQNPNPNPNOMOMPMOLOMOMOMOMNLNLNLNLNLMKMKMKMKMKLKMJMJLJLJLJLJKIKIKIKIJIJIJHIHJHJGJGIGIGIGIGIGIGIGIGIGHGHGIGIGHFHFHFHFHFHFHFHFHFHFGEHFGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEGEHFHFHFHFHFHFHFIGHFHFIGIGHGIGIGIHIGJHJHJHJHJHJHKIKIKIKJKJLJLJLJLJMKMKMKMLMLMKNLNLNLNMNMOMOMONONONPOPOPOPOQOQPQPQPQPRQRQRQSQSRTSTSUTTSUTVTVUVUVUWUWVWVXWXWYXYXZXZYZY[Z\[\[\[]\]\^]_]_]`^`_a`a`bababacbdcecedfefegfhfhghgjhjhjiljlkmkmknlompnpnpoposqsqsqsrtsvtvtvuwuxwxwzx{z|{|{}|}|}~IHIHJIJJJJKKKKLLMMMMMMNMONOOPPQQQQRRRRSSSSUTUUUUVUVVWWXXXXXXYYZZ[[\\]]^^^^``````aababababbccddedeegfhgihiijijjkjmlmmmlmlnmonooqqrrssuuuwwxÀxyĀyzƀ{{ǀ|}ɀ}}ʀ}~ʀ}~ʀ}~ˀ~̀΀΀΀π΀πрррҀԀՀՀ؀؀ـڀۀ܀ހހ―〛瀜逛耝逞逜ꀞ耠ꀝ通瀝逞逝ꀝ速뀠쀠퀞쀞뀝ꀜ逘瀕䀕〖䀙䀘〕䀕‖〖〔〔ပހހ܀ۀڀ׀׀Հ׀ՀՀҀрӀҀррπ̀||ˀ{{ʀzzɀxxǀxxǀyyǀvvŀuuÀtustssrrqqppoonnmmllkkkkkkjjiiiihhhhgffeddcccbaa``__^^]]]]]\\\[[ZZZZYYYYYXXXWWWWVUVUUUUUUUUUTTSSSRRQRRRQPPOOOOOOMMLLLLLLKKKJJIJIIHIHHGHGGGGF~}~|}{}{|z{yzxzxyxxwxvwuvtustrsqrqqppopopnomomnlnlmkljkjkijijhighghfgffefefdfdecdbdbcabab`a`a`a``_`^_^_]^]^]^\]\\[\[\[[Z[Z[YZXZYZXYWYWYWYWXVXVXVWVWUVUVUVTUTUTUSUSTSTSTRSRSRSRSRRQRQRQQPQPQPQPQOQOQOPOPOPNONONOMONNMNMNMNMNLNLMLMKMLMKLKMKLKLJKJKJKJKJKJKIKIKIKIKIJHJHJHJHJHJHJHJHJHJHIGIGIGIGIGIGIGIGIGIGIGIGIGIGIGIGIGIGIGIGIGIGJGIGIGJGIGIGIGIGIGIGIGJHJHJHJHKIKIJHKHJHKIKIKHKIKIKIKILJLJLJMKMKMKMKMKMLMLMLNLNLNLNLNLNLOMOMOMOMOMPNONPNPNPNPNPOPOPOPOPOQPQPRPRPRPRPRQRQSQSQSQSQSRSRTRTRTRTRTRUSUSUSUSVTVTVTVTVUWUWUWUWUWUWUWVXVWVZXZXZXZXZX[Y[YZY[Y[Z\Z\Z\Z\Z\Z][][][][][][][^\^\^\^\^\_]^]`^`^_]_]`]`]`^`^`^`^`^a_a_a_a_a_a_a_a_a_a_a_b`a_b`b`b`b_c`c`cacadacadadadadbebebdbececececececececfdfdfdfdgefdgegegegegdgegehfgegehfhfhfhfhfhfhfhfhfhfhfhfhfhfhfhfifhfhfhfigifhfhfhfhfhfgehfififhfhfhfhehfgehfhegehegegehegdgegdfdgdfdgdfdfdfdfdecfdecececebebdbebdbdadacacacac`b`b`b`b`b`b`a_b`a_a_a_a_a_`^`^`^`^`^_]_]_]_]_]_]_]_]_]_]^\^\^]^\][^\^\][][][][][\[\[][\Z\Z\Z\Z[Y[Y[Y[Y[YZXZXYWYWYWYWYWYWXWXWXVXVXVWUWUWUVTVTVTVTVTVSVSVSUSUSURURTSTRTQSQSQSQSQSPSPRPRPRPRPRPRPQOQOQOQOPNPNPNPNPNPNPNPNONOMOMOMOMOMNLNLNLNLNLNLMKMKMKMJMKMKLJLKLJLJLJLJKIKIKIJIJHJHJHJHJHJHJHJHJHJHJHJHIGJHIHIGJHJHIGIGIGIGIGIGHFHFHFHFHFHFHFHFHFHFGEGEHFHFHFHFGEGEGEGEGEHFHFGEHFHFGEHFHFHFHFHFHFHFHFHFIGIGIGIGIGIGIGIHJHJHJHJIJIKIKIKIKIKIKIKJLJLJLJLKMKMKMKMLNLNLNMNLNMNMNMNMNMNMONPOPOPOPOPOQPQPQPQPRQRQRQSQRQSRSRTRTSTSUTUTVTVTVUWUWVWVXVXVXWXWYXZXZY[Y[Z[Z\[\Z][]\^\^]_]_^`^`_`_a`bacacbcbdcecedfdfegehfigihihihkjkjljmkmlnlnmonpopoqprqrqsrtstsutvuwuwvxwywzy{y|z|{}|~}~HHIHIIJIJJKKLKLLLLNMNMONNNOOOOPPQQRRRRSSTTTTUUUUWVWVWWWWXXXXZYZZ[[\[\\^]^]`_``a`aabbcbcbcbccddedfegfhgihiijikjlklknmnmonpoppppqprqssttuuvwÀxyŀz{ƀz{Ȁ|}ɀ~ˀ̀̀̀΀΀ππҀҀҀӀӀՀՀՀՀ׀؀ـ݀܀܀݀݀䀕〓—〘倛瀠ꀣ쀢퀥񀡡񀤥񀣤쀚通通逜耜瀚耚耜逛耙瀙怘䀕—‐ဏ߀܀ۀڀۀڀ׀׀׀րրՀҀрЀ~~΀}~̀||ˀ{{ɀ{{ɀzzȀyyȀxxǀwwƀvvĀuuttrrqqppppopnnmmmmllkklkkkkkjihhgfededdcbbaaaa```_^^^]]]]\\[[[[ZZZZZYYYYWWWWVVVVVUVUVUUUTTTTSSRRRQRQQQQPOONNNNMLMLLLLKKKKJJIJIIIIHIHHGHGGF~~}}}{|{|{{z{yzxywywwuvtusustrsqrqqpqoqopoonomnmmllkljljkijhjhihihhfgfgefdfdedecdbdbcacabab`a_a``_`^`^_]_]_]^\^\]\\[\[\[[Z[ZZYZYZYZYYXYXYWYWXVXVXVWUWUWUWUVTVTUTUTUSUSTSTSTSSRSRSQSRSQRQRPRQQPQPQPQOQOQOQOPOPNPNPNONONOMOMOMNMNMNLNLNLMLLKMKMKMKLKLKLKLJLJLJLJKIKIKIKIKIKIKIKIKIKIJIJHKIJHJHJHJHJHJHJHJHJGJHJHIGJHJHJHJHJHJHJHJHJHJHJHJHJHJHJHJHJHKIKIKIKIKILIKIKILJLJLJLJLJLJLJMKMKMKMKMKMKMLMLMLNLNLNLNLNLOMONOMONOMPNPNPNPNPOPOPOQOQOQOQPQPQPQPQPRQRPRQSRSQSRSRSRSRTRTSUSTSUSUSUSVTVTVTVTVTVUVUWUWUWTWUWUWVWVXVXUXVXVXVYWYWYWZX[Y[Y[Z[Z\Z\Z\Z][\[][][][][^\^\^\^\^\_]_]_]_^_^`^_]`^_]`^`^a_a_a_a_a_b`b`b`b`b`b`b`b`b`b`b`b`cacab`c`cadacadadadadbdbebebecececececececfdfdfdfdfdgegegegdgegegehehehfhfifhfhfifigifigigjgjgjgigjhigigjgjgjgigjhjgjgjhjhjhjhjgjgjgjgjhjgjgjgjhjhigigjgjgjhjhigjgigigigigifigighfhfhfhfhfifhfhfhfgegegehegegegegefdfdfdfdfdfdfdebebecececdbebdadbdadbdbcacacacac`c`cab`b`b`a_a_a_a_a_`^`^`^`^`^`^`^`^`^`^`^_]_]_]_\_]^\^\^\^\^\^\][][][\[]Z\Z\Z\Z\Z[Z[Z[Y[Y[YZXZXZXYXYWZWYWXWXVXVXVXVXVWUWUWUWUVTVTVTVTVSVSUTUTUSUSUSTSTRTRTQTQSQSQSQSQSQSQRPRPRPRPRPRPQOQOQOQOQOPOPNPNPNPNOMOMONOMOMOMOMOMNLNLNLMLMKMKMLMLMKMKLJLJLJLJLJLJLJKIKIKIKIKIKIKIJHKIJHJHKIKIJIJHJHJHJHIGJHJHIGIGIGIGIGIGIGIGIGIGIGHFHFIGHFHFHFHFHFIGHFHGHFHGIGIGIGIGHGHGHGHGHGIGIGJHIGJHJHJHJHJHJHJHJHKIKIKIKIKIKIKJKJLJLJLKLKLKLKMLMKMKNLNLNLNMOMOMOMONNMONONONPOPOPOQOQPQPRPRQRQRQRQSRSRSRSRSRTSTSUTUTUTVTVUVUWUWVWVXWXWYXYXYXZYZYZY[Z\Z\[][]\]\^]^]_^`^`^a_a`a`bacbcbcbdcededfefegfhfhghgihjikikjljlknlnlnmononpoqprqsrsrtsutvtvuwvxwxwzxzyzy{{|{}|~}~}~HGHHHHJIJIKJKKLLMLMMNMONOOOOPOPPQQQQRRSSSSTTUUUUVVWWXXXXYYYYYYZZZZ\\]\]]]]^^__````aacbcbdcdcddeefegfhghhiijjjjkkllnnooooppppqqrrssttuuvv€wwÀxxŀyyƀzzǀ||ɀ~~ˀ΀ЀЀЀЀрҀӀҀԀՀր׀؀ــــڀ݀ހ߀ထ–〕怗耘耙瀛瀜ꀣ퀧񀥥񀣣󀧧򀨨󀪩󀨨󀣢󀣣򀥦󀦧񀞞퀝쀜쀞뀜逞퀞퀞퀝쀜ꀝꀚ這耙瀖怖倕–ހ߀܀ڀۀڀڀــ؀׀ԀӀЀπ~̀~~̀||̀{{̀zzˀyyȀwwƀvvĀttÀss€rrrrqqppppoooonnmmmmmmmmkkjjihgggfeeddccbbbbaa``___^^]]]]\\[[[[[[ZYYYYXXXXXWWWWVWVVVVVUUTTTTSSSSRRRQQPPOONNMNMMMMLLLKKKKJJJJJJIHIHIHHGGGGF~~|~|}{|z{zzyyxxwxvwuvtusustrtrsqrprpqopnpnonnmmlmlljkjjikijhjhighfhfgffefefdeddcdcdbbacabab`a_a_`_`__^`^_]_]^\^\]\][\[\[\Z[Z[ZZYZYZXYXXWXWXWXWXWXWWVWUWUVUVTVTVTUTUTUSUSTSTSTRTRSRSQSRRQRQRQRPRPRPQPRPQPQOQOPOPOPOONONONONOMOMNMNMNLNLNLMLMKMKMKMLMKLKLKLJLJKJLJLJLJLJLJKIKIKIKIKIKIKIKIKIKIKIJIKIKIKIKHKHKIKIKIKHKIKIKIKIKIKIKIKIKIKILJLJKILILJLJLJLJLJLKMKLJLJMKMKMKMKMKNLNLNLNLNLNLNLOMNLOMOMONPNPNPNPNPNPNPOPOPOQOQPQPQPQPQPRPRPRPRPRQRQSQSRSRTRTRTSTRTSUSUSTSUSVTVTVTVTVTWUWUWUWUWUXVWVXVXVXWXWYWYWYWYWYWZXZXZXZXZXZX\Z\Z][][][][][^\^\^\^\^\_]_]_]_]_^`^`^`^`^`^a_`^a_a_a_b`b`b`b`b`cab`cacacacacadbdacacadbdbdaebebdbdbebecebecfcececfdfcfdecfdfdgegefdgegehfhehfhehfhfifigififigifjgjgjgjgjhjhjhjhjhjhjhjhjhjhjhkijhkikikikikililililililililikikikikikikikhlilikilikiljliljkikikhkikhkhkhkhkhkhjhjhjhjgjgjhigigjgigigigififigigifhfhehegegegegegegefdfdfdecececececebecebecebdadbdadacadbcacacab`b`b`b`b`b`b`b`a_a_a_a_a_a_a_`^`^a_`^_^_^_^_]_]^]_]^\^\^\^\^\^\]\][][\[\[\[\Z\Z[Y[Y[Y[Y[Y[YZXZXZXZXYWYWYWXWXVXWXVXVWUWUWUWTWUVUVTVTVTUTUTUSUSTSTSTRTRTRTRTRTQTQSQSQSQRPRPRQRPRPRPRPQPRPQOQOQOPNPNPNPNONPNOMOMOMOMOMNMNLNLNLMLMLMLMKMKMKMKMKLKLJLKLJLJKJKJKJKJKILIKIKJKIKIKIKIKIJHKIJHJHJHJHJHJHJHJHJHJHJHJHJHJHJHJHJHJHIGIGIHIGIGIGIGIHJHIGIGIGJHJHIHJHJHJHJHJHJHJHJHKIKIKIKILJKIKILJLJLJLJLJLKLKMKLKMKMLNLNLNLNMOMONNMOMONONPOONPOPOPOQOQPQPQPQPRPQPRQRQRQRQSRSRTSTSTSTSUSUTUTVTVUVUVUXVWVXVXWYWYWZXZXZYZY[Y[Z\[\[][]\^]^]^]_^`_`_`_a_b`bacbcbdbdbdcecfdgegfhghgihihjikjkjljljmknlomononpoqprpsqtsututvuwuwvxwyxyxzy|z|z}{~}}|}~IHIHIIJJJJJJLKLKLLMLNMNNOOOOPPPPQQQQRRSRTSTSUUVUVVWWXXXXYYYYZZ[[[\\\\\]]^^____````aabbcbdcedeefefeffffhhiikjlklkmmlmnnoopqqqrrsssstsvuwwÀxxĀyyŀyyǀ{{ǀ||ɀ}}ʀ}}ˀ̀ЀӀҀҀӀՀՀԀԀ׀؀ـڀ܀݀ހ݀ۀ܀߀—〕䀔倓倖瀖這쀜퀝뀠쀡􀩩󀦧󀪫񀡢򀟠򀢢򀠡񀢤󀣥󀢤󀡢񀠠쀛耛瀙倗倕䀔倕怑‐ဏހ݀݀܀ۀۀڀ׀ՀԀӀҀр~~π}}π||̀{{ˀyyȀwwȀvvƀvvƀuuĀuuĀtt€sr€qqqqppppoooooomnmmkkjjihhhggffeedddcccbba`a```__^^]]]\\\\[[[ZYZZYXYXXXXXXWXWWVVVUUUUTTSSSSRRQQQQPPOOOOONMMMMMLLLLKKJKJJJJIIIIHIHHGGFGFFF~~}~|}||{{yzxzxywxvwuwuvtvtustrsqrqrpqpqopnomnmmlmlmklkkjkikijiighghggfgffefdfdeddcdcdbcbcab`caa_a_`_`^`^`^^]_]^\^\][][\[\Z\Z[Z[Y[YZYZYYXYXYXXWXWXWXWWVWVWVWVWVWUVTVTUTUTUSUSTSTRTRTRSRSRSRRQRQRQSQRPRPRPQPQPQOPOPOPNPOONONOMOMOMOMOMOMNLNLNLNLNLNLMLMKMKMKMKMKMJLKLKLJLJLJLJLJLJLJLJLJLILJLIKILJKILJLJLJLJLJLJLJLJLJLJLJLJLJLJLJLJLJLJLJLJMKMKMKMKNLMLNLNLNLNLNLNLNLNLOMNMNMOMOMPNPNPNPNPNPOPNPOPOQOQOQPQPQPQPQPQPRQRQRQRQSQSQSRTRTRTRTRTRUSUSUSVTUTUSVTVUVUWUVUVUWVWVWVWVXVXVXVXVYWYWYWYWYWZXZXYXZYZXZX[Y[Y[Y[Z[Z\Z\Z^\^\^\^\_]_]_]_]`^_]`^`_`^`^a_`^a_a_a_a_a_a_b`b`cacacadbcadbdbdbdbdbdbdbebebebfcfcfcfcfdfdfdfdecfdfdfdgdfdfdfdfdgegehegfgfhfififhfhfigigigigigigigjhjhjhjhkikhkhkikikilikililikililjljkiljljljmjljljmkljljmkmkmjmknkmkmkmkmkmknkmkmkmkmjmjmkmkmkmkmkljmkmkmjljmkmkmjljljljljmjljlililililjlilikikikijhjhjhjhjhjgjhjgigififigifhehfhfhffdgegegegefdfdfdfdfdfdfdedecececebdbecdbdbdbdbdbcacacadbcacacacacab`b`b`b`b`b`b`b`a`a`a_`_`^`^_^`^`^_]_]_]_]^]^]][^\][][\[\[][\Z\Z\Z[Y[Y[YZYZY[YZXZXYXYWYWYWYWXVXVXVXVXVWUWUWUVUWUVTVTVTUTUSUSUSUSUSTSTRTRTSTRSRTRSRSRSQSQSQSQRQSQRPRPRPQOQOQOPOPOPOPNPNPOONPNOMOMOMOMOMOMNMMLMLMLMLMLMLMKMKMLMKMKMKLKLJLJLJLKLKLJKJLJLJKIKIKILJKIKIKIKIKIKIJIJIJIJIKIJIJIKIKIJHJHKIKIJHJHJIJHJHJHJHJHJHKIKIKIKIKIKIKIKIKIKIKILJLJLJLJLJLJLJLJLKMKLKMKMLMLNLNLNLNMOMNMOMOMONONPOPOPOPOPOQOQPQPQPRPRQRQRPRQSQSQSRSRTRTRTSUSUTUTVTVTVTVTVUWUWVXVXWXWXWYWYXYXZX[Y[Z[Z\[\[]\]\^]^]^]_^_^_^`_a`a`babacbcbdbecedfdfdgegfhgigihihjikiljlkmknlnmnmpnpoqpqprqsqtrusutvuwvwvxvywyxzy{z|{}{~}}|~~HHIHIIIIJJKKKKLLLLMLMMNNOOOOPPPPQQRRRRRRSSTTUUUUVVVVXWXXYYZY[[[[\[\\]]]]^^^_`___`_a`babbdcedfegfgggggghhiijjkjlkmlmmmmmmooppqrsstttuvvÀwwÀxxƀxxƀyyȀzzɀ{{ʀ{{ʀ}}̀~~̀ππрԀր؀׀׀؀؀؀ۀۀ݀ހ‐–䀓】ဒ〔倖瀗耗逖耗這쀜򀨨󀮰􀯰[[[[[[󀥦󀢤񀤥񀧧򀤤񀢢뀝뀛ꀗ耘逘耕瀒們䀓‒߀ހ݀܀ۀ؀؀׀ՀԀӀҀЀ||΀{{̀zz̀yyˀyyɀxxǀvwƀvvŀuuĀuuÀssrrrrrqssrrppnnmmllkkjjihghggfffeddddcccbbaa``__^_^^]]\\\\[[Z[ZZYZYYYXXXXXXWWVVVVUUUTTTSSRRRQQQPPPPPOOONNNNMMMMLLLLLLKKJJIIIIIIIHHGHGGGFF~~~}|}||z|zzyzxyxxwxvwuvuutustrtrsqsqrpqopnomnmnmmlmklkkjkjjijhihighghfgfhfgffdececdcdbcbcacabab`a`a_a_`^`^_]^]^]^]^\]\\[\[\Z\Z\Z[Y[Y[YZYYXYXYXYWYWXWXWXWXWWVWVWUVUVUVUVTVTUTUSUSUSUSTSTSTRSRSRSRSRRQRQRQQPQPQPQPQPQPPOQOPNPOONONONPNONOMOMOMOMOMNLOMNLNLMLNLNLMKMKMLMLMLMKMKMKMKMKMKMKMKMKMKMKMKMKMKMKMKMKLJMKMKMKMLMKMKMKMKMLMLMKMLNLNLNLNLNLOMOMOMOMNMOMOMONPNONONPNPNPNPOPOPOQOQOQOQOQPQPQORPRQRQRQSQSQSQSQTRTRTRTSTSTSTSUSUTUTUTUTVUVTVUVUWUWUWUWUWVWVWVXVXVXVXVXWYWYXYWYWYWYXZX[Y[Y[Y[Y[Z[Z\Z\Z\Z\Z\Z\[][][]\_]_]_^_]`^`^_^`_`_a_a_a_a_b`b`b`b`cabab`dbcadbdbdbdbdbecececfcfcfcfdfdfdgdgefdfdgegegegegegegegegegegegehfhfhfhfififhgjhjhjhjhjhjhjhkhkikikikilililjljljljmjmjljljmjmkmjmknknkmknknkolnkomolnlnlnlnlnlolololololololompmomnlolomololololomololomololnlololololnlnlnlnmnlnlnknlnknkmkolmknkmkmkmjmjmjmjliljmjljlikikikikikikhkhjhjgjgigigighfhfhfhfgegegegegegfgfgegegefdfdfdfdfcecececebecebecdbecdbdbdbdbdbdbcadbcab`b`b`b`b`b`b`a_a`a_a_`_`_`^`^`^_]_]_]_]^\^\^\^\]\][\[\[\[\Z\Z\Z[Y[Y[Y[YZYZXZXYXYXYXYWYWYWYWXVXVXVXVXVXVWUWUWUVUVTVUVTVTUTUTUTUSUSTSTSTSTSTSSRSRSRSQSQRQRPRQRPQPQPQPQPQPPOQPPOPOPNPOONPNPNPNONOMNMNMNMNMNMNMNLNLNLNLNLNLMKNLMLMLMLMKMKMKMKMKMKLJLJLJLJLJLJLJLJLJLJKJKJKILJKJKILJKJKJKJKIKIKIKJKJKJKJKILJKILJKIKILJKJLJLJLJLJLJLKLJLKLKLKMKMKMKMKMKMLNLNLNMNMOMNMONOMOMONPNPNQOQOQOPOQPQPRQRPRPRQRQRQSQSQSQTRSRTSTSUSUSUSVTVTVUVUWUWUWUWVXVXVXWYXYXZXZYZYZY[Z\[\Z][\[]\]\^]^]^]_^_^`^a_a`a`b`cacadbdbececfdfegegfhghgihjhjikjljljmkmlnmononpoqorprqsqtstsutvuwvwvxvywzx{yzy|{}|}|~}~~IHIHIIJJJJKKLKLKLLMMMMNNNNOOPPPPQQQQRRSSSSTTTTTTVVWWWWXXYXYY[Z[[\\]]]]^^^^__`_``aaaaaabbcbededfegfhhhhhhihjjkkllmmmmnnooooppqqrrtuwwÀxxŀwwĀxxƀxxǀyyǀyyȀ{zʀ||̀}}΀~~΀ЀрӀԀӀՀ؀܀܀ۀۀۀ܀߀〓〖怗瀗耗瀕倗怖瀕逘뀙쀚쀚쀛퀝􀟠􀡢􀤥󀨩YZ\[\\^^_`^_]]\]\]\^[]򀡡쀘뀕耕瀕倓䀐『『‏݀܀ۀڀ؀׀րրӀрр~Ѐ}~΀z{̀yyˀyyʀxyȀwwǀwwǀvwŀvvŀuuĀuuÀuuttrrppnonnmmlljjjjiihhfgfgeeeedddcbbaaa`____^^^]]]\\[[[ZZZZYYYYYXXWWWWWWVVUTTTTSTSSRRRRQQQQPPPPPONNNNNMMMMLLLLKKKKIIIIIIIHHHHHGGGFGFFEFE~~}|}|}{|z{y{yyxxwxwwvwuvtuststrrqrpqpqopoonomonnmmllkljljkijhjiihighggfgegefdfdedececdbdbcacababaa`a``_`__^_^^]^]^]^\^\][\[\[\Z\Z\Z[Z[Z[YZYZXZXZXYXYWXWXWXWWVXVWVWVWVVUVUVTVTVTVTUTUSTRTRTSTSTRSQSQSQSRRQRQRQRQQPQPQPQPQPPOPOPOPOPOONONPNONONOMOMOMOMOMNMOMOMNMNMOMNLNMNLNLNLNLMKNLNLNLNLNLNLNLMLMLMKMKNLNLNLNLNLNLNMNLNLOMOMOMOMOMONONONOMPNPNPOPNPOPNPOQOQOQOQOQOQOQPQPQPQPRPRQRQRQRQSQSQSRTRTRTSTSTSTSUSUTUSUTUTUTUTVTVUWUWVVUWUWVXVXVXWXWYWYWYWYWYWYWYWYWYXZXZXZXZY[Y[Y[Z[Z[Z\[\[\[\[\[][][][^[^\^\^\_]_]`_`^a_a_a_b`b`a`b`cacacacacadbdbdbdbdbdbececececfcfcfdfdfdgdgegehehfhfhfhfhfhfhfhfigigifjgigigigigigigjhjhjhjhjhjhkikikjkiljljlimjljmjmkmkmkmkmkmkmkmknlmknlnlololololomolpmomompmpnpnpnqnpmqnpnpnpnpnpnpnqnqnpnqnqoqnqoqopnqoqopnqoqnqnpnpnqnqopnompnpnpnpnpnpnpnompnpmomomompmpmomomomololomomnlnlnlnlnlnknknknkmkmkljljljljljlilikijhjhjhjhjhjgjgjgifjgigigighghghghghfgegegfgegegdgdhegdgdfdgefdfdfcfdececececececdbdbdbdbcacacacacab`b`b`b`a_a`a_`_`^`^`^`^`^_]_]_]^\^\]\]\]\]\\[][\Z\Z\Z\Z\Z\Z[Y[YZYZYZXZXYXYXYWYWYWYWXVXVXVXVXVXVWUWUWUWUVUVUVUUTUTUTUTUSUTUSTSSRTRTRSRSRSQSQSQSQRQSQRPQPRPQPQPQOPOQOQOQOPOPOPNPNPNONOMONONOMOMOMOMOMNLNMNMNMNMMLNMMLMLMLMKMKMKMKMKMKMKMKMKMKMKLJLKLKLJLJMKLJLKLKLKLJLJLKLKLKLKLKLJLKLJLJLJLJLJLKLKLKLKMKMKMLMLMKMLMLNLNLNLNLNLNMNMOMOMONONONPNPOPOPOPOQPQPQPQPQPRQRQRQRQRQSRSQSRTRTRTSUSUSUTUTVTVTVUVUVUWUWVXWXWYWYXYXYXYXZY[Y[Z[Z[Z\[][\[]\^\^\^\_]_^_^`_a_a`b`b`cbcacbdbdcececfdfegegehghgihihjikjkjlklkmlnmonpopoqprprqsrsrtsutvuwvwvxwyxzyzy|z}{}|}|~~HHIHJIJJKKKKLKLLMMMMNNOOOOPPPPPPQQRQSSSSTTUUUUUUWVWVXWYXZYZZ[[\[]]^^_^____`_````aabbbbccddeefegfgghhiijijjkkllmmnnnnooppqqqqrsssttvvÀxxǀ{zɀzzǀ{{ɀzzɀzzʀ{{ˀ{{̀}}π~рӀԀԀր׀׀ـۀހ߀߀ဒ—倔䀕倖怗耛쀛쀛퀛쀚쀙퀚򀠢YZZ\Y[[\]]]]___^^]_^aabccdab_`^__a_b]^Z[XYWYYZXYXX􀞡򀝟񀜝뀘逕瀔逑耑耒瀑䀏ހ݀݀݀܀ۀـրՀԀԀр~Ѐ||΀{|΀z{̀zz̀zzˀzzɀyyȀyyȀxxƀxxŀvvĀttÀrrqqppnnmmlllkkkjjiihhggffeefeedccbbaaa```__^^^]]\]\\\[[ZZZYZYYXYXXWWWVVUUUUTTTTTSSSRRRRRQQQQPPOOONNMMMMMMLLKKKJJJIIIIIIIHIHHGHGHGGFFF~~~|}{}{|z|zzyyxyxxwwvvuvuuttstssrsqrprpqopoonomnmmlmkmlljljkikiihighghfhffegefdededececdcdbcacacabaa`a`a``_`^_^_^_]^\]\]\]\][\[\[\[\Z[Z[Z[Z[Z[Y[YZXZXZXYWYWYWYWXWWVWVWVWVVUWVVUVUVUUTUSUSUSUSTSTRTRSRSRSRSRRQRQRQRQRPRPQPRPQPQPQPQPPOPOPOPNPOPNPOONPNPNONONOMONONOMOMOMOMNMOMOMOMOMOMOMOMOMNMOMOMOMOMOMOMOMOMOMONPNOMPNPNPNPNPNQOPOPOQOQOQPRPRPQORPRPRQRQRQRQRQRQRQRQRQSQSQSRSQTRTSTSUSUTTSUTUTUTVTVUVUVTVUWUWUWUWUWVWUWVXVWVXVXVXWXWYWZXYXZXZXZXZXZY[Y[YZY\Z\Z\Z\[\[\[][][][][]\^\^\_]_]^]_]_]_]_]_]`^`^b`b`b`b`cacacacadbdbdbecececececfdfdfdfdfdfdgdgegehehehfgfhfhfhfigigigigjhigjhigjhjhkijijhkikikhkikikikjkiljljkjljljmkljmknlnlnlnlnlomnmomomomomomompnonpnpmpmpnqnpnqoqoqoqoroqnqoqorpqorprpqorororpqorpspspsprprprprpsqsqrpspspsprpspsqrprprprprprprprprorprprorprprprprpqoqororoqoqoqnqoqnqnpnpnpnpmpmpmompmomomnmnmomnlmknlmknlmkmkmjljlikiljkikikikikikikijijijijijhjhigjhjhigighfhfhghfhghfhfgegegehegefdfdgdfdfdecfdfdecececdbdbdbdbdbdbcacacab`b`a`a`a_a_a_`_a_`^`^_]_]_]_]_]^\^\]\]\]\][][][\Z\Z\Z\Z[Z[Y[Y[YZYZYZXZXZXYXYXYWYWYWYWYWXVXVXWWVWUXVXVWUWUVUVUVTUTUTUSTSTSTSTSTSTRSRSRTRTRSRSQSQSQRPRPRPRPQPQPQPQPPOPOQOPOPOPOPOONPNPNPNPNONONONONOMNMNMNMNLNLNLNLNLNLMLNLNLMLMLMLNLNLNLMKNLMLNLMKMKMLMLMKMKMLMLMKMKMKMLMKMLMKMKMKMKMLNLNLNLNLNLNMOMOMOMOMOMNMONOMONONPNPOPOPOPOQOQPQPQPRPRQRQRQRQSQSRSRSRSRTRTSTRTRUSUTUTVTVUVUVUWVWVWVWVWVXWXWYWYXZYZYZYZYZY[Z\Z\Z\[][^\^\^]^]_]_^_^`^a_a_b`b`babacbcbdcecededfefegfgfhgihihjijikjljlkmkmlmlononporprqrqsrsrtsutvuwvxvxwyxzy{z{{|{}{~|~}~GGHHIIIIJJJJLKLKLLMMMMNNNNOOPPPPQQQQRRSRSSTTTTUUVVVVWVXXXXYYYYZZ\[\[]]_^`___````aabbbbccccddeeeeffgghhiijjjjkkllmmnnonooppqqrrsstttt€vwÀwwĀxxŀ{{Ȁ||ˀ}}ˀ}~ˀ~~΀}}΀}}΀}~πҀӀՀր؀ڀۀ܀܀ހ‑【〒倔倖怗瀕瀗ꀚ쀛񀟟򀟟򀠠򀞟񀞡򀞡򀠡􀟠􀡢VWY[Z\Z[\\\\^^abcdcbaaddgghigiegcebecfab^_[\[][\YZZ[Z[[[YY򀠡􀛜񀛜쀘쀕쀔뀕뀕途瀓䀐『ဎ‎ဍ݀ۀـ؀׀؀րԀԀ~~р~~Ѐ||΀{{ˀ}}̀||̀zzˀzzʀyyȀwxǀvvƀttŀrs€qqppoonnmmmmlljjjjiihhggggfgeeddccbbbaaa``__^]^]]]\\\[[[[ZZYYYXXWWXWWVVVVVUUTTTTTSSSSRRQQQQPPPPPOOONNNMMMMLLLKKKJJJJJJIIIIIHHHHGGGGFFF~~~~}}||{|z|z{yzxzxxwxwvuvtuttstssrrqrqqpqpqopnomnmnmmllkljljkjjikijhihhghfgfgfgefefefdecdcdccbcbcababaa`a`a_`_`^_^_^^]^]^]^]^\]\][\[\[\[\[\Z\[[Y[ZZYZYZYZXYXZXYWXWXWYWWVWVXVWVVUWVVUVUVTVTUTUTUTUTTSTSTSTRTRSRSRSRSRSQRQRQRQRPRQRPQPRPQPQPQPQOQOPOQOPOPNPOPOPOPOPNONONONONONPNONPNPOPNONPNPNPNPNPOPOPOPOPOPOQOQOQOQOQOQOQOQORPRPRPRPSQSQSQRPSQSRSQRQSRSRSRTRSRSRTSSRUSUSTSUSUTVTVTVTVUVTWUVUWVWUWVXVWVXVXVXVXVXVXWYWYWYWYXYXZXZYZY[Y[Y[Y[Z[Z[Z\Z\Z\Z\[\Z\[][^\^\^\^\^\^\^\_]_]_]_]_]_^`^`_a_`_a``_a`a_cacbcadbdbdbecececececfdfdfdfdgegegehegfheifhfhfigigigigjhjhjhkhkijhkhkikikiljljljljmjmjmjljmkmkmkmkmkmknlnlnlnknlolomomompmpmompnpnpnpopnpnqoqoqoqorproqoqorprprprpsprpsqsqsqsqsqsqsqtrtrtrtqtqtrtqtrururtrtrtrtrurtrtrtqtrurtrurtrtrtrtrurtrustqtqtqtqtqtrtrsqtrsqrpsqsqtrsqsqsqsqsqrprprprprprprpqorpqoqorpqoqopnpnpnpnomomomomomnlnlomnlmkmkmklklklklkljljmkljljljljkikikikijhjhihjhjhjhigjhigjhigigigigifhfifhehehehegegegefdfdfdfdecfdecdbdbdbdbcacacacacabab`b`b`a_a_a_`^`^`^_]`^_]_]_]^]^]^\]\][][][][\[\Z\Z\Z[Z\Z[ZZY[Y[YZYZYZXZXZXYXYXYXYXYWXWXWXWXWXVXVWUVUWUVTVUUTUTUTUTUTTSUTTSUSUSTSTRTRTRSRSRSQRQRQRQRQRQRQRPRPQPRPQOQOQOQOPOPOQOPOPNPNPNPNPNOMPNONONOMNMOMOMOMOMNMNMNMNMOMOMNMNMNMNMNLNMNLNMNMOMNLNLNLNLNMNLNLNMNMNMOMOMOMOMOMOMOMONONONPNPNPNPNPOPOPOPOQPPOQPQPQPQPRPRPSQSQRQSRSRSRTRTSTRTRTRUSUSUSUTUTVTVTVUWVWVWVWVWVXWXWYWYWYXZXZYZY[Y[Y[Z\Z][][]\^\^\^]^]_]`^`^`_`_`_a`b`b`cacbcbcbdcecfefegfgfgghgigihjhkikikjlkmlmlnlnmpnoopprprqsrtrtsutvuvuwvxwyxyxzy{z{z||}|~}}~HHHHIIIIJIKJKKLKLLMLMLNNNNOOPOQPQPQQRRRRTSTTTTUUUUVVWVWWWWYXZYZZ[[\[]\]]^]_^``aabbcbccdddeeeffffffgghhhiijkkllmmmmnnooppqqqqrrsstt€uuÀuuĀwwƀxxǀyzȀz{ʀ|}ˀ~̀΀΀ЀрҀӀր׀ـۀ݀߀߀ဏ】倒倒怕耗這ꀙꀙ뀙쀜񀟠򀢥TUTUUWWXXXXYWXYZ\]_`_``aceghfeggjjjkjlilikgihjgiefac_a^_]^\\\\]]]\[ZYYYZYZXYYYYZXX󀝜󀚙񀘗쀗途瀒怒瀑怐【‎ဌ߀ހ܀ۀ݀ۀـրՀҀ~~Ѐррр~~Ѐ|}̀{{ˀyyʀwxɀvvǀuuĀssÀrr€qqqqnnnnnnmmllkkjjjjiihhggffeeddcccbba```___^^^^]]\\\[[[[ZZYYYXWXWXWXWVVUUUUUUUTTSSRSRRRQQQQPPPPOONMNMMLLLLLLLKKJJJJJJIIIIIHIHHGHGGFFFFE~~~}~}}|}{|{{yzxxwxwxwwvvuututtstssrsrrqrppopopnonnlnlmlmklkkjkikijhjhigighfhfgefefefeededdcdccbcababab`b`a`a`a_`_`_`^`^_]_]_]_]^]^\]\]\][\[\[[Z\[[Z[Z[Y[Y[YZXZXZXZXYWYWYWXWXWWVWVWVWUVUWVVUVTVTVTUTUSUSUSTSTSTSTSTRSRSRSRSQSRSRSQSQRQRQRQRPRPRPRPQPQPQOQPQPQOQOQOPOPOQOQOQORPRPQPQORPRPRPQOQPQPQPQPQPQPRPRPRPRQRQRQSQSQSQSQSQSRSRSRSRSRTRSRTRTSTRTRUSTSUSUSUSUSUTVUVUVUVUWUWVWUWVXVXVXVYWXVXWYWXVYXYWZXYXZXZXZY[YZY[Y[Y[Z[Z\Z\Z\Z\Z\Z\[][][][]\]\]\^]^\^\_]_]_]`^_^`^`^`^a_a_a_a_a_b`b`b`b`b`cacaecececfdfdfdfegegegegegegehfhfigigigigigjhjhjhjhjhkikikjkiljljljljljmjmkmkmknkmknlnknlnlnlolomononomomomompnpnpnqopnqoqorprprprprprpqprprqrqsqsqsqrqrqsqtqsqsqsqtqtrtrtrururusususususususususususususwtvtwtususvtvtvtvsvtusvtvtvtwuvtvsusvtvtwtwtvtvtususutvtutututusvtustsususustrtrtrtrtqtrusussrtrtrsqsqsqrpspsprprproroqoqoqoqopnpnpmpnomomomomnlnmnlnlnlomnlnlmlmlmllklklkljljljljkjljkjkikikikijhjhjhjhjhjhjhigigififhfhfgehfgegegegegefdfdfdececdbecdbdbdbdbdbcbcab`b`b`b`a_a_a_a_a_`^`^`^`^_]_]_]^]^\^\]\^\^\]\][\Z][\Z\Z\Z[Y\Z[Z[Z[Y[YZYZYZYZXZXYXYWYWYWXWXWXVXVWUWVWUVUVUVUVUVUVUVUUTUTUTUSUTUSTSTSTRTRTSSRSRSRSRSQSQSQSQSQRPRQRQQPRPQPQPQPQOQOPOPOPOPOPOPOPNPNPNPNONPOPNONONONONONONONONONONONOMONONONOMOMOMONONONOMONPNOMPNPNPNPNPNPNPNPOPOPOQOQPQPPOQPQPQPRQRQRQRQRQSRSRSRSRSRTRTRTSUSUSUSUSUTUTUTUTVUVUVUWVWVWVXVYWYXYWYWZXZXZXZY[Y[Z[Z\Z\[\[\[]\^\^]_^_^_]_^`^`^a_a_a_a`a`bacacbdcdcededfegefegfhfigihjijikikiljlkmlnlnmononpnqorqrqsqtrtsututwuwvxwxwyxzyzy{z|{}|~}}~HHHHIIIIIIJJKJLKLKLLMMMMNNNNOOPPPPRQRRSSTSTTUUUUVUWVWVXWXXXXZY[Z[[\[\\]]^^^^__``babbbcddeeeeffffgghhhhiijkkkllmmnnoooopqqqrrsstttt€ttÀvvŀxxƀyyɀyyˀ{{ˀ{{̀||̀ππЀҀӀԀր׀ـڀ݀ހ߀‑】倐䀑倓瀓瀔耖쀗뀚퀜򀝞󀠡UVVVXXUVVWXYYZYZZ[[]^`abdeefghhhihllopnomompmokljkklijdebc`a_`^^_____^\[[[[\\\\\[\[[ZZXXVV񀙚񀙛퀕ꀔꀗ뀔ꀓ耑䀐『‍ဌ߀ހۀـ׀ՀԀՀԀԀ~~Ҁ}}π{{̀zzˀzz̀xxʀwwȀuuƀssĀssÀrrÀrrqqppppoonnmmllkkjjiiggggeeddccbbbaaaa````__^^]]\]\\[[[ZZZZZYYXYXXWWWWVWVVVUUTTTTTSRRRRRQQPPPOONNNNNNMMMLLLLLKKKKJJJJJJIIIIIHHGHGGFGFFE~~}~}}||{|z{yzyyxywxwwvvuvuvtusutsrsrsrrqqppppopnnnnnnlmllklkkjkjkijijhihigighggfgfgefefdededdcdccbcbcababaa`a`a`a``_a_`^`^___^_]_]_]^\^\]\]\][]\\[\[[Z[Z[Z[YZYZYZYZXYXYWYXYWXWXWXWWVWVWVWUWUVUVTVTVTUTUTUTUTTSUSUSTSTRTSTSTRTSSRSRSRSRSRSQSQSQSQSQRQRQRQRQSQRPRQRPRQRPRPSQRQRQSQRQRQSQRQRQSQRQRQSRSQSQSQTRSRTRTRTRTSTSUSTSTSTSUSUTUTUTUTVTUTVUVUVTVUWUVUVUWVWVXVXVXVXWXWYWXVXWYXYXYWZXZY[Y[Y[YZY[Y[Y[Z[Z\Z\Z\Z\Z][][][][]\]\^\]\^\^\^]_]_^_]`^`^`^`^`^`_a_a_a`b`b`b`b`b`b`cacacadbdbcadbdbecfdgegegegehfighfhfififigjhigjhjhjhkikikikikikjkjlklkmkljmkmknlnknlnlnlolomomomompnpnpnpnqnqnpnqnqoqoqoqoqoqorprprprqsqsqsqsqsrtrsrtrtrtrtrustrususurususususususvswtusvsvswtwtvsxuwuwuwuxuxuxuxuvuvuvuxuxuxuxvxuxuxuxvxvxuxuwuxuwuxuxuxuwuxuxuxuwuxuwuwuxuwuwuxuxuxuwuwuwuxuwuwtwuwuwuwtwtwtusvtvtuswtususususurustrtrtrtrsqsqsqsqrqrpsprprpqoqoqopopnpopopnpnpnononpmomononnlnmnlnlmlnlmkmkmkmkmkljljljljlkkjkikikhkhjhjhihjhjhjhigigifhfhfhfgegegegegefdfdfdececececdbdbdbdbcababacabab`b`a_a_a_a_a_`_`_`^_^_^_^_]_]^\^]^\^\^\][][][\[\Z\Z\Z\Z\Z\Z\Z\Z[YZY[YZYZYZYZXYXYWXWXWXVXVXVWUWUWUWUWUWVVUWUVTVTVUUTUTVTUTUSUSUSUSTRTRTRTRSRSRSRSRSRSQSRSQSQRQRQRPRPRQQPQPRPQORPQOQPQOQOPOQOQOPNPOPOPOQOPNPOPOPNPOPOPNONPOPNPOPOPOPOPOQOPOPOPOPOQOQPQPRPQPQPQPQPQPRQRQRQRQRQRQRQSRSRSRTRTSTSTSTRUSUSUSUSUTVUVUVUVUVUVUWVWVWVWVXVXWXWYWYXZXZXZY[Y[Y[Z[Z[Z[Z\[\[]\]\^]^]^]^^____`_`_`_a`a`babacbbacbdcdcededfdfegfgfhfigigjhjhjijikjlkmknlnlompnpnpoqprqsrsrtrtsutvuwvxwywywzy{z{z|{}|}|~}~HGHHIIJIJIJIJJKKKKLLMMMMNNNNPOOOPPQQRQSRSSTTUUUUVVVVXWWWXXYYYYZZ[Z[[\\]]^^_^__``a`bbccccddffffggghghhiijjjjjllmmmnopppqqrrsstttt€ttÀuuĀvvŀwwǀxxȀzzʀ{{ˀ}}̀}}π~~ЀрҀӀԀԀր؀ڀ܀݀ހހ‐【〓怕耖逖ꀕ逕쀗򀠠򀡢􀢣UUUUUVVVVVWWXXXXZZXXXYZ[]^^_^`^`_aefghgiikkkoossuwuwtvqsproommnmkkfgeecdabbbaabba`^^^^^_____]^__]]Z[XXVWVVVVVW􀛝򀙛퀘쀕逕耓耑瀏倐倐䀎」ညހ܀ڀڀـڀ׀׀Ԁ~р}}π}}΀||΀zz̀yyʀwxɀvvȀuuƀttŀttĀss€ss€qqppppoonnmnlljkjjhighffeeddcccccbbaba`__^_^_^^]\\\\[[[ZZZZZYYXXXXXXWWVVVVUUUUTTSRSRRQQQQPPOOOOONNNNMMMMLLKKLLKKKKJJIIIIIIIHHHHGGFGFFEFE~~~}}||{{zzyzyzyyxyxxwwvwvvuvuuttssrsrrqrpqpqppoonomnmnlmllkljljljkijijijiihhghghfgegfgefdededdcecdbcbcbcababababaa`a`a``_`_`_`__^_^_]^]^]^\^\]\]\][\[\[\[[Z[Z[ZZYZYZXZXZXYXYXYWYWXWXVXWXWXVWVWVWUWUWUVUVUVTVTUTUTUTUSUTTSTSTSTSTSTSUSTSTRTRTSSRSRSRTRSRSRSRSRTRSRSRSRSRSRTRSRTRTRTRTRTRTRTRTSUSUSUSUTUSTSUSUTUTUTUTVTVTVTVTVTWUWUWUWUWUWVWVXVXWWVXVXVYWYWYWYXYWYXZXZXZXZXZY[Y[Y[Z[Z\Z\Z\Z\Z\[][][][][]\]\^\^\^\^\_]_]^]_]_]`^`^`^`_`_a_a_a_a_b`b`b`b`b`b`cacacadbdbdbdbececececececfdfdhfhgigigigjhjhjhjhjhjhjhkiljljljljlklkmkmknkmknlnmomolomomonononpnpnpnpnqoqoqorproqprprprprqrqsqsqsqsqsqsrsqtrsrtrsrusurusususususvsvsususvsvsvuvuxuxuvtwuwuxuxuxuxvxvxwxvxvxwxvxvxwxwxwxwywywzxzxzwywzwzxzxzxxwzxzxzxzxxwxwzxzxzyzwxwxwzwzwzwzxzxzxzwzxzxzxzxzwzwxwxwzwxwxvyvyvxwxuxuxuyvxuxuxuxuxuvuvuxuwtvuvsvsvsusvsusususususustrtrsqtrsqsqsqrqrprprprprprpqpqpqoqppoqopopnpnpnpnomomomomomomnlmlnlnmnlnlmkmklklkljljkjljkikikikikhjhjgjgjgigighfhfhfhfgegegegegefdfdecececdbdbdbdbdbdbcabab`bab`b`b`b`a_a_a_a_`^`^`^_^_]_^^]^]^]^]^]^\^\]\]\]\][][][\Z\Z\Z[Z\Z[Z[Y[YZYZXZXYXYWYXXVYWYWXVXVXVWVWUXVWUWUWUWUVUVUVTVTVTUTUSUSUSUSTSTSTSTSTRTSTSTRTRSRSRSRSQRQSQRQSQRPSQRQRQRQRQRQRPRPQPQPQPQPRPQOQPQPQPQOQPQOQOQPQPQPQPQPQPQPQPRQQPQPQPRPRQRQRQRQRQRQSRSQSRSRSRTRTSSRTSTSUSUSUTUTUSVTVTVTVUVUWVWVWVWVXWWVWVYWYWYWYWZXZXZXZXZY[Y[Z[Z\Z\Z\[\[]\]\]\^]_^_^^^`___a```a`a`bababacbcbdbdcececedfdfegfgghghgigihihjijikjlklkmlnmnmomonpoqpqprprrtstsutvtvvvvxwxwyxzy{y{z|{}|~}~}~HHHHIIIIJJKJKKKKKKLLMMNNNNONOOPPPPPPRQRRSSSSTTUUVVWVWWXWXXYYYYZYZZ[[\\\\]]^^_^```aabbbccddeeffgggghhiijjkkkkllmmnnnnppqqqqssttÀttÀvvĀvvŀwwǀwwǀxxʀyyˀzzˀ{{̀}}΀~~πрӀԀԀր؀׀ڀ܀߀‏ဏထ䀔怔瀕逖ꀘ뀙쀙󀟟󀠡􀡣TUTUVWXYWWVWVVVWVWWWWWYYZ[[\[[\][\[\]__abcbcabbcfhkmkmnpstttxx{|z|z}vxuwttppponmkkhhddddeddcddaa^___`aaa_aaabbaa^_[\YZYZXYXXVV􀝞򀛜쀓ꀔ逓耓耐倍߀ހ݀݀܀܀؀ՀԀӀЀ~Ѐ|}πz{΀yz̀wwʀvvȀuuǀuuǀtuǀuuŀssĀssÀrrqqppoonnllkkjjiiiiggffefdeddccbbaaaa``___^^]^]]]\\[[[ZZZZYZYXXXXXXWWVVVVUUTTTTSSRRRQRQQPPPOOOONNNNMMMMLLLKLKKKKKJJJJIIIIHHHHHGHGGFGFFE~~}~|}||{|{|{zyzyyxyxxwxwwvwvututtstrtrrqrqqpqpqppoononnmnlnlmkmklkljjijiihihihhghghfhggfgegefdeeedecddcbcbcbcbcbbababaa`a`a`a``_`_`__^_^_^^]^]^\^\^]]\]\][\[\Z[Z\Z[Z[Y[YZY[YZXZXYXYWYWXWXWXWXWXVXWXVWUWVWVWUWUVTWUVUVTVTVTUTVTUTUTUTUSUTUSTSUSUTUSUSTSTSTSTSUSUSUSUSUSUSUSUSUSUSVTUTVTVTUTVTVTVUVUVTVTVUVTVTWUWVWVWUWUWVXVXVXWXVXWXWYWXWYXYWYXZXZXZXZXZY[Y[Y[Y[Y[Z[Z[Z\Z\[\[\[\[\[]\]\]\^\]\^]^]_]^]_]_]`^`^`^`^`_`_a_a_a_a_a_a`a`bab`cacacadbdbdbdbecdbececfdfdecfdgefdgegegegegehfjhjhjhjikikikjljljljlklklkmlnlnlnlnmomomompnpnpmpnpoqoqoqoqoqorpqpqprpsprpsqsqsqtrsqtrtssrtrtrtrururusususususvsusvsvuvsvuvuvuvuxuxuxvxvxwxvxvxvxvxwxwywywywywywxwzyyw{yzxzx{y{y{yzy{y{y{y{y{y{y{y{y|z|z}{{y|z|z|z|z|z|z{y|z|z|z{y{y{y|{|z|z|z{y{y{y|z{y{y{y|z{y{y|y{y{y{y{y{yzyzxzxzxzxzxzxzxzxxwywywywxvxwxwxvxvxvxuxuxuxuxuwuvsvsvuvuususususustsustssrsrtrsrsqtrsrsqsrsqsqrprpqpqpqoqoqoqoqopnpnpnomomomonomomomnlnlnlmkmkmkmkljljljkjljkjkijijhjijhihigigigighfhfgegfgegefefdfdfdecececececdbdbdbcbdbcbcacab`b`a`a`a_a_`^a_`^`^`^`^_^_^_^_]_]_]_]^]^]^\^\][][][][\[\[\Z\Z[Y[Z[YZYZXZYZXZXZXYWYWYWYWYWXVXVXVXVWVWVWVWVVUVUVUVUVTVTVTVTUTUTUTUTUTUSTSTSUSTSTSTSTRTRSRSRSRSRSRSRRQRQSRSRSQSQRQSQSQSQSQRQRQRQRQRQRQRQSQRQRQSRSRRQSQSQRQSQSRSRSRSRTRSRSRSRTSTSUSTSUTUTUTUTVTVTVTWUVUWUWUWVWVXWWVXWYWYWYXYXYXZXZXZX[Y[Y[Y[Y[Z\Z\Z\[][][]\]\]\]\^]^]_^`_`_`_a``_a`a`bababacbccdcdcecedfdfdfegehfhgihhgihjijikjkjkjlklkmlnmomonpoppqprqrqrrsrttutvtwuwvxwyxyxyxzz{z|{}}~}~HHHHHHIIIIJJKKKKKKLLLLMMNNNNNNOOPOPPQQRRRRSSSSTTUUVVWVXWXXYYYYZZZ[[[[[\\]]]]^^^___``aabbccddeeffgghhiiijjjkkllllmmnnooppppqqrrss€uuÀuuĀwwƀxxǀyyȀyyʀzz̀z{̀{{΀||̀πрҀԀՀր׀ـۀ܀݀߀‒䀓倔怓怓怕逗뀗뀙򀜞󀝞󀞞VWXXXYXYZ[[\ZZYYZ[ZZYYYY[[\\^^^__`__`aabbcceefdfefgiloprrtvwxxzz~z}z|uurrtssrpolkhhgfggggffcdababbcab`bbdbcab_`\][\[\[[[\XYUU󀜞󀚛󀚛󀗘퀖쀖뀔耓怐䀍ဌ​߀ހ܀ڀ؀׀րӀӀ~Ҁ|}Ѐzzπxx̀wwˀwwʀxxʀwwɀvvȀuvƀuuƀttŀrrrrppoommmmllkkjjiihhggffffeeddccbbbbaa``____^^^]]\\\\\[[[ZZZZZYYYYWWWVWVVUUUTTSSTSSSRRRQQPPPOOOONNNNNNMLLLMLLLKKKKJJJJIIIHHHHHHHGGGFGFGFFE~}~|}|}{}|{z{z{yzxyxxwwvvuvuvuvtustssrsrrqrqqpqppoonnmnmnmnlmklkljkjkikijijiihhghghghghggfgfgffefeedededdcdcdccbcbbbbababaa`a`a``_`_`__^_^_^_^_^^]^]^\]\][\[\[[Z[Z\[[Z[Y[YZYZXZXZXZXZXYXYWXWXWXWXWXWXWXWXWWVWVWUWUWUWUWUWUWVWUWUWUWUVTVTVUVTVTVTVTVTVTVTVTVUWUVTVTWUVTVUVUVUWUWVWUWVWUWUWUWVWVWUXVXVXVXVXVXWXWXWXWYWYXZXYXYXZXZXZYZYZY[Y[Y[Y[Y\Z\[\Z\Z][][][][]\][]\]\]\^]^]_]^]^]_^`^`__^`^`^`_`_a_a_a`a`b`baa`b`a`cacacacbcbcbdcececdcecfdfdfdfefegegegfhfgehfigigigigigjhjgkjkjljljlkmkmlmlnlnlnlnlonononpnpnqopopoqopoqpqprqrprpsqsqsqtrsqsrsrtrurtsususususususususvsvuvsvuvuvuvuvuvuxuvuvuxuxwxwxwywxvywywywywywywyw{yzx{y{y{y{y{y{y{y{y{y|z{y}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{~}~}}{}{~|}{}{}{}{}{}{}{~|}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{|z}{}{}{|z|z{y{y{y{y{y{y{y{y{y{y{y{yzyzxzxzxzxzxzxzxywxvxvxvxvxuxuxuxuvuvuvsvuvsusutvtusutusustrustrtrtstrsqsrsqsqrqsqsqrprprorpqoqpqoqopnqopopnomomomomnmnmnmnlnlnlmlmkmkljljljkjkikikijhigjhihigighfgfhfhfgegegefdfdfdecececececdcecdbdbcbcacabab`b`b`a_a_a_a_a_a_`^`_`^`^`^`^_^`^_]_]_]^\^\^\]\]\]\][][][\Z\Z\Z\Z[Z[Z[YZYZYZXZXZXZYYXYXYWYWXWXWXWXWWVWVWVWUWVWUWUVUWUVUVTVUVTVTVTVTVTUTUTTSTSUTUSUSUSTSTSTSSRTRSRTSTRSRSRSRTRTRSRSRSRSRSRSRTSTSTSSRTSTSTSTSTSTSUSUTUSUSUSUTUSUSUTUTUTVUVUVTVUVUWUVUWVWVXWXWXWXWXWXWYXYXZYZX[YZYZY[Y[Z[Z\Z[Z\[\[]\]\]\]\]\^]^]^]_^_^`_`_a_a`a`b`bababbbbcbdcdbdcecedeefefegfgfhfhgigihjijikjkjlklkmkmmnmnmonpopoqpqprqsrsrtstsutvuwvwvxwyxyy{z{z|{|{}|~}~HGHHIHIHIIJJJJKJKKKKLLMMMMNNNNONOOPOQPQQQQRRSSSSTTUUUUVVWWXXYYYY[Z[[\\\\\]]]]^^^__``aaaabbccddeefffghhhhijjkkkkkllmnnoppoppprrssss€uuÀvvŀwwǀxxǀyyʀzzʀzzˀ||̀||΀~~Ѐ~~ЀҀҀՀր׀ـڀڀ܀ހ߀ဎ䀑倔瀖逖ꀖꀕꀖ뀘퀛򀝞򀝞򀟟󀟠VWWXXYZ[\\\\\]]^]^\^]^]^^^]]]]_`aabcacbccecddfefgiijikkmmpuwvxyz{{|}~||zywuyxxwuuppnmlljkjkijdebbccccbccddfdfbc`a]^^_^^]^]^[\XYST􀛛񀙙񀗗퀖뀓耑怏䀎䀍​ဋ܀܀ۀـ؀ՀԀ}~Ҁ{|рz{Ѐyz΀yỳyỳyỳxxˀwwɀwwɀuuǀttŀssĀrrĀpp€oooonnmmllkkiihhhhggffefeeddccbbbbaa``_`_^^^^]]]]]\\\\[[ZZZZYYXXXWWWVVVUVUUTTTSSRRRRQQQPPPPOOOOONNNNMMMMLLLLKKKKKJJJIIIIIIHHHHGGGGGGFF~~~}~}}|}||{|{{zzyzyyxxwxwwvvuvuuttstssrrqrqqpqppopoononnmnmmlmllklkljkjkjjjjijiihihhghggfhggfgfgffefefeededdceddcddccbbbbbabaa`a`a``_`_`_`__^_^_^^^^]^]^\^\^\]\\[\[\[[Z\Z[Y[Z[Y[YZYZYZYYXYXZXYXYXXWXWYWXWXWXWXWXWXWXWXWXWXWWVXWWVXVXVWVWVWVWVWVWVWVWVWVWVWVWVWVWVWVXVXVXVXWXWXWXVYWXWYWYWYWYWYWYWYXZXZXYXZXZXZYZY[YZY[Z[Z[Z\Z\Z\Z\[\[][][][]\]\]\^\^]^]_]^]_^_^_^`^`^`^`_`_`_a`a_a`a`a`bab`bab`cacacbcbcbdbcbdcdcdcdcdcededfdfdfegegegfhfgfhfhghghgigigigjhjijhjhjijikikimknlnmnlomomomonpnpnonpnpopoqpqprprpqprprqsqsqsrtrtrtrtrtrutusususususvuvsvswuwuxuxuxuxuxvxvxwwvxwxwxwxwxwzxywywzxzxywzy{y{y{y{y{y{y{y|{{y|{|{|{|{|{|{}{~}}{}{~}~}~}~}~}~}}~}~}}}~}}}}}}}}}}}}}}~}}}}}}}}}~}}}}}}}}}}}~}}}}{}{}{}{}{}{}{}{}{}{}{{y}{|z{y{y{y{y{y{y{y{y{y{yywyxywywzxywxwxwxwxwxwwuwuwuxuxuxuvuvuwuusvuvuususuststrtrtrtrtrtrsrtrsqrqsqrqsqrqrqqprpqoqoqopopopnpnononomomnmnlnlmkmkmkmkmkkjlikikikijikijhjhighghgighfhfgegegegegefefefeedfdecececdcdbdbcacacacacab`baa`a`a`a_a_a_a_`_`_a_`^`^`^_^_]^]^]^\^\^\]\][\[\Z\[\[\[[Z\Z\Z[Z[Z[Z[ZZYZYZXZYZXYWYXYXYXXWXWXWXWXWXVXVWVWVWUWVWVWUVUWUWUVUVUVUVUVUVUVUVTUTUTUTUSUSUTUSUSTSUTTSUSTSUSTSUSUSTSUTUTUTUTUTUTUTUTUTVTVTVUVUVTVUVUVTVTVTVUWUWUWUWVWVXWXWXVXWXWYWZXYXZXZXZYZYZY[Z[Y[Z[Z[Z\[\[\[]\]\]\]\^]_^^]_^_^_^_^`_``````a`bababacbcbdcdcdcededfefefefegfgfhgihihjijhjikjkkkklkmlmlnmnmononpopoqqrqsrtststsutvuvuwvxwxwyxyyzz{z|{}|}|~}~}IHIHIHIIIIJJJJKKKKKKLLMMMMNNNNOOPOPPPPQQRRRRSSSSTTUTVUVVWWXXYYYYZZ[[\[\\]]^^^_^___````bbbbbccddeeeffgghhiijjkkkkllnnooooqqqrqrrsssĀttŀvvƀwwǀxxȀxyȀyzɀ{{̀}}΀|}΀}~ЀрҀԀՀր؀ڀڀ܀݀߀߀ဌ‍【瀓逗쀚쀛򀞠VVVWWXYYZ[\]^_^_^_^^`aab`b`aabab`abbcdegdedfegghgiikkllnkmnpoqwxz{}~~~}{{|{{ztussqropmolmghefefghfggihjfhdfcdbbab_`^_^_]]Z[VWUVVWUVTU󀚚񀘙퀒ꀑ耐怏倍䀎」​ဈ݀ۀڀ؀׀}~Ԁ|}Ӏ{|р|}Ҁ{|Ѐz{Ѐzzπyz̀xxˀxxˀvvɀuuȀuuƀttƀtsŀqqÀqq€ppoonnmmllkkiiiihhhhggffeeddccccbbaa````__^_^^]]]]\\[[ZZZZZYYYXXXXWWVVUUUTTTTSSSRRRRRQQPPPPOOOOOOONNNNMMMLLLLLKKKKJJJJIIHHHHHHGGGGGFGFFE~~~~}~}}||{|{{zzyzyyxxwxvwvwvvuvuuttstssrsrrqrqqpqppoqoononnmnmnmmlmllklkkjkjjijijijiihihihhghhhggfgffefefeedededdcccdccbcbbbbbbab`babaa`a`a``_`_`__^_^_^^]^^^]^]]\]\][][\[\[\[\Z\[[[[Z[Y[Y[YZYZYZXZYZXYXYXYXYXYXYXYXYWYXYXYWYWXWXWXWXWXWXWXWXWXWYWYWYXXWYWYWYXYWYWYXYWYXYXYXYXYXZXZYZYZY[YZY[YZY[Y[Y[Y[Z[Z[Z\[\[]\\[]\]\]\^\^]^]^]^]_]_]_^_^_^_^_^_^`_`_a``_a`a`a`a`a`babababacbdbcbdcdcdcdcececededededededfdfegegefegehfhghghgihihihihjijijijijikjkjljljlklklklkmkompnpnpnpoqoqpqprprqrqrqrqsrsqtrtstrtrtrusususususututvuwuwuwuwuwuxwxvxvxwxwywywywxwywywywyxyxzxzx{y{y{y{y{y{y|z|z{y|{|{|{|{|{|{|{|{~}~}~}~}~}}~}~}~}}}~}~~~~@?@?@?@?@@A@A?A@@?@@@?A@A@A@A@A@A@@?A@A?A@A@A@@@@@A?A?A@A?A?A?A?A?@?A?A?@?@?@?@?@?A?A?@?}}}~}}~}~}}~}~}~}~}~}~}}{}{|{}{}{}{}{|{|{|z{y{y{y{y{yzyywzyyxywxwxwxwxwywxwxwxwxwxwxwwvvuvuvuwuwuvuvtvtvtutusuttsustsuststrtssrsrsqsqsqrprprpqpqpqpqopopnpnnmnmnmnlnlnlmkmkljlklklklkljkjkijijhjhjhjhigigigighfhfhfhfhfhfgefdfdedededecdcdcdcdbdbcbdbcacbcacacab`b`b`a`b`a`a``_`_`^`^`^_^_^_]^]^]]\]\^\]\]\]\\[][][\Z\Z[Z[Z[Z[Z[Z[Y[YZY[YZYZXYXYXYXYXXWYXXWXWXWXVXVXVXVXVXVWVXVXVWVWVWVWVVUWVWUVUVUVUVUVUVUVUVUVUUTVTVTVTVUVUVUVTVUVTVTVTVTWUWUWUWVWUWVWUWUXVXVWVXWXVXVXVYWYXYXYXYXYXYXYXZYZY[Y[Y[Y\Z\Z\Z\[\[\[]\]\]\]\^]^]_^^^_^_^`_`_`_`_``a`a`aaaabbcbcbcbccdcedeeeeeeffgfgfgghghgihihiijjjikjkjlkmlmlmlnmnmonpopoqprprqrrsrsrtsututvvwvwvxwxxyxyy{z{z|{}|~}~}~IIIIIIJJJJKKKKKKLLLLMMNNNNOOOOOOPPQPRQRRSSSSTTTTUTUUVVWWWWXXYYZZZ[[[\\]]]^^___``````aabbccdddeeeeffghhiijjjkllmmnnooooppqqrrst€tuĀvvƀvvƀwvȀxwɀyyˀyyˀ{{̀|}΀}~π~ЀҀӀԀՀ׀ـڀۀ܀܀߀‐䀐䀐們耓ꀕ뀙퀝򀝞򀟠􀞟􀟡􀟡TUTUVVWWXXZZ[\\]]^^__`abbcdfdgceefefdedeegfhgigihjjllmlmmoopnpoqrtzz~}}y{vwuvuwrtpqlmjkjkijjkkmjlhjgihhefdfdfceab_`\]XYXYWXWWVVUUTUTVSUSUTV󀘘񀗘쀒ꀒꀑ透瀎倍䀊〈߀ހ܀ڀ׀ր~Հ~Հ}~Ԁ}~Ӏ|}р|}Ѐz{΀zz̀yỳxxˀwwʀvvȀuuǀtuǀssŀqqÀqqÀoonnmmllkkkkjjiihhggggffeedecdbbabaaaa``____]^]]\\\\[[[[ZZYYYYXXXXWWVVUUUUUTTTSSSSRRRRRQQQPPPOOOOONNNNMMLLLLLLKKKKJJJJIIIIHHHHHGGGGGGFFEFE~~~}~|}|}||{{zzyzyyxyxxwwvvuuuutututtstssrrqrqqpqppopopnpnonomnmnmmlmllklklkkjkjjijijiiiihihigihhgffgffegffefeeddcedddcccccbcbcbcbbababaa`a`a``_`_`__^_^_^_^_]_]^]]\^\]\]\]\][][\[\[\Z\[[Z[Z[Z[Z[Y[Y[Y[Y[Y[YZYZYZYZYZYZYZYZXZYZXZXZXZXZXZXZXZXZYZXZXZYZXZYZY[YZY[Z[Y[Z[Y[Z\Z[Z\[\Z\[\[\[[Z\[][][][][^\^\^]^]^]^]_^_^_^`__^_^`_`_`_a``_a`a_a`a`a`babababacbcbdbdbdbdcdcdcdcecededededfdfegefefegfgfgfgfhfhfhfhghghgjhjhjijijikjkjkjljljmklklkmlmlnlnlnlnmomomomrprprprqrqrqsqsrtrsrtrtsusususvtvtvtvtwuwuwuwuwuwuwuxwxwxwxwxwxwzwzwzxzx{y{y{y{y{y{y{y{y{y{y|z|z|{|{|{|{}{}{|{~}~}~}~}~}~}}}}}}~~~@?@?@?@?@?@?@?@?A@@?A@A@A@A@A@A@A@A@A@A@BAA@A@BABABABABABABABABABABABABAA@BABABABABABABABABABABABABAA@A@A@AAA@BABAA@BAAAA@A@A@A@A@A@A@A@A?A@A@A@@?@?@?@?@?@?@?}}~}~}~}~}~}~}}|~}~}}{}{}{|{|{|{|z|z{y{y{y{y{y{y{y{yzyzyzyxwyxzyxwyxzwywxwxwwuwuwuwuwuwuwuwuvuvtvtususuststststststrsrsrsrsqrqrpqpqpqpqopopnpnomnmnmnmmlnlmlmlmllklkljljljljkikikikikijhihihigihighfhfgegefefegefdfdededededdcdcdcdbdbdbcbcbcbcabababaa`b`a``_a_a_a_`_`^_^_^_^_]^]^]^]^]^]^]]\]\]\]\][][\[\[[Z\Z[Z[Z[ZZYZYZYZYYXYXZYZXYXYXZXYWYWYWYWYWYWYWYWXWXWXWXWXWWVWVWVWVXWWVWVWVWVWVWVWVXVWVWVWVWVWVXVWVWVXVXVXVXVXWXWXWXWYWYWYWYXYXYXZXZXYXZYZYZY[Y[Z[Y[Z[Z\[\Z\[\[][][]\]\]\^]_^^]_^_^_^_^`_`_`_``a`a`aababababbcbcbdcdcdcedeeeefeffgfgfhghhhhihhhiijijjkjkjlkmlmlnmnmnnononppppqprqrqsrsststtvuvuwvxwxwyxyxyy{z{z{{|{}|~}~~HHIHIIJJJJJJKKLKLLLLLLNNNNOOOOOOPPQPQQRRRRSSTTTTTTUUVVVVWWXWXXYYYY[[[\]]]]^^^___``aaaaaaabbccdeeeeffggghhhiijjkllmnnnnoopprrrs€ttĀtuĀvvƀwwȀwxȀxxʀyxʀyz̀z{̀||΀}}π~~ЀӀҀӀՀ؀ـڀ܀݀݀ހဌ‏】倓怔耔뀖퀘򀡢􀣤SUUVUVVVVWXXZY[[\]\^^__`abcedfeghjgighikijhihiijkmmononpqsqssutvrtrtwy}~z{xyxyvwstpqnpoommlmlmlmjkklkkjjikhiegdebb^^[\Z\Z[YYWXVWVWVWUWUVUWUV󀗘򀕖퀔퀒ꀑꀏ耍怌倊〉ဈ݀܀ۀڀــ׀~րՀ}~Ӏ}}Ҁ||р||Ѐzz̀yz̀xxˀwwɀuvȀtuȀstƀrsŀqqÀpp€oonnmmllllkkjjiihhggggffeeddddccbbaa`a``__^^^^]]\]\\[[[[ZZYYYYXXXXWWVVVUVUUTTTTSSSRRRRRRQQQPPPOOOONNNNMMMMMLLLKKKKKKJJIIIIIIHHHHGGGGGFFFFF~~~}}|}|}|}|{z{zzyzyyxyxxwwvwvvuvuuttstssrsrrqrqqprqqppoononnmnmnmnmmlmlmllklkkjkjjikjjiiiihihihhhhggfgffefefefeededededdcdcdccbcbbbcbcbbabaa`a`a``_`_`_`__^`^_^_^_^^]^]^\]\]\]\]\]\]\\[\[\[\[\[\[\[\[\[\[\[[Z[Z[Z\Z\Z[Z[Z[Z[Z[Z[Z[Z[Z[Z[Z[Z[Z\Z\Z\[\Z\[][\Z\[][][]\][][]\][]\^\^]^]^]^]_]_]^]_^_^_^`_`_`_`_``a`a_a`a`a`a`bababababacacbcbcbdcdcdcdcededededfefefefefefegfgffegfhfhghghghgihihihihjiihjijikjkjljljljlklkmlmlmlnlnmnmnmomomonpnpoonpoqpposrsrtrtstsustsvtvtutvtvuwuwuwuwuxvwuxwxwywywywywzwzyzyzyzyzyzy{y{z|{}{|z|{}{}{|{}{}{}{}{~}~}~}~}}~}~~~~~~~@?@?@?@?@?@?@?@?A@A@A@A@A@A@A@A@A@A@AABABABABABABABABABABBBBBABACBCACACBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBBACBBABABABABABABABAA@A@BAA@A@A@A@A@A@A@A@A@A@@?A@@?@@@?@?@?~}~}}}}~}~}~}~}~}}{}{~|~|}|}||{}{|z|z|z|z|z{y{yzyzyzyzxzyzyzwzwzwxwxwxwxwxwxwxvwuxvvuwuvuvuvtvuvtuttsuststrtrsrrqsrsqrprpqpqopnpoqopnpoononnmnmnmnmnmmkmlmkmklkljljkjkjljkijijhihigigighghgighghfgfgegegffefefdfefdedfdfdecdcdcdcdbcbbacbcab`babab`a`a```a`a``_`_`__^_^_^_^_^^]_^^]_]^\^]]\]\\[]\]\]\\[[Z\Z\Z\Z[Z[Z[ZZYZYZYZXZYZXZXZXZXZXZXZXYWYWYXYXYXXWYWYXYWYXXWXWXWXWYXXWXWYXYXYWYWYWXWXWYXYXYXYWYXYXZXZYZYZYZYYXZY[ZZY[Z[Z[Z\Z[Z\Z\[][][]\]\]\]\]\^]^]^]^]^]_^`_`_`_a_`_a`a`bababababbcbcbdcdcccddedfeeefefegfgggghghgihihjijikjkjkjlklklkmlmmnmnmpopoppppqqqqrrsrtsutututvuvuwvwvxwyxzyzy{z{z|{}|}|~~~HHIIIIJJJJKJKKKKLLLLMMNNNNOOOOPPPPQQQQRRRRSSTTTTUUVUVUWVXXXXYYYYZZZZ[[\\]]^^^^__``aaaabbbcbccdddeeffgghhhhiiijkkkklmnnnoqqqrrsst€uuŀvvƀvvǀwwɀxyˀxyˀyz̀{{̀||π}}π~~Ѐ~рӀՀ׀׀ـڀ܀݀߀߀ဌ‎䀏怐瀒逕ꀖ뀘퀙񀜝򀞠SSSTTUUVVWWXWYXYZZ\\^^]^```babcddffgfhhjijijjlnolnmnmnnoqssutvuwvwxzy{yzxzz{}~{{z{yzuvturspqppopppopopoqoqlnkljkgiegcd_`^_^^[\YZYZXZYZXYWYXZWYVWUU򀕖퀒쀑뀐逍瀍怋䀊 ߀ހ݀ۀڀـ׀րր~Ԁ~~Ҁ|}Ѐ{|πzz΀yz΀xỳwx̀uvʀttȀssƀqrŀqqÀpp€poonmmmlkkjjjjiihhggggffeeeeddccbbab````____^^]^]^\\\\[[ZZYYYYXXXXXWVVVVUUUUUTTSSRSRRRRRRQQPQPPOOOOONNNNMMMMLLLLKKKKKJJJIIIIIIHHHGHGGGGGFFFF~~}~}}||{|{|z{zzyzyzyyxxwxwwvvuvuututtstssrsrrqrqrqqpqppoononononnmmlnmnmnmlklklkkjkjjjjijijiihihhhhggggggffffffefefeededdcedddccdccbcbbabababaa`a`a`a_`_a``_`_`^`^_^_^_^^]_]^]^]^]]\]\^]]\]\]\]\]\]\]\]\]\]\]\\[]\]\]\]\]\][]\]\][]\]\^\]\]\^\^\^\^]^\^]^]_]^]_]^]^]_]_^`^`^`^`_`_`_`_`_a_a`a`aaa`bababbbabacbcbdbcbdcdcdcdcedededededfefefeedfefegfgfgfgfgfgfhgihihihihjiihjijijijikjkjkjkjkjlklkmlmlnlmlnlnmnmomnmomonpoqopopoqppoqprprqrpsqrqutvtvtvtwuwuwuwuwuwuxwxwxwywywzwywzwzyzy{y{yzy|yzy|z|{|{|{}|~|}{~|~|~|~}~}~~}~}~}~~@?~~@?@@@?@@A@A@A@A@A@A@A@AAAAA@AABABABABABABBBABABABABBBBBBCBCBCBCBCBCBCCCCCCCBCCCCCCDCDCDCDCCCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCCCDCDCDCDCDCDCDCDCCCDCDCCCDCCCCCDCCCCCCCDBDBDBCBCBCBCBCBCBCBCBCBCBBABACACABABABABABAA@A@BAA@A@A@A@A@A@A@@?A@@@@@~~~~~~~}~}~}~}~}~}~}}|}|}{|{|{}{|{|z|{|{{z{y{yzyzyzyzyzyyxywywywywxwxwxvxvwuwuwuwuwuvuvtvuvtutuststssrsrsrsqrqqpqpqpqoqoqopoqoononononnmonnmnlnmmlmlmllklklkkjkikijijijijijiihihihhghghggfhfgfgfgffefefefeededdcdceddcdccbcbcbdccbcbbaaababaa`a`a`a`a``_`_`_`_`_`__^_^_^_^^^^]^]^\]\]\]\][][]\\[\[\[\[\[[Z\[\[[Z[Z[Z[Z[Y[Y[Y[YZYZXZYZY[YZYZXZXZXZXZYZYZXYXZYZYZYZYZYZYZYZYZYZYZY[Y[YZY[Z[Y[Y[Z[Z[Z\Z\[\Z\Z\[][][]\]\]]^]^]^]^]^]_^_^`_`_`_`_a```a`b`aababbbacbccccdcdddcddededeeeeeeffgfgggggfhghgihihjjjjkjkklkmlmlllnmnmonnnpooopoqpqqrqrqsrtsututuuvuvvwvxwxwyxyxzz{z|{||}|}|~}~HHHHIIIIJJJJKKKKLLLLMMNNNNNNOOOOOOPPQQRQSRSRTSTTTTUTUUVVWVWWWWXXYYYYZZZZ[[\]]^^^^^__``aabbbbccddddeefffghhiiiijjjjkllmmmooppqqqrrs€ttŀvvƀwwȀwxʀxyʀzzˀyzˀz{̀|}π|}Ѐ}~Ҁ~ӀӀՀ׀؀؀ـۀ܀݀߀߀『倏瀑耑耒逓뀗퀙򀜞󀞠TTUVVWWWWXXYYZZ[[\]]^_``abbcdfegegfgghhihijjllnoprrtqsrtrtuvvxxzxz{}z}|~~~}}yyxyxyuvtustttqsqrsvrtprnpkmikegcdbc`a`a^^\][\[]Z\Z[Z[Z\YZXYVWTUTTRS􀗙󀕗񀕗񀔕쀐뀏逍瀋怋倊《〉  ހހ݀ڀڀ׀Հ~Հ}~Ҁ|}Ҁ{|рz{рyzπxx̀vvˀvwʀtuȀstƀssŀrrĀqqÀpp€oonnmmllkkjjiihhhhggffeeddddccbbaaaa``______]^]^\]\\[[ZZZZYYXXXXWWWWVVVVUUUTTTTSSSSSRRRRQQQPPPOOOOOONNNNMMMMLLLKKKKKJJJJJJIIIIHHHHGGGFGFFF~~~}~|}{|{{z{z{zzyyyyxyxxwxwwvvuvuuttststssrsrrqrqrqqppoqppopopoononnmnmmlmlmllklkkjkjkjjijijijiihhhhghghggggggfgffefefeededededdcccdbdcbbcbbbbababababaa`a`a`a``_`_`_`__^`^_^_^_^_^_^_^^]^]^]_^^^^]_^_^_]_^^]^]^]^]^]_^^^^]_]_]_]_^_^_^_^_^_^_^_^_^`^`_`_a_`_`_a_a`a`a`a`a`bababacbcbcbcbcbcbdcdcdceddcedededfeedfefefefefefefegfgfgfhghgihhgihihihjijijijijijikjkjljkjlklkljmkmkmlmlmlnlnmonomomonpopopoqpqpqoqprprprqsqsqsqsrtrtrtsususwuwuywxvywywywywywzyzyzyzyzyzyzy|y|{|{|{|{}{}{}|~|~|}~|~|}~}~}}@@A@@@A@A@A@A@A@A@AAAABABABABABABAAABABAAABBBBBBBBBBBBBBCBBBBBCCCBCBCBDCCCDCDCDCDCDCDCDDEDDCDCDCDCEDEDEDEDEDEDEDEEEDFEEDEDEDEDEEEDEEFEFEEDFEFEEEEEEEEEEEEDEDEDEDEDEDEDEDEDEDEDEDEEEDEDEDDCEDDCEDDDDCDCDCDCDCDCDCDCDCCCDCCCCCCBCBCBCBCBCBCBBABACABABABABABAAAAAAAA@A@A@A@A@A@@@@@@@@@@?~~~~}~}~}~|}~|~|}{~|}|}|}{}{|{|{|{|y|y{yzyzyzyzyzyywywywywywxvwvxvwuwuvuvuvuvtusustsuststrtssrrqrqrprqqpqpqpqoqopopnpoononnmnmnmnmmlmlmlljlklkljkjkjkjkikijijijiihihihihighghghggfhggfgffefefeedededdceddcdcdccccccbdcccbbbbbaaabaa`baa`a`a``_`_`_`_`__^_^_^_^_^^]^]]\]\]\]\]\]\]]]\]\]\]\\[\[]\\[\[\[\[[Z[Z[Z\Z\Z\Z\Z[Y[Z\Z\Z\Z[Y[Z[Z\Z\Z[Z[Z\Z\Z\Z\Z\Z\Z\Z][\[\[][][\[\[]\]\]\]]]]]\^]^^^^_^_^_^`^`_`_a```a`a`a`baaabacbcbcbccdcddddedeeeeeeeeeefffeffgfhghgihihjijiihjjjjkjlkllmlmlmmmmnnonooooppqpqprqrqrqsrsststsvuvuwvwvxwxxyxzy{z{z{z{{}|}|~}HHIHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQRQSRSSTTTTTTUUVUVVWWXWXXYXXYZZ[[[[\\\\]]]^____````aabbbccdeeeffffgggiiiijkklllllmnnooppqqrssÀttĀuvĀvwƀwxȀxyʀyz̀yz̀{|΀{|΀|}π~р~ҀӀԀր׀ـڀۀ݀߀߀‍‎倐瀐耒뀔퀕퀗񀛝󀝞TUTUTUTTTUUWWXXYY[Z[Z[Z[]^^_^````acdefhijljlkmklklkkmlqptttvtvvxuxvxwyz||~|~~~|}{|z{y{xzxzuxsvvxstppoqopjlhifgefbdac_`^^]_]^\]\]\]\][\Z[XYWXVWTUSTRSRS􀗘􀔕퀐퀏ꀏ耏瀐瀏怎瀌䀌〉ဈހ܀ۀـ׀ր~Հ}~Ԁ|}Ӏ{|рz{Ѐyz΀xỳwwˀvvʀuvȀtuȀstǀrrŀpqÀop€nonnlmllkkjjiihhhhggffeeddddccccbbaa`a__^^^^^^]]]][\[[[[ZZYYYYXXXWWWVVVVVVUUTTTSTSSSRRRRQPQPPPPPOOOONNNNNNMMMMLLKKKKKKJJIIIIIIHHHHHGGGGFFFFFFE}~~|~|}||{{z{z{zzyzyyxxwxwwvwvwvvuututtstssrsrsrrqrqrqqpqppppopoonononnmonmlmlmlmllklkkjkjkjjjjijiihjiihihhhhghghggffegffefeedededdcdcccdcdccccbcccbcbbabababab`a`a`a`a``````_```_`_`_```_`_`_`_`_`_`_`^`^`_`_`_`_`__^`_`_a``_a``_`_a`a_a_a`baa`baaabababacabacbcbcbcbdccbdcdcdcededededededfefefefefegfgfgfgfhghghggfhgihihihjijiihjikjkjkjkjkjkjkjlklklkmlmlmlnlnlnmmlnmonnmonpnpoonpoqprpqprqsqrqsqsrsrtrtrtrtststsutututvtwuxvwuywywzyzy{y{yzyzy{y|{|{|{|{}{}{~}}|}~|}~~~~}}~@@@@A@AAAAAABABABABABABABBCBBABBBBCBBACBBBBBCBCBCCCCCCCCCCDCDCDCDCDCDCDDDCDDEDEDDDDDEDDDEDEDEDEEEDEDEEFEFEFEEEFEFEFEFEFEFEFFFEFFFFFEFFFFFFFEGFGFFFFFFEGFFFGFFFGFGFGFGFGFGFFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEEEFEFEEDFEEDEDEDEDEDEDEDEDEDDCDCDCDCDCDCDCDCCBCBCCCBCBBBBBBBBABBBABABABABABAA@AAAAA@A@A@A@A@@@@@A@@@@@}~}~}}}}|}}{}|}|}||{|{|{|{|{|{{y{y{y{yzxywywywywxwxvwvwvwuvuvuvtvtutususuttstssrsrsqrqrqrpqpqpqpqoqpqppopnononnmnmnmmlmlmlmlmllklklklkkjkjljkjjijijijhjhigigihhghggfhghggfgfgffefefeededfeededdddddcdcdccccbcbbabababababaa`a`a````_`_`_`__^_^_^_^_^_^_^_^^]_^_]^]^]^]^]]\]\]\]\]\]\]\]\][^\][]\\[][][][][][][][]\]\]\][][][]\]\]\]\^\^\^\^\^\^]^^^]_^_^_^_^_^_^_^_^`_a`a`a`aaaababababbcbdcdcccddddddddeeeeeefeffffffgggghghghhhhihiiiijikjkjkklkmlllllmmnmnmononpopoqpqprqqqrqsssststtutvuwvwwxwyxyxzyzy|{|{|{}|}}~}~~HHHHIIIIJJJJKKKKLLLLMMMMMMNNOOOOPPPPQQQQRRSSSSTTTTUUVUVVWWWWXXXXYYYYZZZ[[\\\]\]^^^__````aabbbcbcddddeeffgghhiiijjkkkllmmnnooopqrrr€stÀttĀuuƀvwǀwxɀxyˀyzˀz{̀{|΀|}π|}Ѐ}~рҀӀՀր؀ـڀۀ݀ހ‌䀎們耒耓耓ꀕ쀖񀛝󀛝󀞠TUUVUVVWUVUWVXWXXYXYYZZ[[]]^]_^_`a`aaabccegijmmporprpqppopppssxxwyxzy{{||}}~~~}|~|~z|vyxxutqqqrnpnplnjkghdecdbc`a_a_`_a^`^_]^\][\Z\YZWXVWTVTUSURTQS􀗙󀕗򀓕퀓퀔쀒ꀐ透耍怋倊〉ဇ߀߀܀ڀـ؀րր}~Ԁ|}Ҁ{|Ѐz{Ѐxy΀xỳwx̀vwˀuvʀttȀssƀrrĀqqĀpp€oonnmmlmkljjjjiihhggffffeeddddbbbbaa````____^^^^]]\\[\[[ZZZZYYYYXXWWWWVVVVUUUTTTTSSRRRRRRQQPQPPPOOOONNNNMMMMMMLLLKKKKKJJJJIIIIHHHHHHGGGGGGFFFEFE~~~~~}}|}||||{{z{zzyzyyxxxxwwwwvvuvuuuvuutsstststsssrrrqqpqqpppopopooooonmonnmnmmlmllklklkkjkjkjkjkjjiiijiihhhhhhghggfgfgfgffefefefeeeeedddddddddcdcdccbcbcbbbbabbbbbabababababababababaa`bababaa`babababaa`a`babababababababacacbbacbcbcbcbdcdcdcdcdcdcdcdcededfeedfefefeedfegffegfgfgfhghghghghghghgihihjijijijikjkjkjkjkjkjlkkjlkmklkmkmlmlnmmlnmnmomonononpnpopoqopoqpqqqpqprqsrsrtrtststrtsusutvtvtvtvuwvwvwuwvxvwvywxvyw|z|{{y|{|{|{}|~}~}~}~}}~~~~~A@A@A@AAAAAABAAAAABABABBCBCBCBCBCBCBCCCCCCCCCCCCCCDCDCDCDDDCDDDDEDDDEDEDDDDDDDEDEDEDEEEEEEEEEEEEEEFEFEFEFFFFFFFFFFFFFFGFGFGFGFGFGFGFGFHGHGHGHGHGHGHGGGGGGGGGHGGGGGGGGGHGHGHGGGGGGGGGHGGGGGHGHGHGGGGGGFGFGFGFHGGFGFGFGFGFGFGFFFGFFFFFFFFFFFFEFEFEFEFEFEEEEEEEFEEEEEEDEDEDEDEDEDDDDCDDDDDDDCDCCCCCCCCBCBCCBBBBBBBBBBCBBABABBBABABAAAAAAAAAA@A@A@@@@@~~~~}~~~}}|}|}|}||{|{|{|{{y{y{y{y{y{yzyzyywywywxvxvwvwvvuvtvtututuststsustststrsrsqrqqprprprqqpqppopoonpoononnmnmnmmlmlmlmlmllklklklklkkjkjjikjjiihjijiihhghghghghghggfffgfgffffeedededededddeddcdcdcdccbcbcbcbbababaaabaaaa`a`a`a`a`a``_a`a``_`__^___^_^_^_^_^_^^]^]^]^]^]^]^]^]^]^]^\^]^]^]^]^]^]^]^]^]^]^]^]^]^]_^_^_^___^_^_^__`_`_`_`_a``_a`a`a`a`babbbacbccdcdcdcdcddddededeefeffffgfhggghghgihihihihihiijikjkjkjkjlklklkmlmmnmnmononpopopoqpqqrqsrsrsrssututuuvvvvwvwwxwyxzyzy{z|{|{}|}|~}~}~~HHIIIIJJJJKKKKLLLLMMMMNNOOOOOOPPQQQQRRSRSRTSTTUUUUUVVVWWXXXXXXYYYZZ[[[\\\]\]]^^_``````aabcccccddddeffggghhiiijjjkklmmmmnnooppqqr€rsĀttĀtuƀvvǀvwǀxyʀyyˀz{̀{|΀|}π}~Ѐ~рӀՀր׀؀ڀۀ܀܀ހ‌『『倏瀐耒ꀕ쀗񀘙󀚝󀜟UVUVVWWXVXWXXZZ[Z[[\Z\[]\^]__a`babbbcdefhjjlkmoqruuwvwvuwwxwxx{|~فƁǁ}{|{{xxwxuvuwtvqrmoikgighefbdbcab`b`a_a^_]^\][\Z[XZWYVXUWUVTUSTRSRRQR􀕗񀖗񀖗񀕖퀐ꀐꀍ瀌怌個〈 ߀݀݀ۀۀـ؀~ր}~ր|}Ԁ{|Ӏ{{ҀzzЀyz΀wx̀vẁuuɀttȀrsǀrsŀqrĀpqĀooÀnomnlmklkkjjiihighfgefeeddddccbbaaaa`a``__^^^^]]]]\\[[[[ZZYYYYYYXXXWWVVUUUUUUUTTSSSRRRQQQQQPQPPOPOONOONNNMMMLLLLLKKKJJJJJJIIIIHHHHGGGGGGGFGFFFFE~~~~}}~}|{|||{|{{z{zzyyxyxyxxwxxwwvuvuvuuuuuuutstttssrsrsrrqrqrqqpppppppooonnnnmnmnmmmmllllllkkjkjkjkjjijijiiijiihhhhghghghggfffgfgffefefefeeeeeddededdddddcdcdcdcccdccbcccccbcccccccccccccbcbcccbcccccbcccbcbdcccdcdcdcdcdceddcededededfefefefefegffefegfgfgfgfgfgfhghghghgihihihihihjijijikjkjkjkjlklklklklklkmlnmmlmlnmnmnmnmonnmononoooopoqoqoqpqpqprqrqrqsqsqsqsrtrsrtsututvtvtwuvuwuvuwvwvxvywywxvywzxyw{yzxzyzy{y~}~}~}~}~}~~~~~~A@A@AAAAAAAAAABABABBBBCBBBCBCBCCCCCCCCCCDCDCDCDCDDDDDDDDEDEDEDEDEDEDEEEDEEEEEEEEEEFEEEEEEEFEFFFFFFGFFFFFFFGFGFGFGFGFHGHGHGHGHGHGHGHGHGHGHGHGHGHGHGHGHGHHIHIHIHIHIHIIIHIHIHIHIHIHIHHGIHIHIHIHIHIHIHIHIHHGIHIHIHIHIHHHHHIHIHIHIHIHIHIHIHHGHGHGHGHGHGHGHGHGHGHGGGGGGGGGGFFFFFGFFFFEFFFFFEFEEEEEEEEEEEEEEEEEEDEDEDEDEDEDDCEDDCDDDCCCCCCCCCCBCBCCBBCBCBBBCBBBBBBBBABAAAAAAAAAAAA@A@~~~~}~}~}~}~}~}}{}{|{|{|z|{|z{y{y{y{yzyyxyxywxwxwxvwvwvwuwvvuvtututututtsuststssqsrsrrqqpqpqpqppopopopoonnmnmnmnmnmnmmlmlmllklklklklklklkkjkjjijiihjiihihihihihhghggggfgfgfgffefefefefeeeeefeedddeddcdcdcdccbcbbbbbbbbabbbabababababaa``_a`a`a``_`_`_`_`_`___`^`^_^`__^`^`_`__^____`__^`_`_`_`__^`_`_`_`_a`````a`a`a`a`a`a`a`babbbabbbbcbcbccddddddededfeeeffffgggggfgghghghghgihihihjijijijjkkkkkjllllllmlmmmlnmnmonpopopoqpqprqrqrqsrsststsutuuutvuwvxwxwxxyxzzzy{z|{||}|~}~}~~~~HHIIIIJJJJKKKKKKLLLLMMMMNNOOPPPPPPQQQQQQSSSSTTTTUUVVVVWWWWXXXXYYZZZ[[[[[\\\\]]^^____aaaaaabbbccdddeeeffggghiiiijjklmllnnnonopppqqrst€ttĀuuƀuvȀwxɀxyʀyzˀz{̀z{̀{|π|}Ѐ}~р~ӀՀր؀ـڀڀ݀ހ߀‌䀎䀏倐瀐耐耓뀕퀗񀛝󀛝STTUUVVWWXXXXXXYYZ[]^`^_]^^_^_^`^``bbcceefeghjlmlnoqsvtwvyxxzz||ȁˁŁǁʁȁÁāɁЁρʁǁ́ǁف|||}|}|~|}yzstnqjmjljkghegdfbdacac`c_a^_\^[][\Z\YZYZWYVXUWTVTUTTSTRR󀗙󀕗񀔕퀑뀎逍瀍瀊䀉〈 ߀݀܀ۀڀـ؀~~ր}}Ԁ{|Ҁ{|рyzЀxy΀wx̀vẁuuˀtuɀstȀqrǀqrŀppÀop€nonnlmkljkijijhhggggffefdedecdbbbbabaa``__^^^^^^]^\\\\[[[[ZZZZYYXXXWWVWVVVUUUUTTTSTSSRRRQQQQQPPOPPOONNNNNNMMMMLLLKKKKKKJJJJJIIIIIIHHHHHGHGGGGGFFFF~~~~~~}~}}|{{{{|{{zzzzzzyyyyxxxxxxwwwwwvvvvututttttssssssrrsrrrqqqppppppoooononnnnnnnnmmlmllklklklklkkjkjjijjjijijiihihihhghggghggfhggffffeffffeefeedfeededeeeeeeeeededededfeedededeefeedededededfefefeedfeededfefefefefffffegfgfgfgfgfhghghggfihhgihihihjiiijijijijikjkjlkkjlklklklklkmlmlmlmlmlnmnmnmononooooooonpopoqpqpqopoqprqrqrpsqsrsrtrtststsutututvtvtvtvuvuwvwvwvxvxvywywxwyxyxzxzxzxzz{y|{|{|{|{|{|{}{~~~AABAAABABABABABBBBBBCCBBCBDCDCDCDCDCDCDCDDDDDDDDDDEDEDEEEEEEEEFEFEFFFFEEEEFFFFFEFFFFFFFFFFGFGFGFGFGFGFGFGFGFGFGFHGHGHGHHHGHHHHIHHHHHHHIHHHIIIHIIIIIIIIJIIIIIIIIIJIJIJIJIJIJIKJJIJIJJJJJJJIJJJJJJJJJJJIJJJJJJJJJJJIJIJIJJJJJJJJJIJIJJIIJIJIJIJIJIIIIIIIIIIIIIIIIHIHIHIHIHHHHGHGHGHGHGHGHGHGHGHGHGHGHGGFGFGFFFFFFFFFFFFEFFFFEEFFFFEEEEEEEEDDEDEDEDEDDDDDDCDCDCCCCCDCCCDCCCCCCCCBCBCBBBBBBBBBAAAAAAAABABA~~~~~}~}~}~}~}~}~||{|{}{{z|z{y{yzyzyzyzxyxyxywxwxvxvwvwvwvwuvuvuvtututusustssrtrrqrqrqrqqpqpqppopppopoononononnmnmmlmlmlmlnmmllklklklkkjkjjikjjijijiihihjihhihihhggggggfhggggfgfgffffefffeedededededdcdcdcccdcccdcdccccbbbcbcbcbbabbbababaaaaaaaa`a`a`aaa`aaaaa`a`a`a`a`a`a`a`a`a`a`baa`a`baa`babacbcbbacbcbcbcbccdcdcddddedddddedeeffgfgfgfgggggghhhhihihihiijijjjjjjlkkjlklklklkmmnmnmnnnnnnoopopopoqpqpqprqrqsstsssttttuuuuvvwwwvwvxwxwyxzyzzzz|{}|}}}}~~HHIIIIJJKKKKKKLLLLMMMMNNNNOOPPPPQQQQRRRRSSSSTTUUUUVVWVWWXXYYYYZZZZZZ[\\\\\]]]]^^____``aabbbbccdddeeefffgghghhijjjjkllmmnnoopppqqqr€rsÀstĀuuƀvvǀvwȀwxˀxỳyz̀{{π||Ѐ|}Ѐ~Ҁ~ӀԀր؀ـڀ܀݀݀߀ဋ「䀍倎怐怑递뀓쀔쀖񀜝RSSTTUUVWXWYXZZZYZY[Z[\^`bababababacacbddeeggihilmnpoqrttuxzz{z{~ŁÁāƁȁɁ́с́ЁӁсс́сׁׁсՁ؁с΁ȁyytuprnpnojlhjgifgefcedecebd`b_`^_]^\][\Y[XZXYVXVWVWVVUVTUSTRSQR􀗙󀖗񀕖퀒쀑쀏逎而耊怉倈〇စ߀݀܀܀ڀـ؀~~ր}}Հ{|Ӏz{рzzрxyπwx΀vẁuvʀuuɀstȀrsƀqrŀqqŀppÀoonnlmllkkjjjjiihiggfgefeeddcdccbbaaaa``____^^^^]^]]\\\\[[[[ZZYYYYXXWXWWVVVVUUUUTTTTSSSSRRQQQQQPPPPOOOOONNMMMMMMMMLKKKKKKKJJJJJJIIIIIIHHHHGGGGFFFFFF~~~~~}}}}|||{{|||{{{zz{zyyyyyyxxxxwwwwvvvuuuuuuuuuttttsssrsrrrqqrqqqqpqpqppoooponnonnnnnmmmmmlmlmllklklkkjkjkjkjjijiihihihihihhghgihhghggfgfgfgfgfffgfgfgfgfgfgfgfgfgfgfgfgfgfgfgfgfgfgfgfgfgfgfgfhghggggghhihhghghghghgihihihihjijijikjkjjikjkjkjlkkjmlmlllllmmmmmmmlnmnmnnnnnnnnonompopopoppppqqqpqqqqqprqrqrqsrsrsrsrtssrusususutvtvuvuvuvuwvwvwvxwxwxvywywywzxzxzyzyzyzy|{{y|{|{|{|{}|}|~}~}~}}|~~BBAABABBBBBBCBBBBBCCDCDCCCDCDCDDDDDDDDDDEDDDEDEEEDEEEEEEFEFFFFFFFFGFFFFFGFGGGGGFGFGFGFGFGFHGHGHGHGGGHGHGHGHGHGHHHHIHIHIHIHIIIIIHIIIIIHIHIHJIJIJJKJJIKJKJKJJJJIKJKJKJKJKJKJKJKJKJKJKJLKLKKJKJKJLKKKKJKKKJKJKKKKKJLKLKLKLKKKKKLKKJKJKJLKKJKJLKKJKJKJKJKJKJKKJJKJKJKJKJKJJJJJJJJJJIJIJIJIIIJJIIIIIHJIIHIHIHIHHHIHHHHGHGHGHGGGGFGFHGGGGGGFGFGFFFFFFFFEFEFEFFFEEEEEEEEEEEEEEEEDDDDDEDDDDDDDDDDDCCDCCCCCDCCCCBBBBBCBBBBBBBBABA~}~}~}~|~|~|}{|z{z}{{z{z{z{y{yzyyxyxyxyxyxywxwxwxwwvwvvuvuvtvtvtuttssrtrtrsrsqsqrqrqqpqpqpqppopopooopoonononnmnmmmmmmlmlmllllklkkjlkkjkjkjjijijiihihjiihihhhihhghghghghghggfhgfffffefefefefefeedfefeededdcdcdddcccdccccccccbcbcbcbcbcbbbbbbbbbbbcbbacbcbcbcbcbcbcbcbcbcbcccbcbccdcdcdcdcddedddeeeeedeefefefeffgfgfffgghghhhghhihihiijijijjkjkklklkllllmlmmnmnmononoopoooppppqpqpqqrqrqrrrsssttttuuuuuuvvwvwvwwxxxxyyyyzz{z{{{{{|}}|}~~HHIIIIJJJJKKKKKKLLLLMMMMMMNNOOOOPPQQQRRRSSSSTTTTUUVVVVWWWXXXYYZZZZ[[[\\\]]]]^^^_^_``aa`aaabbbcddcddeefffghhhhiijjkklkllmmnnooppqqrÀrsÀrsÀttŀuvǀvwȀwxɀxyˀyz̀z{̀{{π{|π|}Ҁ~ҀԀԀր؀ڀڀ݀ހဉဌ䀌䀎瀏耐耐递ꀓ쀕󀚜RSSTTUTUUVVWWXXYY[Z[Z[[\[]]^`bcddeefdfdfefdefghihijknpprsuvxxz{}ÁŁȁǁʁˁʁǁˁЁсҁցׁ܁ׁ݁݁ف߁ށہׁс́āz{vxsuqrnokmklikijhjgifhegdfdeac_a_`^_]_[]Z[ZZYYXYXYXYXYVWUVSTRSQR󀗙񀖗񀔕퀐뀏ꀍ耋瀉倉䀈〇စ߀݀݀ۀۀـ׀~׀|}Հ{|Ԁz{ҀyzЀxyπwx̀vẁuv̀tuˀstʀrsȀrsǀqqŀopÀopÀmnmnlmkljkjjijiighfgffefeeddccccbbabaa``____^_^^]^\]]\\\[[ZZZZYYXXWWWWWWVVVVUUUUTTTTSSSRRRQQQQQQPPOOOONNNNMMMMMMLLLLKKKKKKJJJJJJIIIIIIHHGGGGGGFFFFFFFF~~~~~~}|}|}|||{{{{{{zz{zzzyyyyyxxwxxwvwwwvvvuuuuuttttttstssrssrqrqrrqqqqqpppooooononononnnnmmlnmmlllmllklkkjlkkkkkkjjijijijijiiiiiihjiiiihihhhhhhghhihhhihhhihhhihhgihihhhihihiiiiihihihjiihihjijijijijikjkjkjlklkkjlklklklklklkmlmlmmmlnmnmnmnnononpoonoooopppppoppppqpqqqprqrrrrsrrrsrsrtstststttttsttuuutuuutvvvvvuvuwvwvxwxvxxywxwzxzxzx{yzx{y|z|{|{|||||{|{}|}|}|~}~}~}~~~~~~BABBBBBBBBBBCCCCDCDCDDDDDDDDEDEDEEEEEEEEEEEEFEFEFFFFFFFFFFFFFFGFGGGFGGGGGGHGHGHGHGHGHHHHHHHGHHHHIHIHIIIIIIIHIHJIIIIIJIJIJIJIJIJIIIJJKJJJKJKJKJKJKJKJKKKKLKLKLKLKLKLKLKLKLKLKMLLKLKLLLLMLLKMLMLMLMLMLMLLKMLMLMLMLMLMLMLMLMLMLMLMLMLMLMLMLMLMLMLMLLLLLMLLKLKMLLKLKLKLKLKLKLKLKLKLKLKLKLKLKKJKJKJKJKJJJKJJJKJJJJJJJJJJIJIJIJIJIIHIIIHHHHHHHHHIHIHHGHGHGHGHGGFGGGFGFGFGFFFFFFFFFFFFFFFFFFFEEFEEEEEEEEEEEDDDDDDDDDDDDDDDCDCDCCCCCCCBBCBBBBBBB~~~~}~}~}~|}|}|~|}{}{{z{z|{zyzyzyzxyxyyxxyxxwyxwvwvwvwuvuwuvuutututtttstssrsrsrsrrqrqrqrqrqqpqqqppopopopoononnnnnnmnmnmmmmlmlmllklklkkjkjkjkjkjjikjjijjjjjijiihihhghghghggggfgfgggfgfgfgfgfgffefefeeeededeeeeedddddddddeddcdcededdcdceddcdddcdddddceddcedddddedededeeeeeefefefefefegfffgfgfgggggghghhhhiiiiiiiijijijjjjkklklkllllnmmmmmnmonononpopoooqprqqqqqrrrrrrssssssttttuuuuuuvvwvxwxwxwxxyyyyzzzz{{|||}||}}}}~~~~HHIIJJJJKKKKLLLLLLMMMMNNOOPPPPPPQQRRRRSSTTTTUUUUVVWWWWXXYXYYZZZ[[[[\\\]]^^^^^__`````aabbbcccddddeeeffghhhhiijjjkkllmmnmnoppqqrqr€rsĀstŀstƀuvȀvwɀwxʀwxʀyz̀yz̀z{΀||Ѐ}~Ҁ~ӀՀրրـ؀ڀ݀ހဋ》「倎瀐耑递뀓쀓퀕񀙚񀚛󀜝RSSTTUUVUVVWWXXYYZZ[Z[Z[[\[\\^^_abbcdfhijkjkikijikikikkmqstuwy{|~Łǁȁ́́́Ёсԁс΁Ӂځ܁܁܁؁сǁÁÁ}~||wxsspqnomolmkmkmjkijijhjgiefcebd`b`a]^]]\\[[Z[Z[[\Z[XYUVTUSTSTQSPQPQ􀘙󀖗񀔖쀏뀎逌耋瀋怉〈 ဆ߀݀܀ڀـ؀~׀|}Հ{|Ԁz{ӀyzҀxyЀwx΀vẁuv̀tuˀstɀstȀrrƀqqŀpqÀopÀnomnlmlmkkjkijijhhggfgffeeeeddbcbcabaaaa``____^_^^]^\][\[[[[[[ZZYYXXWXWXVVVVVVUUUUTTTSSSSSRRQQQQQPPOOOOOOONNNNMMMMMLLLLLKKKKJJJJJJJJIIIIHHHHHHGGGGFFFFEEEEEE~~~~~~~}}||{|{||{{{{zzzzzzzyyyxxxxwwwwwvvvvuvuvuuuttttttssssssrrrqrrqqqqqppppppopoooooononnmnnnmmmmmnmnmlllklklklkkjlkkjkjkjjjkjkjkjkjjjjjjikjjikjkjkjkjkjkjkjkjkjkjkjkjkjkjkjlkkjlklklklklklkllmlmlmlmlmlnmnmnmnmnmnmonononoopopopoppqpqpqqrqqqqqrrrrrrrrsrsssststsssttututvuvuvuvvvuvuwvvvwvvvwvwwxwxwxxxxxxzxzxzyzyzyzz|z|zzz{z}|||}|}|}|}|}|~~~~~~~CCCBCCDDDDDCDDDDDDEDDDEDEEEEEEEEFEFFFEFFFFFFFFGGGGGGGFGGHGGGGGHGHGHGHGHGIHIHIHIHIHIHIHIIIIJIJIJIJIJIJIJJKJJJKJKJKJKJKJKJKJKKKJKJKKLKLKLKLKLKLKLKMLMLLLMLMLMLMLMLMMMMNMNMNMMMMMNMNMNMNMNMNMNMNMNMNMNNNMNNNMNNNNNNNMNMNMNMNMNMNMNMNNNMNNNNNNNNNMNMNMNMNMNMONNMNMNMNMNMNMNMNMNMNMNMNMMLMLMLMLMLMLMLLKLKLKLKLKLKKKKKKKKKKJKKKJKJKJKJKJJJJIKJJIJIJIJIJJJIJIJIIIIIIHHHHGHGHGHGHGHGHGHGHGHGGFGGGGGGGGGGFFFFFFFEFEEEFEEEEEEEEEEDDDDDDDDDDDDDDDCCCCCCCCCBCBCBCB~~~~~~~|~|}|}|}|{z|{|{{z|{{z{z{zzyzyyyyyyxywywxwxwwvwvwvwvvuvuvuutututtttststssrsrsrrqrqqqqppopppppoppoooooonnnnnnnmmmmmmlmllkmlmllklklklklkkjkjkjkjjjjijijijiihihihiihhhhhhhhhhhghghggfgfgfgfffgfffffeefffffefefefefefefefefefffefegffefffegfffgfgffegfgfgfgfgggghggghggghghhihihihihjijijjjjkjkjkklkllllllmmnmnnnnnnnnpoooppqpqprqrqrrrrsrssssttssttuuuuuuvvvvwwwwwwxxxyzyyyzzzzzz{{||}}}}}}HHIIIIIIJJKKKKLLLLLLLLMMMMNNOOPPQQQQQQRRRRSSSTTUUUUUVVWWXXXXXYYYZZ[[[[[\]]]^^^^__`_````aaaabccccddeefffgfghhhiiiijjkklllmnmnoppqqrqr€rsÀstŀtuƀuvǀvwɀwxʀxyˀyz̀zz΀{|π|}р}~р~ҀԀր׀ـڀ܀܀ހ​‌」倎怏耑递뀔퀕񀘙񀚝󀛝RTSTSUUVUWVXWXXYYZZ[[\[\[][]]^]_]_^`abefeggiklnopqopoqlnlnnprtxzz|}Łȁ΁ЁӁӁԁցցՁԁہ݁ށځׁρʁƁ~~xyuvstrspqpqpqnolnlnkljlhjhjfhdfbcbc`a_`_`^_]^]^\][\YZWXVWUVUVSTRSQRQSQR󀖗񀓕쀏쀎ꀍ瀌瀊倉〉〇〆စހ݀܀ۀــ}׀}~ր|}Հ{|Ӏz{Ӏyzрxyπww΀vv̀uùtuˀstɀrsǀqrƀpqŀopÀno€mnmnlmllkljkijijhhggfgffeeddcdccccbbbbaa````__^_^^]]\\\\[[[[ZZYYYYYYXXWXWWVVVVUUUUTTSSSSRRRRQQQPQPPPOOOONNNNNNNNMMLLLLKKKKKKKKJJJJJJIIIIHHIIGGGGGGFFFFFFFFFFEEEE~~~~~~~}}}|||||||{{{{zzzzzzyyxxyyxxwwwwvvwvvvvvuuuuuuuusssssssrrrrrrrqqqqqpqqppppppppoooooopoononnmnmnmnmnmmmmmmlllmllllkmllllkmllklkmlmlmlmlmlmllkmlmlmlmlmlmlmlmlnmnmmlnmnmnnmmnnonononpoonoopooopoppppppqqqpqprqrqrqrqrrsssrsstttsttttttutututuuuuuuvvvuvuvvwwxwxwxwxwxxxxxwyxxwxxyxyxzxzxzzzzzz{z|{|{{z||}|}|}}}}~~~~~~~~~BBBBBBBBBBBBDDDDDDEEEEEEEEEEEEFEFFFEFFFFGFGGGGGGGGHHHHHHHHHHIHIHIHIHIHIIIIIHJIJIJIJIJIJIJIJJJIKJKJKJKJKKKJKKLKLKLKLKLKLKLKLKLKMLMLMLMLMLMLMLMLMLMLMMMMMMMMMLMLNMNMNMNMNNNNNNONONONONONONONONOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOPPPOOOOOOOONPOOOOOOOOOOOOOPOPOPOPOOOOOPOOOOOOOOOOOOOOOONOOONONONONONNMONNMNMNMNNNMNMNMMMMMNMMLMMMMMMMLLLMLLKLKLLLKLKLKLKKKKKKJKJKJKJJJJJJIJIJIJIIIJIIIIHJIIHIHHHIHHGHHHHHHHHHHHHGGGGGGGGGGGGGGGGFFFFFFFFFEFEFEEEEEEEEEEDEDDDDDDDDCCCCCCCCBCCBBBB~~~~~~}|~|}|}|}||{|{{z{zzz{zzyzyzyyxyyxxyxxwxxxwwvwvwvwvwvvuvuuuututtttstsssssrrrrrrqqqqrqqppppoppooooonononnmnnononnmnmnmmlnmnmmlllmllklklkkjkkkjkjkjkjkjkjjjjjiijiihiiihihihihihhhhghggghghggghggggggggggggghggggggghggggghghghghghhhghhhhihihihiiihiiiiihjijijjjikjlkkjlkllmlmlmlmlmmnmnnnnooooooppqqqqqqrqrrsrsrssstttttttttuuvvvvvvwvwwwwxwxxxxyyzz{{zz{{|{||||||}}~~HHIIIIJJJJKKKKLLLLLLMMMMNNNNOOOOPPPQQQRRRRSSSSTTTUUUVVVWWXWXXXXYZZZZ[[[\\\]]]^^^___``aaaaabbbcccdddeeeefggggghhiiijjjkkllmlmnonooppqqrssĀstƀuvǀuwȀuwȀwxʀwxˀyz̀z|΀{}π{}Ѐ}~Ӏ~Ԁր؀؀ڀۀ܀ހހည「倍怎怏耑ꀒ쀔퀖񀘛񀙛󀛝SSSTTUTVUWVWVXWYYZZ[Z[Z[[\]^]^^_^`_`_``aaccefhijjlnorsvwtuoqoqprsuxz{~~ǁ́ρՁՁ؁݁ځہցځyzvzuxx|~ׁ݁ҁ́ƁāÁÁÁ|~xzwyuwtutustrsqropnomnkmjkhjfhefdebcababab`b_a^_]^\]YZXYWXVWUVTUTUSTSTRTRS􀕗򀕗񀓕퀐뀎ꀎꀍ耋怊倉〈〆ހ܀ۀڀـ~؀}~ր{}Հ{|Ԁz|ӀyzрwyπwxЀvw΀uùtuˀstɀrsǀqrƀprŀpqĀopÀno€mnmnlmkljkjjiihighfgfgffeededdccbcbbab`a``__^_^^]]]][\[\[[ZZZZYZYYXYXXWXWWWWVVUUUUTSSSSSSSRRRRQQQPPPPPPPOOOONNNNMMMLMLLLLKKKJJJJJJJJIIIIIIIIHHGGGGGGFFFFFFEEEE~~~~~~~}~~}}}|||{{{{{{zzzzyyyyyyyxyxxxxwwwwwvvvvvvuuuuuuttttsssssssrsrrrrrrrrrqqqqqpqqpppoppooopopoooooopopoonononnnnnnnnnonnnnnonnonnonnnnnoonnnnnnooooooononpopopoppppqpqprqqqrqrqrqrqrqsrsrsrsrsststststststtttttuuuuvuvvvvwvvvvvxwxwxwxwxwxwxxxxyxyxyxyxzxzyzzzzzzzzzzzz|z|z||||||}|}|}|}|}|~~~~~~BBBBBBCBCBCCCCCCDCDCDDDDEEEEFEFEGFFFGFGFGGGGGGHGHHHHHHHHIHIHIHIHIHJIIHJIJIJIJIJJJIJJKJKJKJKKKKLKLKLKLKLKLLLLLKLLLLMMMLMLMLMMMLMMMMNMMMMMNMNMNNNNNNNNNNNNNNNNNMNNONNNONONONPOPOPOOOPPPPPOPPQPPPQPPPQPQPQPQPQPQPPPPPQPQQQQQQQQQQQQQPQQQQQQQPQQQQQQRQQPQPQQQQQPQQQQQQQQQQQPQQQPQPQPQPQPQPQPPPQPQPPPPPQPPPPPPPQPPOPPPPPPOOOOOOOOOOOOOOOOOONNNNNNNNNMNMMMNMNMNMMMMMLLMLMLMLMLLKMLMLLKKKKKKKKJKJKJKJKJKJJJJJJJJJJJJIIIIIIHIHIIIIIHIHIHIHHHIHHHHGHHHHHHGGGGGGGFGFFFFFFFFFFFFEEEEEEEEEDDDDDDDDDCCCCCCCCCCC~~~~~}~~}|}|}|}|}||{}|{z{z{{zz{zzyzyyyyyyxyxxxxxxxwwwwwwwwvvuuuuvtutuutttststsssssssrrrqqqqppppppppppoppoopoonooonononnnnmnmnmmlnmmlmmmlmllllklklklkkjkjjjkjkjkjjijjjijijijijijijijijiiiihihihihihihiiiiihihihihiiiijijijjjijijjkjjjkjkjlklkkkkjlklklkmlllllmlmlnmnmonononooppqpppppqqrqrrrrrssssssststtuuuuuuuuvvwwvwwwxxxxxxxxyyzz{{{{zz||}|}}}}~~~~~~HHHHIIJJJJKKKKKKLLLLLLMMMMNNNNOOOPPPPQRRQQSSSSSTTUUUVVVVWWWXWXXYYZZZ[[[[[\\]]]]^^__``a`aabbbbbccdddedeeffgghgihiijjkjkkllmlmmnnooppqpqqr€stÀstĀtuƀuvȀvxɀwyʀxzˀyz̀z{΀{}Ѐ{}Ѐ}р~ӀՀ׀׀؀ۀ܀݀​䀍倍怎瀐逑뀒쀓퀔񀘛񀙜󀛝RSSTTUTUUVUWUWVXWYWYY[Z\\^\^\^]^^_^`_``aabbcaccddefgijkmnoqsrsqsrtrttvuwxz}ÁÁā́ρЁՁׁځ߁~}|{|{{z||y}wzぼށՁ́ȁƁǁǁŁ~z}z|{|yzxxuvtustrsqrnpmnlnkmhkfhefdebdbcbcbcab_`_`^_\^\]Z\XZWYVWTVTVTVTVSUSTRTQSQR􀖘󀔖򀔖񀒔퀏뀎逌瀋怊怈䀇〆စ߀߀݀܀ڀـ~؀}׀|~ր{}Հz|ӀyzҀxyрwyπvx΀uẁtuˀstɀstȀrsǀqrƀpqƀopÀop€nomnlmlmklkljjijhihhggffefeeddcdccbcbbaaaa``__^_^^]^]]\][[[[[[ZZYZYYXYWXWXWWVVVVUUUUTTTTSSSSRRRRQQPQPQPPOPOOOONNNNMMMMLLLLLLKKKKKKJJJJJJIIIIIIIIIIHHGGGGGGGGFFFFFFEEEE~~~~~}}~~}}}||||{{{||{{{{zzzzzzzzyyyxxxxwwwwwvvvvvvuuuuuuuuttttttttssssssrrrrrrrrrrrqrrrrqqqqqqqqqqqqqqppppqqqqppqqppqqppqqppppqpqpqqqqqqqpqqqqqqqqrrrrrrrrrrsrsrsstssstttststtttutuuuuvuuuvvwvvvwwwwwwwwxxxxxxxwxxyxyxyxzxzxzxzxzzzzzzzzzzzz{{|||{||||||||}|}}}|~~~~}}~~~~BBBBBBCBCCCCCCCCDCDCDDDDDDDDDDDDEEEEEEEEGFGFGFHGHHHHHHHHHHIIHHIHJIIIJIJIJIJIJIKJKJKJKJLKKJLKLKLKLKLKLKLKLLLLLLLLMLMMNMNMNMNMNNMMNNNNNNNNNNNNNNNNOOOOOOOOOOOOOOOPOOOOOOPPPOPOPPPPPPPPPPPPQQQQQQQQQQQQQQQQRRRRRRRRQQRQRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRSSSSRRRRRRRRSSSSSSSSSSRRSSSSSRRRSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQRQRQRQRQQQQQQPQQQQQQQPPPPPPOPOPOPOOOOOOOOOOOOOOOOONNNNNMNMNMNMNMMMNMNMNMMMMLMMMLMLLKLKLKLKLKKKKJKKKJKJKJKJKJKJKJJJJIJIJIIIJIJIIIIIIIHHIHIHHHHHHHHGHGHGGFGFGGGFFFFFFFFFFFFFEEEEEDEDDDDDDDDDDDCCCCCC~~~~~~~~~~~}|}|}|}|{{{{{{{z{{zzzyyyyyyyyxyyyxxxwwvvwwwvwvvvvuvuuuvuututttssssrrrrrrrrrrrrqqqqqqqqqqqqqppoqpqppopopoononnnnnnnnnnmnmmmmmmmmlmlmlmllllllklkkkllkkkjkjkjkjkjkjkkkjjjkjkkkjkjkjkjkjkkjjkkkjlklklkkkkklklkmlmlmmmlmmmmnmnmnmnnnooooopoppppqqppqqqqrqrrrrrrssssttttuuvvuuuuvvwwwwwwwxxxxxxxyyzzzzzzzz{{{{{|||||}}~~~~~~HHIIJJJJKKKKKKKKKLLLMMMMNNNNOOPPPPPPPQQQRRSSSSSTTUUUVVVVWWXXXYXYYZZ[Z[[\\]\]]^^_^_```aaabbbcbcccddeeeefgfgghhhijjkjkjkkllmmnmnnooppqqrrs€stÀtuŀtvƀuwǀvxɀwyɀyz̀z{̀z|π{}Ѐ|~р}ӀԀԀր؀ـۀۀ݀߀‍倎怏瀐递뀓쀕񀙛󀚜RSTUTUUVVWVWVWVXWYXYY[Z\[\\]]^^`_`_a_a`a`aabbdceceeffgghijklnooqprrttvuwwyyz{}~ÁƁˁ́сց܁ށ߁y|~~灸فҁЁ́ρ΁ˁÁ~~}~y{wxuvvwuvstprpqoplojlgifhefefdfefcdbcababac`b^`]^Z\YZWXVWVWUWUWUWTVSUSTRTQRPQ􀔖򀔖򀒔쀏ꀎ逍逋瀊䀉䀈〆စ߀ހ݀܀ۀـ~؀~׀}~ր|~Հ{|Ԁz{Ӏy{рxyπwx΀vw΀vv̀uùstʀstȀrsƀqsŀprŀopÀop€nomnmnlmkljkjkijiihhggfgffffdecdbcbcbbaa````__^_^_]]]]\\[[[[[[ZZYZYZXYXXWXWXVWUVUVTUTUTTSSSSSSRRRRQQPQPQPPOOOONONNMNNMMMMMLLLLLLKKKKJJJJJJJJIIIIIIHHHHHHGGGGFFFFFFEEEEEEEE~~~}}~~}}}}}}||||||{{{{zzzzyyyyyxxxxxxxxxwwwwwwwwwwvvvvvvuuttuuuuuuuuttttttssssssttssssssssssssrrssrrrrsrsssssssssssrtstssssssstttsttttttttuuuuutuuvvvvvvvvuuwvwvwwwwxwwwxxxxxxxxyxyxyxyxzyyyzy{z{zzzzzzz{z|z||||||||||||||}}~~~~~~~~~~~BABBBBBBBBCBCBCBCCDCCCDCDDDDDDDDEEEEEEEEEEEEFFFFGFFFGFGFHHHHHHIIIIIIJIJIJJJJJJJJJJKJJJKKKKKKKKLKMLMLMLLLLLLLMMMMMMMMNMNMMMNNNNNNNNNONNOOOOOOOOOOOOOOPPPPPPPPPPPPPPPPPPQQQQQQQQQQQQQQQQQQRRRRRRRRRRRRRRRRRRRRSSRRRRSSSSSSSSSSSSSSTTTSTSSSSSSSTSTSTTTTTTTTTTTTUTTTTTTTUUUTUTUTTTUTTTTTTTTTUUTTTTUTTTTTTTTTTTTTTTTTTSTTTTTSTTSSTTSSSSTTSSSSSSSSSSSSSSSSRRSSRRRRSSRRRRRQRQQQRQQQQQQQQQQQQQQPQPQPPPPPPPOOPPOOOOOOOOONONONNNNNNNNNNMMLNMMMMMMMMMMLMLLLLLLLLKLLLLKKLKKKLKKKJJKJJJJJJIJIJIJIJIJIIIIIHHHHHHHHHHHGHGHGGGGGGGGGFFFFFFFFFFEEEEEEEDEDDDDDDDDDDDDDCCCCCC~~~~~}|}|}|}|||||}|{{{{{z{z{z{zzyyxyyyxyxxxxxxxxwwwwwwvwvvvuuuuuutttttttttttsssssssssrrrrqqqrrqrqrqqpqppppppppopopopopoooooooononnnmmnmnmmmmmmlmlmlmlmlmlmmmlmlmmmlmlmmnmmlmmmmmmmmmmnmmmnmmmnmnmononononpoonoooopoppppppppppqqqqqqrrrrrrssssttttttutttuuuuvvwvwwxxwxxxxxyyyzzzzz{{{{||||||}}||||}}}}HHIIIIJJJJJJKKKKKKLLLLMMMMMMNNOOOOPPPQQQQRRRRSSSSTTTTTUVVVVWWXXXXYYZZZ[\[\[\\]]^^^___``aaabbbbbccdcddeeefgfgghghhiijjkklkllmlmnoopoppqqrqrrs€stÀtuŀtvǀvxȀwyɀxyˀxz̀z{̀{|΀|}р}~Ҁ~Ӏ~Ԁր׀ـڀ܀܀ހހ‌」䀎怏瀑耒逓뀔񀚛󀛝STTUUVVWWXWXXYXZY[Y[Z[[\\]\^]^_``aacbcbcacbccddfegfhhihiiklmnopqqrrtsuuwvxxz{}Áǁρсԁԁځ߁vx܁ف؁فԁЁ́Ɂā~}~{}z{wyxyvwuvstqrpqnpklijghgifhgigiegdfdfdebcab_a]_\]Z[YZXYWXWXVWVWUVTVSUSTRSQRPRPQ󀔖򀔖񀒔퀑퀐쀎뀍逌耊怉倈䀇 ငဃހ܀ۀڀــ}׀}~ր{}Ԁz|Ҁy{ҀyzҀxyЀwxπvẁuvʀuvʀstʀrtɀrsǀqrƀpqĀopÀno€no€mnlmlmllkkjkjkijhihighfgefdecdcdbcabab`a``_`^_^^^^]]\\\\[[[[ZZZZZZYZXYXXWWWWVVUVUUTUTTTTTTSSRRQRQRQQQPPPPPOOOONNNNNNMNMMLLLLLLLKKKKKJJJJJJIIIIIIIIHHHHHHHHGGGGGGFFFFFFEEEEEEEEEE~~~}}}|}|}}|||||||{{{{{zzzzzzyyyyxxyyxxxxxxxxwwwwwwwwvvvvvvuuvvvvvvuuvvvvvvuuuuuuuuuuuuuuuuvvuuuuuuuuuuuuuuuuuuvvvvwvvvwwvvwvwvwwwwxxxxyyyyyxyxyyyyyyzyzzzzzz{z{z{z{z{z||{z||{z||||||}}||}}}}~~~~~~}}~~~~BABACBBBCBCBCCDCDCDCDCDDDDDDDDEEEEEEEEEEEEEEFFFFFFFFFFGFGFGGHGHGHHHGHGJIJJJJJIKJKJKKKJKKKKLLKKLLLLLLMLMMMMMMMNNNNNNNNNNNNNOOOOONOOPOPOPOOOOOPPPPPPPPPPPPPPQQQQQQQQQQQQRRQQQQRRRRRRRRRRRRSSSSSSSSRRSSRRSSSSSSSSSSSSSSTTTTTTTTTTTTTTTTUUUUUUUUUUUUUUUUUUUUUUUUVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUVVVVVVUUVVUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTSSSSSSSSSSSRRSSSSRRRRRRRRRRRRRRQQQQQQPQPQPQPQPPPPPPPPPPPPOOOOONNNNNOONNNNNNNNNNNMNNNNMMMMMMMMMLMLLLMLLLLLKKLKKKKKKKJJKJKJJJJJJJIIIIJJIIIHIHIHIHHHHHHGGGGGGGGGGGGGGGFFFFFEFEFEEEEEEEDDDDDDDDDD~~~~~~~~~}}}}}|}}||{{||{{|{zz{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvvvuuuuuuttttuttttttsttttsssssrsrsrrrrrrqqqrqqqqqqqqqqqqpqpqppopopopoononoopoonononononoonnnnoopopopooooopooopooooopopopppoqpppqqqqqpqpqqqqrqrrrrrrsrsrssssttttttuuuuuuvvvvvvvvwwxxxxwwyyyyyzyzz{zz{{{{||||}}}}}}}}~~~~HHIIIIIIIIJJJJKKKKLLLLLLLMMMMMNNOOOOOPPPPQQRQRRRSSSSTTTUUUVVVVWWWXXXXYYZZ[[\[\\]]]]^^___```aaabbbbccccddeffffgfgghhihjikjlklkllmmnmnnopqpqqrrs€st€tuŀtuƀuvǀvwȀwxʀxzˀy{̀{|΀{|΀|~Ѐ}рӀԀՀ׀؀ۀۀ܀ހညဌ」䀎倐耑递ꀓ뀔퀕񀙜󀙜󀛞STSUTVTVUWVWWXXYYZY[Z\[]\]\^]^]^]__``bbccddedfefdfeggihjikjlkmmnoqqrrstuvwwywywyyz|}āǁʁҁԁׁ؁܁qrww}}zzށہففсǁŁ~}|{}|~y{xyvwsurtoqlnkmjljlikjlhjhihighegdeac`b_`^_\][]Z\Y[XZWYWXWXVWUVTUSTSTRSQSQS􀖘󀔖򀓖񀑔퀑퀐퀎뀍逌耋怉倈䀇々စ߀݀݀ۀۀـ~؀}׀|}ր{|Հz|Ӏz{ҀxyрxyЀvx΀vx̀uwˀtvɀtuɀstɀrsǀqrƀpqĀopÀopÀno€mnmnlmllkljkijhihighfgefefdeddcdbcbcab`a`a__^_^^^^]]\]\\[\[[ZZZZYZXYXYXYWWWWVWUVUVUVTUTUSTSTRSRRRRQQQQQQPPOOOOOOOONNMNMNMMMMMLLLLLKKKKKJJJJJJJIJIIHHHHHHHHHHGGGGGGGGFFFFFFFFEEEEEEEEDD~~~}}~~~~}}}}||||||{{{{{{{{{zzzzzyyyyyyyyyyxxyyyyyyxxxxxxxxxxxxxxxxwwxxwwwwwwxxwwwwxxxxxwwwwwxwwwwwxxxxyxyyyyxxxxyyzyzyzzzzzz{{{z{{{{{{|||||||||}~~~~~~~~}}~~~~~~~~BBBBBBBBCBCBCCCCCCDCDCDDDDDDEEEEEEEEEEEEEEEEEEFEFEFFFFGFGFGGGGHGHGHGHHHHHHIHIIIIJIIIKKLKKKKKLLLLLLLLMMMMMLMMMMMMNNNNNNNNNNOOOOOOPPPPPPPPPPPPPPPQQQQQQQQQQQQRRRRRRRRRQRRRRRSSSSSSRRSSSSSSSSSSSSSSSSTTTTTTTTTTTTTTTTTTUUUUUUUUUUUUUUUUUUUUVVUUVVVVUUVVVVWVVVVVWWWWVVVVVVVVVVWWWWWWWWXXWWWWXXXXXXWWWWWWWWXXWWWWWWWWXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVWWVVWWVVVVVVVVVVVVVVVVUUUUUUUUUUTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSRRRRRRRRRRRRQRQRQQQQPQPQPQPPPPPPPPPPOPOPOOOOOOOOOOOOOONNNNNNNNMMMMMMMMMMMMMLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJJIIIIIHIHIHIHHHHHHHGGGGGGGGGGGFFFFFFEFEEEEEEEEEEEDDDDDDDDDDDDDD~~~~~}}}}}}}}|||{|||{|{|{{zzzyyzzyyyyyxxxxxxxxxwwwwwwvvvvwwvvvvvvuuuuuuuuttttttttttttttssssssssssrrrrrrrqrqrqqqqqqqqqqpqqqqqqqqqqqpqqqqqqqqqqqqqpqqrrqqqqqqqqrrrrrrsrsrsssssrtssstststtttutututuuvuvvuvvvwwvvvvwwxxxxyyyzyyyyzzzzz{||||||||||}}}~~~~~~~IIIIJJJJJJJJKKKKKKLLLLMMMMNNNNOOOOOOOPPQPQQRRRSSSTSTTUTUUVUVVWWXXXXYYYYZZ[[\\\]]]]^^^__```abbbbcbccddedeeffffgghhiijijjkkllmmnmnnonooppqqrqrrsÀsuĀtuŀtvƀvwȀvwɀwxˀxỳy{̀{}π|}Ѐ}~р}ҀԀՀ׀؀ـڀ܀ހ߀​「䀎怏瀏耑逓뀔쀕񀙜򀛞􀛞SUTUUVUWVXWYXYYZY[[\[][]\^^__`_``a`baccddfeffgghgihjhjikkmlnmooqqrrsstuvvxyzz{{}{}{}|}ǁ́́΁ԁցցہ߁uvwx~~}~偿聴߁сˁǁā~}~y{vwsuqsoqoqoqmolnmolmkljkikhjghdebcab`a^`]^\]\]Z[Y[XYXYWXVWVWUVUVTUSURTQSQSPR󀕘󀓖񀒔퀏뀎逍而瀊怈䀈䀆 ߀߀݀݀ڀــ~؀|~ր|}ր{|Ԁz|Ӏy{рxzπxzЀwx΀vẁuẁuvˀtuɀrtǀrtƀqsŀqrŀpqÀoqÀnpmnmnlmllkljkijijhighggfgefdedecdbcbcabab`a_`__^_^^]]\]\]\\[\[[ZZZZYYXYYYWWWWWWVWUVUUTUTUTTSSSSRSRRQRQRQQPQPPPPOPOOOONONNMNMMMMMMLLLLLLKLKKJJJJJJJJJJIIIIIIHHHHGHHHGGGHGGFGFGFFFFFFEEEEEEDD~~~~~~~}~}}|}|}||||||{{{{{{{{|{{{{{{{{{zz{z{{zzzzzzzyzz{zzzzzzzzzzyzzzz{z{zzzzzzzzz{{{{{z{{{{{{{{{{||||}|}|}|}|~~~~~~~~~~~~~~BBBBBBBBCCCBCBCCCCCCCCDCDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFGFGFGFGGGGGGGGGGGGHHHHHHIIIIIIIIJJJJJIJIKJKJKKKKLLMMMMMMMNNNNNNNNNNNNNOOOOOOOOPPPPPPPPPQPQQQQQRRRRRRRRRRRRRRRRRRRSSSSSSTSSTTTTTTTTTTTTTTTTTTUUTTUUTTUUUUUUUUUUUUVVVVVVVVVVUUVVVVWWVVVVVVVVWVWWWWWWXWXXWWXWXWXWXXXXYXXXXXXXXXXXYXYYYYYYYYYYYYZYYYZYYYYYZYZZZZYYZZZYZYZYZYYYYYYYZYZYZYZZYYYYYYYYYYYYZYYYYYYYYYYYYXYYYXYYYXYXXXYXXXXXXXXXXXXXWWWWWWXXWWWWWWWWWWWWVVVVVVVVVVVVVVVVUUUUUUUUTTTTTTTTTTTTTTTTSSSSSSSSSSSSRRSSRRRSRRRRRRQQRRQQQQQQQQQQQQPQPQPPPPPPPPPPOOOOOOOONNNNNNNNNNMMMMMMMMMMLLLLLLKLLKKKKKKKKKJJJJJJIIJIJJIIIIIIHHHHHHHHHHHGHHHGGGGFGFGFFFFFFEEEEEEEEEEEEEEEEEDDDDDD~~~~~~~~~~~~~}}}}||||||{{{{{{{{{z{{{zzzzzyyzyyyxxxxxxxxxxwwwwwwwwwvvvvvvvuvuuuuuuttuuttuuutttttttssssttsstssststsrrsssssssssssssssssrssssttssssttttttttuutttuuuuuuuuuvvuvvvvvwvwwwwwxwwwwwwxxyyyyyyyyyzyz{{{{{{|||}||||}}}}}}~~~~~~HHHHHHIIIIJJJJKKKKKKKLKLLLLMMMMMNNNNOOOOOOPPPPQQQRRRSSSSTTTUUUUVUVVWWWWXXYXYYZZ[[[[\\]]]]^^^^___``aaabbccccddedefgfggghihiijjkklkmlmmnnoopoqpqpqqrrs€stÀtvĀtvŀuwǀvxȀwyɀxzʀy{̀z{̀z|π|~Ѐ}Ҁ~ӀՀր؀؀ڀ܀ހހ߀‌」怎瀏瀐耒ꀒ쀔퀕񀙜򀚝󀛞STTUTUUWVWVXWYXZY[Z[[\\]\^]^]__`_a`babacbdcdegfhghhihjjljlkmlnnpprqsrttvuwwyxzz{{||~~~ÁˁЁӁځفہ݁xzvwxx̺Âѿϸȳxy䁷فԁ́ˁȁƁ~{|xyuwtvsurtqsqsorornomolmkmklikgiegcebc`a_`^`^_\^[]Z\Z\YZYZXYWXVWVWUVTUSTSTRSRSQR􀕘󀔖񀔖퀑쀎ꀎ逌瀌耊怉〈〇 ߀ހ݀ۀڀـ؀~׀}~ր|~Հz|Ӏz|Ӏy{ЀxzЀxyπvx̀vx̀uwˀuwʀtuɀrtǀrtƀqsŀqsĀpqÀopÀnonomnlmklkljkjkijhighghfgefeedecebdbcacab`a``_`__^^]^\]\\\\[\[[Z[ZZYZYYXYXXWXWWVWVVVVUUTUTUTUSTSTRSRSRRQRQRPQPQPQPPPPOONONONNMNLMLMMMLLLLKLKKKKJJJJJJJJJJJJIIIIHIHIHHGHGHGGGGFGFGFFFFFFFFEEEEEE~~~}~~~~~~~~~~~}}}}}}~}~~}}|}}}||||}}||||}}||||}}}}|||}|}|}}|}}}~}}}}~~}}}}~~}}~BBBBCCCCCCCCCCDCDDDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEFFFFFFFFFFFFGGGGGGHGHHHHHHHHHIIIIIIIIIIIJJJJJJJJJJKJKKKKKKKKKKKKLLLLLLMLNNONNOOOOPPPPPPPPPPPPPQQQQQQQQQQRRRSRRRRRRSSSSSSSSSSSSSSSSTTTTTTTTTTTTUUUUUUUUUUUUUUVVVVVVVVVVVVVVVVWWVVVVWVWWXWWWXXXWWWWWXXXXXXWXXXXXYXXXXXYYYYYYYYZZYYZZYYYYZZZZZZZZZ[[[ZZ[[ZZ[[[[[[[[[[ZZ[[[[[[[[[[[[[[[[[[\\[[[[[[[[[[\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZ[Z[[ZZZZZZZZZZZZZZZZZZZZZYZYYYYYYYYYYYXXXXXXXXXXWWXXXXWWWWVVWWVVVVVVVVVVVVUUUVVVVVVVUUTTUUUUTTTTTTTTSSTTTTSTSTSSSSRSRSRRRRRRRRRRRRRRQRQRQRPQPQPPPPPPPPPPOOOOOOOOOOONNNNNNNNNMMNNMMLLMMLLLLLLLLLKKKKKKKJJJJKJJJJJJIIIIIHHHHHHHHHHHHHHHGHGGGGGGFGFGGFFFFFFFFFFEEEEEEEEEEDDDD~~~~}}~~}|}}}}}}||||||{{{{{{{{{{zzzzyyyyyyyyyyyyxxxxxxwwxxwwwwwwwwwwwvwwvvvvvvvvvvvvvvvvuuuuuuvuuuuuuuvvuuvvvuuuuuvvuuuuuuuvvvvvvvvvvvvvvvvwwwwwwwwwxxxxyyyyyyyyyyzzzzyyzzzz{{{|{|||||}}}}~~~~~~~~HHHHIIIIIIIIJJJJJKKKKKLLLLLMLMMMNNNNNONOOOPPPPQQPQQQRRSSSSTTTUTUUVVVVWWWWXXYYYYZZZZZ[[\\\]]^^^^__````aaabbbccddddeeffgfhgihihjijjlkllmmnmnnoopopprqrrsrtsuÀtuŀuwƀvxȀvxɀxzʀy{ˀy{̀z{΀|}Ѐ|}Ѐ}Ҁ~ӀՀր؀؀ڀۀ܀݀߀」䀍倎怐逑递途쀕퀕񀙜󀚝󀜟STTUUVUVUWVXWYXYYZZ\Z\\]]^]_^`^`_``aabbdcecedeegfhgihjikkllnlnmonpprqtsutvwyy{{}{~|~ŁˁՁځၼ׻ǂ˂΂łƶıµƴķǻyyၼ݁Ձρ΁ʁƁÁ|~z|xzwyuwtvsurururtqtornpnpmoknikhjfhdeceac`b`b^`]_]^\^[]Z\Z[YZXZWXWXVWUVTVTUSURTRSQR󀕘󀔖񀔖퀐뀎ꀍ耋瀊怉䀈䀇〆ဆ߀ހ܀܀ڀـ~؀~؀}׀|~Հ{|Ԁz|Ҁz{рy{рxzЀwy΀vx̀vxʀuwɀtvȀsuȀrtƀrtƀprĀqrĀoq€npmnmnlmlmkljkjkijijhighfgfgefdececdbdacab`a`a``_`__^_]^\]\][\[[[[[[ZZYZYYXXWXXXWWWWVVVVUVUVTUTTSTSSRSRSRSQRQRQQQQPQOPOPOPNONONNMNMNMMLMLMKLLLKKKLKKKKJKJKJKJJIJIJIIHIHIHIHIHHGHGHGGGGFGFFFFFFFFFF~~CCCCCCCCCCDDDDDDDDDDDDDEDEEEEEEEEEEEEEEEFFFFFFFFFFFFFFFGGGGGGGGGHHHGHGHHHHHHHHIIIIIIIIJJJJJJJJJJJJJKKKKKKKLKLLLLLLLLMMMMMMMMMMNNNNNNNNPPPPPPQQQQQQRRRRRRRRRRRSRSRSSSRSSTSTTTTTTTTTTUUUUUUUUUVVUVUVUVVVVVVVVVVVWWWWXXWWWWWWXXWWWWXXXXXXXXXXXXXXXYYYYYYYYYYZYZYYYYZZZZZZZZZZZ[ZZZZ[[[[[[[[[[\\[[[[\\\[\[\\\[\\\\\]\]]]]\\]]]]\]]\]]]]]]]]]]]^]^]^]^]]^]^]^^]^]]]^]]]]]]]]]]]]]]]]]^]]]]]]]]]]]]]]]]]]]]\]\\\\\]]\\\\\\\]\\\\\\\\\\\\\\[[[[[[[[[[ZZZ[Z[YZZZZZZZZYZYYYYYYXXYXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVUUUUUUUUUUUUUUUUTTTTTTTTTTTTSTSSTTSSSTRSRSRSRRRSRSQRQRQQQQQQQQQQQQPPPPPPPPOOOOOOOONNNNMMMMMMMMMMMMMMLLLLLLLLKKKKKKJKJKJJJJIIIIIIIIIIIHIIHHHHHHHGGGGGGGGGGGGGFFFFFFFFFFEEEEEEEEEEEEEEDDDDDD~~~~~~~~~~~}}~~}}}}|||||{||{{||||{{zzzzyyyyyyyyyyyyxyxxyyxyxyxxxxwxxxwwxxxxwwwwwwwxwxxxwwwwwxwwwwwwxxwwxxxxxxxxxxxxxxxxxxxxxxyzzzzzyyzzzzzzzzzz{{z{{{{{{|||||||}}}}}}}~}~HHHIHIIIJJIJJJJKKKKKKLKLKLLMMMMMMNNNOOOOOPPPPQPQPQQRQRRRSSSTSTTTTTUUUVVWWXWXXYYYYZZZZ[[[[\\]\]]^^___```aaaabbccdcddeeffgghghhihjjkjkklkmlmmnnooppqqrqrrsrttu€tvÀuvĀvxƀwyȀwyʀxzʀy{̀z|̀{}΀|~Ѐ}р}ҀԀՀր׀ـڀۀ݀ހ߀ဋ‍『倏瀐递递ꀔ뀕쀖򀙜󀛞RTSUSUTUUWUWVXWXWYXZY[Z\Z\\]\^]_^`_``a`aabbccddfegfhfhgihjikjllmmnmooqorqssusvuwwyx{{}}~āǁˁҁف{~ϱۺĂтЂӂЂ͂͂ɂɂ˂ɂ˂Âź~~vv⁺ہԁссˁǁā~|z}y|x{wyuxvyuxsvruqsqsoqoqmokmjlhjegegcfcebd`c`a_`^`]_\^[][]Z\Y[XZWYWXVXUWUVTVTUSTRSQRPR󀕘񀕘񀓖퀏뀎ꀍ逌耋瀋怉䀈〇 ဆ߀ހ܀ۀڀـ؀~׀}~׀|~ր|~Ԁ{}Ӏz{рy{Ѐyzπwỳvx̀vxˀuwɀtvȀsuȀstǀrtƀprÀoq€op€oonomnlmlmkljkjkijhihighfgfgfgefdececdbcabab`a`a_`__^_^_]^]]\\\\\\[[Z[Z[YZYYYYXXXXWWWXVWVWUVUVTUTUTUSTSSSSRSRRQRQQPQPQPQOPOPOPNONONOMNNNMNMNLMLMLLKLKLKLJKKKKKJKJKJJIJJJIIHIHIHIHIGHGHGHGGGGGGFFFFFFFFFFEEDDDDDDDDEEDEDEDEEEEEEEEFEFEFEFEFEEFFFFFGFFFFGGFFGHGHGGGHGHGHGHHHHHGHHIHIIIIIIIIIIIJJJJJJJJJJJJJKKKKLKKKLLLLLLLLMMMMMMMMMNNNNNNNNNONNNONOOOOOPPPPPPRRRRQRRRRSSTSTSTSTSSTTTTTTTTTTUUUVUUUVVVUVVVVVWWWWWWWWWWWWWWWWXXWWWXXXXXXXXXXXYYYYYYYZYZYYZYYZZZYZYZZ[Z[[[ZZ[[[[[[[\[[[\[\[\\\\\\\\\\\\\\\\]\]\]]]]]]]]^]^^_^^]^^^^^^^^^^___^____________`_`_`_`_`_`_`_```__``````_____```_`_`_`_`___`_____________`_`_`____^_^___^_^_^_^_]^^_^_^_^^^^^^^^^^^^]]]]]]]]]]]]\\\]\\\\\\[[[[[[[[ZZZZZZZ[YZYZZZYZYYYYYYYYYYYYXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUUUTUUUUUUUTTTTTTTTSTSTSSSSSTRSRSRRRRRRRRQRQRQQPQPQPPPPPPOOOOOONONNNNNNMNMNMMMMMMMMLMLLLLKKKKKKKKKKJJJJJJJJJJIIIIIIHIHIHIHHHHHHHHHHGGGGGGGGFFFFFFFFEEEEEEEEEEEEEEEEDDDD~~~~~}}}~}}}}||||||}}||||{{{{||{{{{{{{{{{{{{{zzzzyzyzzzyyyyyyyyyyzzzzyyyyxyyyyyzzyyzzzzzzzzzzyyzzzzzz{{{{{{{{{{{|||||}}}}}~}}~~~~~~~HHHHHIIIIIIJIJJJJKJKKLKLKLLMLMMNMNMNNONONOOPPPPQPQQRQRRSRSSSSSSTTTUUUVUVVVWXWXXYXYYZYZZ[[[[\\]]]]^^____``a`aabbcbccddedeeffgghgihjikjlklkmlnmnnooppqqrqrrsstuv€uwÀuwĀwxǀwxǀwyǀxzɀz|̀y{̀{}΀{}΀}~р}Ҁ~ӀԀՀր؀ـۀۀ݀ހဍ‎』倐怑耒逓ꀔ쀖퀗񀙜򀚝󀜟􀞡TUTUUVUVVWWXWXXYXZY[Z[[]\]\^]__`_a`b`babbddedeegghhiijikjlkmlnmpnpoqprqsrttvuxvywzxz{~~Ɓȁˁ΁ԁځ߁İݻ҂܂قrpturނނՂۂۂׂłĂҿxy䁻߁؁ׁՁҁ́ʁƁā|{~{}xzwywywyuwsvsurtqsqsnpmokmikhigifhegdfceceac`b_a_a^`]_\^[]Z\Y[Y[XZWYWXVWVWTVTVSUSTRSQR󀕙񀓗퀑쀏뀎ꀍ逌耋瀊倊倈〇 စ߀݀܀ۀڀـ؀׀}ր|~Հ{}Ԁ{}р{|рy{πyzπwỳwỳvxˀuwʀtvȀtuǀstƀrtŀqsÀoq€oqoqnpnomnmnlmkljkjkhihighghfgfgefdfdecdcdbcababab`a_`_`__^^]^]^]]\][\[\Z[ZZYZYYYYXYWXWXWWWWVVVVTUUVTUTUTTSTSTRSQRRRQRQQPQPQPQPPOPOPNONONONOMNMNMMMMMMLMLLKLKLKLKLKKJKJKIJIJIJIJHIIJHIHIGHGHGHGHGHGGFGFGFGFFDDDDDDDEDDDDEEEEEEEEEEEEEEFFFFFFEFFFFFFFGGGGGGGGGGGGGGGHGHGHGHHIHIHIHIHIHIIIIJJJIJJJJJJJJJJJJKJKJKKKKKKKKLKLKLKKKLLLLMLMMMMMMNMNNNNNNNNONONOOOOPOPPPPPPPQQQQQQQQQQQQQQSTTTTTTTUUUUUUUUUUUUUUVVVVVWVVWWVVWWWWWWWWXXXXXXXXYYXXYYYYYYYYZZZZZZZZZZ[[ZZZ[[\[[[\[\[\[\\\\\\\\]\\[\\]\]\]\]]^]^]^]^]^]^]^^^^_]^^_^_^_^_^__`^______`_`_````````````aaa`````a`a`aaa`aababababab`aaaababbbababababababbbbbabbbbbbbababababbbaaaaabaaabababaaaaaa`aaaaa`a`aaa```a`a`a_```_`_`_`______^_^_^_^^^^^^^_^^]^]^]^]]]]\]\\\]\]\\\\\\[[[[[\[[[[Z[ZZZZZZZZYZYZYYYYXYXXXXXXXXXXXXXXWWWWWWWWWWVVVVVVVVVWWWVVUUVVUUUUUUUUTTTUTTTTTTSTSSSSSSSSRRRRRRQRPQQQQQQQPPPPPPOPOOOONONONNNNNNNNNNNNMMMMMMLMLLLLLLKKKKKKKKKKJKJJJJJJJJIIIIIIIIIIHHHHGGGHGGGGGGGGGGGGFFFFFFFFEEEEEEEEEEEEDD~~~~~}~}~~~}~}}||}}}}}}}}|||||||||||{||{|{|||{{||{|||{|{|{|{|||{{||||||||}}|||}}}}}|||}~~~~HHHIIIIIIIIIIJJKJKJKJKKLKLLMLMLMMNMNNONNNOOOOPOPPQPQQRQRRSRSRSSTSTSTTUUUUVUVVWVWWXXYXYYZYZ[[[[[\\]]^]^^_^__``aaaabbccdddddeeeffgghgihjijjkjllmlnlnnonpoppqqsrssttu€tvÀvxŀwyƀwyƀxzȀy{ʀy{ʀ{}̀{}̀|~̀}π~рҀԀՀր׀ـڀ܀܀ހ߀ဍ」倏怐瀒耓途ꀕ퀕퀗򀜟􀜟TUTVUWUWVXWYWYXZY[Y[Z\[][]\^]_^`_a`bbcbdcecfdgehfhgjhkikkmlnlonpoqpsqsrtrtsutwvxwyy{z||~~ƁɁˁ΁сՁ߁xz̴ۻȂnvu~{|swr؂΂т˂łɼ||tu聹ぶ߁ځׁցԁс́Ɂǁف}{~{}z|z|x{vyuwuwuwsuqsprnpmokmjlikhjfifhegdfdfcebdac_a^`^`]_\^\^[]Z\Z\XZXZWYVXVXUWTVSUSTSTRTRS񀕙񀓗퀐쀏뀎逌而耋怊倉䀈 ဆ߀݀܀܀ڀـ؀׀~Հ}Ԁ|~Ӏ|~Ӏ{|рz{Ѐy{πwỳvx̀uwʀuwɀtvȀtvǀsuƀrtĀrtÀqs€proq€oqnpmomnlmkljljkijhihighghfhfgefdedecdbdacabaaab`a_`_`^_^_]_]]]]\][\[\[[ZZYZXYYYXXXXWXVWWWWWUVUVTUTUSTSTSSSSRSRRQRQRQRPQPQPQPQOPOPOPOPOPNONONONNMNMNLMLMLMLMLLKLKKKKJKJKJKJKJKIJIJIJHIHIHIHIHIGHGHGHGGGGGGFGFGFFEEEEEEEEEEEEEEFFEFFFFFFFFGFGFFFFGGGGGGFGGGGHGHHHHHHHHIHHHHHIHIHIHIHIIIIIIJIJIJJJJKJKJKJKJKJKJKKKKLKLKLKLKLLLLMLLLMMMMNMMMMMMMNMNNONONONOOOOOOOOOOPPPPQQQQQQQQQQQRRRRRRSSRSRSSSSTSSUUUUVVVWVVVWVWWWWWWWXXXXXXXXXYXYXYXYYYYYYZYZZ[Z[[[[[[[[[Z[[\[\[\\\[\\\\]\]\]]]]]]]]^]^]^]^]^]^^^^^^_^_^__`^____`______``_`_```_``````a`a`aaaaaaaaaababaabbbbbcabbcabbbbcbcbcbccdcdcdcdcdcdcdddcdcdcdcdcddededecdddcddededdddcdcdddcdddcdccddcdcdddcdcdcdcccdccccbcccbcbcbcbcbcbcbcbcababababaaaaaa`aab`a`a```a```a_`_`_`_`^___^_^_^_^_]^]^]^]^]^\]]]]^]]\\\]\\\\[\[\[\[[Z[Z[Z[ZZZZZZZZYZYYYZXYYYYYXXXXXXXXXXWXWWWWWWWWWWWWWWVWVWUVUVUUUVUVTUTUTTTTTTTTTTSSRSRSRRQRQRQRQRQQQQPQPQPPOPOPOPOOOPOOOONNNNMNNNMNLMMMLLLLLLLLLLKLKLKKKKKKKKJJJJIJIJIIIIIIIIHHHHHHHHHHHHGGGGGGFGFGFGFFFFFFFFEFEFEEDEEE~~~~~~}}}~~~}}~~~~~~~}~}~}~~~~~~~HIIIIJIJJJJKJKJKKKKLKLLMLMLMLMMNMNMNNOOOOPOPOPPQPQPQQRRSRSRSSTSTTTTUUUUVUVVVVWWXWXXXWXXYYZZ[[\\\\]]]]^^__`_``a`bababbccddeefeffgfhghhjijikjlkmmnmnnonpopprqrrssttutvÀuwĀwyƀwyƀxzǀy{Ȁz|ʀ{}ˀ|̀}΀}π~рҀӀӀՀ׀؀ـۀ܀݀ހ߀ဍ『䀏倐瀒逓ꀕ뀕쀖񀚝󀜟􀞡SUTVUWUXVXVXWYXZY[Y[Z\[]\^\^]_^_^`_a`bacbdcfdgehehfihjhkjljmknlnmpnqprqsrutwuxvywyxzy|z}|~}Áāǁˁ́ρԁׁ݁y{~µÂʂՂnv}w؂΂ĂǼ}}xyꁿ偽⁹݁ށځׁՁс΁ˁǁƁŁÁ}|~|~{}y|x{wyvxuwsuqspsnqmolnkmjlikhjgifhegdfcecebdacac_a_a^`]_]_\^[]Z\Y[XZXZWYWYVXUVTVTVSUSTRT󀖙󀕘󀔗񀓖퀏쀎ꀍ逍而瀋倊䀉〉〇߀ހހ݀܀ڀـ׀ր~Հ}Հ|Ӏ|}Ҁ{|рz{Ѐxz΀xz̀wỳvxʀvxʀuwȀtvǀtvƀsuŀrtĀqsĀqsÀpqopopnonomnlmkmkljkijijhighghfgefefdfdecdbdbcacab`a`a_a_`^_^_]_]^]^\]\][\Z[Z[YZZZYYXXXXXXWXVWVWVVUVUVUVTUTUSTSTSTSSRSRSRRRRRRQRPQPQPQOPOPOPNONONONONNNOMNMNMMLMLMLLKLLLKKKLJKJKJKJKIJJKIJIJIJHIHIHIGHHHGHGHGHGHGGGGGHFGGGFGFGFGFGFFFFFFFFFFFFFFFFFFEFFGFFFFFFFGFGFGFGGGGHGHGHGHGHGHGHGHHIHIHIHIHIIIIIIJIJIJIJIJIJJJJJJKKLJKKLKLKKKLJKKLKLLLLLLLLLLMLLLMLMMMMMMMMMMMMNNNNNNONONONOOOOPOOPPPPPPPQQQQQQQPQQQQRQQRRQRRSSSSSSTTTTTTTTTTUTUUUUUUUXYWXXXXYXYXYXYXYYYYYYZZZZZZZZ[Z[[\[\[\[\\\\]\]\]\]\]]]]^]^]^^_]^]^^_^_^^^______`_`_`_`_``a`a`a`a`aabaa`aaaaaababababbbabbcabababbcbcccbcbccdbcbcbcddcdcddddedddddedededeeeeeeeeeeeeeeeefefffffeffffffffffgfffffffffffffgeffffffgfgffffeffgfgeeffffefefefffeeeedeefefeedeefefdedededdddddcdcdcdcccdcdccbcbcbcbbbbababbbabaa`aab`a`a`a_``a`a`a_`_`^__`^___^_^_^^]^]^]^]^\]]^\]\]\]\]\][\[\[[[\Z[[\[[Z[ZZZZYZZ[YZYYXYXYXYXYXXXXXXWXWXWXVWWWWWVVVWVWVVUVUVTUTUTTTUTTSTSTSSSSSSRSRSRSQRQRQQQQQQPQPQPPPPOPOPOPNOOONNMNMNMNMNMNLMLMLMLLLMLMLLKLKKJKKKKKIJIJIJIJIJIJIIIJIIIIIIHHHHHHGHGHFGGGFGFGFFFGFGFGEFEFEEEEDDEEHIHIIJIJIJIKIKJKJKKLKLKLLLLMLMLMMNMNMNNONONOOPOPOPPQPQQRQRRSRSSTSTSTSTTUTUUVUVVWVWWXWXXYXYXYYZYZZZ[[[\\]\]]^^__````aabbcbdcdcededefgggghhihiikikklkmlmmonoopoqqrqsqssttutv€uwÀvxĀxzǀxzǀxzǀz|ʀ{}̀{~̀|̀}΀~ЀҀӀԀՀր׀؀ۀۀ܀݀߀ဌ‎䀏倐怑瀒途뀕쀖퀗򀛟򀜡􀞡SUTVUWVXVYWYXZY[Y[Z\[]\^\^]_^`^`_a`bacacbecfdgegfigjgjhkiljmlompnpnqorqssutwuxvyxzxzy|{}{~}~Áāǁʁ́Ёсԁ؁݁}ɂՂ߂v|ǩ߯ଃˢ {삁sނЂł̱{}ほ݁فԁс΁ˁˁǁÁ}~~}|z}y{vxuwsvrupsoqnpmolnlnkmjlikgigifhegdfdfcebdac`b`b^a^`]_\_[^Z\Z\X[X[WZWYVYVXUWUVTUSURTRT󀗛󀖚󀕙퀐쀐뀏ꀎ耍而怋䀊䀉〈 ဆဆ߀݀܀ۀڀڀـ׀~Հ~Ԁ|~Ҁ|~р{}Ѐz|πy{΀y{΀wỳwxˀvxɀuwȀtvǀtvƀtvŀsuĀrtĀqs€qsprpqnonomnlnlmkmjlijikhjhighfhfgefefdfcecebdbcacac`b`a_a_`^_^_]_]^]^\]\][\Z[Z[Z[YZYZXYXYXYWXWXWXVWVWUVUVUVTUTUTUSTTTSTSTRSRSRRQRQRRRQRQQPPPQOPOPOPOPOPNOMOMNMNMNMMLMLMLMKLKLKLKLKLKLJKJKJKJKIJIJIJHIHIHIHIHIHIHIHIHHHHHIHHGHGHGHGGGGGGFGGGFGFGFGFGFGFGFGFGFGFGFGFGGGGGGGGGGGGGGHGHGHGGGHGHGHGHGHGHGHGHHHHHHIHIHIHIHIHIHIIJIJIIIJIJIJIJIJIJIJJKJKKKKKKKJKKKKLKLKLKLLLLLLMLMMMMMLMMMMMMNMMMNMNMNNNNONONOOOOONONOOOPPPPPPPPPQPPPQPQQQQQQQRRRRRRRRRSSSRSSSTTSSTTSTTUTUUUUVVWUVVVVVVVVWVWWWWWYZZ[Z[Z[Z[Z[Z[[\Z[[\[\\]\]\]\]\]]^]^]^]^^_]^___`^__`_`_`_`_`_`_`_``aaaaaab`aababababbbabbcbcbbbcbcbccdcdcccdcdcdcecdcddedededeefdedfefefefefefegeffffgegfgfgfgfgghfgghÀghÀgh€ghghgh€hi€gh€ghÀghÀghÀhiĀhiÀhiÀhiĀhiÀhiĀhi€hiÀhiÀhiĀhiĀhiÀhiÀhiÀhiÀiiŀhiĀhiÀhiÀhi€hiÀhiÀghÀhi€hiÀhiÀhi€hi€gigh€ghÀgh€gh€gh€hh€ghghghfhghghfgghfhfgfgfgfgffefefefdeefdeefdedededecdcdcdcdcdbdccbcbcbcabbbbbababab`a`a`a`a`a_`_`_`_`_`_`^__`^_^_^_^_^^^_]^]]]^]^\]\]\]\][\[\[\[\[\[[Z[Z[Z[Z[YZYZYZYZYZYYXXXXXXXXWXWXWWWWVWVWUVUVUVUUTUTUTTTTTTSTSTSSSSRSRSRRRRRRQQQQQQQQPQPPPQPPOPOOOPNONONNNOMNMNMNMMLMLMLLLLKLLLKLKLJKKKJKJKJKJJJJIJIJIJIJIIIIHIHIHIHIGHGHGHGHGGGGGGGGFGFGFFFFIJIJIJIJIJIKJKJKJKJKKLKLLMLMMNMNMNMNMNNONONONOOPOPOPOPPQQRQRRSRSRSSTSTTUTUUVUVUVVWWWWXXYWXXYXYYZYZZ[Z[[\[\\]\]]^^_^__``aabacacbdcededffgfhghgihjijjkjlkllmmnnonpoqprqsrtrtsutvuwÀvxĀwyŀxzǀz|Ȁz|ɀ{}ʀ{~ˀ|̀}̀πЀрԀՀՀր؀ـۀ܀ހ߀‎『䀏怑瀒耓ꀕ뀖쀗򀛟򀛟򀝡􀞣SUTVUWVXVXWZX[Y\Z\[][^\_]_^`^a_a`b`badbdcedfdfehfigjhjikjlkmlompprqsqsrtsutvvxwyy{z|z}z}|}āƁȁ́ρсҁցہށtvx{ȶƂۂ삄x|͛⮃粃뷃kmb]뱃ϣ}{s΂Ϻ}xz恻ふ܁؁ՁҁЁρ΁ɁƁف}|~{}z|x{vytwruqtqtproqnpmoloknjmikikgigifhegdfcfbead`c`b_b^a]_]_\^[]Z]Y\X[X[XZWZWYVXUWTVTVSURT򀗛񀖚񀔘퀐쀏ꀎ耎瀍怌怋䀊〉 ߀ހ݀݀ۀـ׀ր׀~Ԁ}Ӏ}Ҁ{~р|~р{|πz|΀z|̀x{̀wyˀwyʀvxȀuwȀtvǀsvŀsuĀruÀrtqsqroqopnpnplnlnkljljkikhjhjhighfhfgefdfdececebdbdbcabab`a_`_`^`^_]_]_]^\^\][][\[\Z[Z[YZXZXZXYXYWYWXVWWXVWVWVWVWUVUVTUTUTUSUSTSTSTRSRSQRQRPRPRPQPQPQPQOPOPNONONONONNMNMNMNMMLMLMLMLMLMKLKLKLKLJKJKJKJKJKIJJKIJIJIJIJIJIJIJIJIIIIHIHIHIHIHHHIHIHHHHHHHHHHHIHIGHHIHHHIHHHHHHHIHIGHHIHIHIHIHIHIHIIJHIHIHIIJIJIJIJIJIJIJIJIJJJJJJJJKJKJKJKJKJKKLJKKKKLKKKLLLLLLMLMLLLMMMLMLMMNMMMMMNMNNNNNNNNNNONOOOOOOOOPPPPPPPPPPQPQPQQQQQPQPQQQQQRRQRRSRSRSRSSSSTSTSTTTTTTUTUTUUVUVUVUVUVVWVWVWVWWWWXWXWXXYXXXYXYYYYY[\\][\[\\]]^\]]^]^^_^_^_^_^____```_`_`_``a`aab`aabababbbababbcbcbcbcbccdcdcdcececedededededededeefefefefeffgegfgfgfgfgfgfggh€fgghghgi€gigigi€giÀgi€hi€hiÀhiÀhjĀhjĀhjŀijĀhjŀijŀijŀijŀikĀijŀikŀikŀjkƀikƀikƀjkŀjkƀjkƀjkƀjkƀjkƀjlƀjlǀkkǀjkǀjlǀjlǀjlƀklǀjlǀjlƀklǀjlǀjlǀjkǀjkǀjlǀjkƀjkƀjkƀjlǀkkǀjkŀjlŀklƀjkƀjkƀjkŀjkƀjkƀikƀjkŀjkŀjkĀjkĀjkĀikĀikĀijÀikĀijĀijÀijĀhj€hi€hi€hi€hiÀghgihigighghghghggfgfgfgfgfgefeffgefefdfdededededecddecdcdbdbcbcabababababab`aaaaa`a`a_`_`_`_`_`_`^_^_^_]^^^]^]^]^]^]^]^\]\][\\][\[\[\[\Z[Z[Z[ZZZZZZYZYZXYXYXXXXXXXXWXWWVWVWUVUVVVUUUUUUUUUUTTTTTTSTSSRSRSSSRRRRQRRRQQPQPQPQPQOPOPOPOPOPNONONNMNNNMNMNMMLMLMLMLMLMLMKLKLKLJKKKJKJKJKJJIJJJIJIJHIIIIIHIHIHIGHGHGHGHFGFGFGGGFGFGFGFFIJIJIJIJJKJKJKJLKLKLKLKMLMLMLMLMMNMNMNMNNONONOOPOPPQPQQRPQQRQRRSRSSTSTTUTUTUTUUVUVVWWXWXXYXYXYXYYZZ[Z[[\[\[\\]\]]^]_^__`_``aabacbdcddedeefegfhfhgihjijikjkkllnmnnonpoqprqsrtrusuuwuwvxÀwyŀwzŀy{ǀy{Ȁz|ɀ{~ˀ|̀}̀~΀πЀрӀӀր׀؀ـۀ݀ހހ‍‏䀏䀑瀒瀓耔逕ꀖ퀘񀝡􀝡􀟣TVTWUXVXWYWZX[Y\Z]Z]\^]_]`^`_b`b`cbdbecedfdgegfigjhjikilkmlomonqorqssusutvuwvxvyxzy{z||~}}~ÁŁƁɁɁˁ́Ёԁׁہ߁rtwz|Ǻʂ܂wś۬ȃlqwƑwlԪǢ½tՂłµ{~wz遼⁸ށ܁ׁՁԁӁЁˁȁǁŁÁ}}|{~y|wyuxtwsvruqtpsproqmpmolnkmilhkhkgigiehdgcecebead`c_b_a^`]_\^[^[][]Z\Y\X[XZWYVYUXUXTWSVSURU󀘜󀖚񀖚񀕙쀒쀒뀐ꀏ透耍怌個䀊〉 ހ݀܀ۀـ؀׀րրԀ~Ԁ}Ӏ}Ҁ|Ѐ{~Ѐz}΀z|΀y|̀xzˀwyʀwyɀvxȀuwǀuwƀtwŀruÀrt€qsqsproqnpnpmomnlmkmjljkikikhjhigifhfhfgefdececebdbdacacab`b`a_`_`^`^`]_]_\^\^\]\][\[\Z[Z[Z[YZXZYZXZXYWYWYWYWXVXVXVWUVUVUVTVTUTUSTSTRTRTRTRSRSQRQRQRQRPQPQPQPQOPOPOOOONOOPNNNOMNMNMNLMLMLMLMLMKLLMKLKLKLKLKLKLKLKLJKJKJKJKJKJKJKJKJKJKJJJJIJIJJKJJJJJJJJIJIJIJIJIJIJIJIJIJJKIJJKJKJKJKJKJKKLJKJKKLKLJKJKKKJKJKKKKLKLLLLLLMKLLLLLLMLMLMMMMMMMMNMNMNMNNNMNNONONONPOOOOOONOOPPQOPOPPPPQPQPRPQQQQRQRQRQRQRQRRSRSRSSTRSRSRSSTSTSTSTTUTUTUTUTUUVTVUWUVVVVWVWVWVWWXWXWXWXXYXYXYYYYYYZYZYZZ[Z[Z[[\[\[[^_^_^_^_^__`_`_`_``a`a`aaaaaababacbcbcbcbcbccdcdcddddddedeefdededeefefffegeffgfgfgfhfhfhghfhghghfhghhi€hi€hi€hiÀhjÀhjĀhjŀhjŀhjĀhjŀhjŀijƀijƀikŀikƀjkƀjlƀjkƀikŀjkƀjkƀjkƀjkǀjkȀklȀklǀklǀklȀklɀlmɀlmɀlmɀlmȀlmȀlmȀlmɀlmʀlnɀmnʀmnˀmnʀlnɀlmɀmnʀmnʀmnʀmnˀmnˀmnˀmnˀlnʀmnˀmnˀmnʀmoʀnoˀmnʀmnʀmnˀmnʀmnʀmnʀmnʀmnˀmnʀmnˀmoʀmnʀmnʀlnɀlnɀmnɀmnɀlnɀlnȀlnɀlmȀlnȀlmɀlnɀlmȀlmȀlmǀkmȀkmȀkmǀkmǀkmƀklǀklǀjlǀjlƀjlŀjlƀjlƀjkƀjkŀjkĀjkĀjkĀijĀijĀhiÀhi€higihi€gihhgighfhfhghfgfgfgfgfgefefefdfdedededecdcdcdcdcdbcbcbcabbcbbababab`b`b`a`a_a```a_`_`^_^__`^_^_^_]^]^]^]^\]\]\][\\][][\[\Z[Z[Z[Z[YZYYYYYYXZXYWXWXWXWXVWVWVWVVVVUVUVUVTUUUTUTUSTTTSSSSSSSSRSQRRRQRQRPQPQPQPQPQOPOPOOOOOONONOMNMNMNMNMMMMMMLMLMLMLMLMLMKLKLKLKKJKJJJKJKIJIJIJIJIJHIHIHIHIHHHIHIGHGHGHGGGHGGFGIJIJJKJKIJJKJKKLKLKLKMKLKMLMLMLNLNMNMNMNNONONOOPOPOPOPOPPQPQPQQRRSRSRSRSSTSTTUTUUVUVUVVWWXWXWXXYYZZZY[Z[Z[[\\]\]\^\^]_]__`_`_a`a`bacacbdbddedeeffgghgigihjijjkjlkmkmmomonpoqprqsqsrttvuwvxvx€wyÀxzĀxzŀz|ǀz}Ȁ{~ɀ|~ˀ}̀̀πЀрӀӀՀր؀ـڀ܀݀ހހ『【䀑倒怓耔뀕쀗쀗񀜠򀞢󀞢􀟤TVTWUXVXVYWZXZY[Z\Z][^\^]_^`^`_a`b`cadcecedfegfhfigihkiljmknlolonqorqsrtrutwuxvxwzwzz}z}{~|~āƁǁˁʁˁ΁сҁׁف߁rty|{~{ɻȂn{›ѥjΔޥce䵄Ρl춃寃Ù}tւÂʹz|tv聿䁺܁ځ؁ӁЁ́ʁɁƁف~}|z}x{vyuxtwtwrvruqtqtpsoqmoloknjmililgjgififhegdfbebdad`c`b_a^`]_]_\_\^Z]Z\Y\Y[WZWZWYVXUWTVTVSURT󀘛񀖚퀓쀒뀑逐逎瀎怍倌䀋 ဉဈހ܀܀ڀ؀؀׀րՀӀ~Ӏ}Ѐ}Ѐ|Ѐ{~π{~̀z}̀y{̀x{ʀwzɀvyǀvxǀuwƀtvŀsuÀrt€rtqsprprpqnqnpmomolnkmjljmilikhjhigifhfhegegefdfdecebdacacbcab`b`a`a_`^`^`^`]_]_]^\]\][\[\Z\Z[Y[Z[Y[XZXZXZWYXYWYWXWXWXVWUWUWUWTVTVTVTUSUSTSTSTRSRSRSRSRSQRQRPQPQPQPQOPOPOPOPOPOPNONONONONONOMNMNNOMNMNMNLMLMMNLMLMKLLMLMLMLMKLLMLMLMKLKLKLKLKLKLKLKLKLKLKLJKKLJKLMKLLMKLKLKLKLLMLMLMLMLMLMLMLMLMLMLMMMMMMMMNMNMNMNMNMNMNNNNNNONONONONOOPOPOOOOPPPPOQOQPQPQQRQRQRQRQRQRQRQRRRQSQSRSRSSTSTSTSUSTTUSTTUTUTUTUTUTVUVTVTVUVUWUWVWVXVWVWVWWXXYXYWXXYXYYZXYYZZ[YZY[Y[Z[[\[\[\[\[\\]\]\]\]]^]^]^`a`a`a`aabababbbbcbcbcbdcdcdcededfdededfefdfegfgfgfhfgghfhghghfhghghgigihjhihihj€hi€ijÀjkĀijÀikÀikÀjkĀjkĀjkĀikŀjkƀjlƀjlǀjlǀklǀklǀklȀkmȀkmɀkmʀlmʀkmɀlmʀlnɀlnȀlnɀlnʀlnʀlnɀmnɀmnɀlnʀlnˀmǹnòmnˀmoˀnpˀmoˀnònp̀np̀np̀op̀np̀op̀np̀op̀oq̀oq΀pq΀oq΀op̀oq΀pq΀pq΀pq΀oq΀pqπpq΀pqπpqπpqπpq΀pq΀pq΀pq΀pq΀pq΀pq΀pq΀pq΀pq΀op̀pq΀oq̀pq̀pq΀pq΀pq̀pq̀pq̀pq̀pq̀op̀oq̀op̀op̀op̀op̀op̀nònpˀnpˀnpˀnpˀnoˀnoˀnpˀmoʀmnʀmoʀmnɀmoɀlnɀmnʀlnɀlnɀkmǀlmǀkmǀklǀkmǀklƀjlŀjlƀjlƀklŀklŀjkĀikĀikĀijÀijÀijÀhi€hjhigigigigigifhfhfhfgegegegegefefdedededededededddecdbdbdbcbcbcbbbbabab`a`a`a`a`a_`_`^_^_^_^_]^]^^_]^]^\]\]\]\][][\[\Z[Z\Z\Y[YZYZYYXYXYXYWXWXWXWXVWWWVWVWVWUVUVUVUUTUSTTTTUSTSTSSSSSSRRQRQRQRPQPQPQPQPPPPPPOOOOOONONONONOMNNOMNMNMNMNMNLMLMLMLMLLKLKLKLKLJKJKJJIJJKIJIJIJIJIJHIHIHIHIHIHIHIHIHIGHGHGHIJIJIJJKIJJKJKJKJKJLKLKLKLLMKMLNLNMNMNMNNONONONONOOPOPPQPQPQPQQRQRQRQSRTSTSTRTTUSUTUTVUVUVVWVWWXXYXYXYXZYZY[Z\[\\]\^]^]^]_^_^`_a_a`aababacbdcecedeefegegfhgihjhjijjkjllmlnlnnpnpoqpspsrtrusvtvuwvywyÀwzÀy{ŀz|ƀ{}ǀ|~ɀ|ɀ}ʀ~΀~΀πрҀӀՀՀր؀ـۀ܀ހ߀‎‐【䀒怓瀔耕ꀖ뀗쀘퀚񀜠򀞢󀟣􀠤TWUXVYWYWZX[X[Y\Z][]\_]_]`^`_a`b`badcecedfegfhfigjhjikjlkmlnmompnqorpsrtsusvvxvyxzx{z|{~}~āƁɁɁʁˁρҁӁ؁܁߁rswx|~~ŻĂЂp|գbpڦepun񹄻wjӞyp҂ȸ~y{su偽ぷ݁ځՁԁρ́́ȁƁā~|z~y}w{vzvyvytxtwsvququorornqmploknjnilikgjgigiegegdfcebdac`c_b^a^a^a]`\_[^[]Y\Y\Y[WZWYVYVXUWTVSVSU􀙞򀙝򀗛퀓뀓뀑ꀐ耏怎怍倌「​ဉ߀ހ݀܀ڀـ؀׀׀րՀӀӀ~р~р}Ѐ|΀|΀{~̀z}ˀy{ɀxzɀxzɀwyƀvxŀvxŀtvÀtvÀsusurtqsproqnpnpmomoloknlnjljlikijhjhjhighfgfhegdfcecdbdbdacacabab`b_a`a_`^`]_]_]_\^]^\]\]\][\Z\[\Z\Z\Z[Y[XZYZXZXYXYWYVXWXVWVXVXUWVWUWTVTUTUSUSUSUSUSTSTRSRTRSRSQSQRQSQRQRPQPQPQPQOQPQOPOPOPOPOPOPOPNONONPNONONONONONONONONOMNNONOMNMNMNMNMNLMMNMNMNLMMNMNMNMNMNMNMNMNMNMNMNNONONONONONONONONONONOOPNOOONPOPOPOPOPPQPQOPPQPQPQPQQRQRQRQRQRQRRSRTRSSTSTSTSTSTSTSTSUSTTUTUTUTUTVTVUVUVUWUWUWUWUWUWVWVWVWVXWXWXWXWYXYXYYZYZXYXZYZYZY[Y[Y[Z[Z\[\Z[[\[\[\[\\]\]\]]^]^^_]^^_^__`_`_`_``abcbdbdcecececedfdeefefegegegfhfgfhgighgigigihihjhjhjijijikik€ikikÀikÀjlĀjkĀjlĀjlĀjlĀklŀklŀklƀlmǀkmƀkmƀlnƀlnǀlnǀlnǀmnȀmnȀmnɀlnʀmnʀmnˀmoˀmoˀnp̀nònp̀np̀np̀np΀op΀np΀oq΀op΀oq̀oq̀oq̀pq΀pq΀oq΀pqπpqπprЀpqπprЀprЀprЀprЀqrЀrsрqsЀqrЀqsрqtрqtрqsрrsрrsрrtрrtрrsрstрstрstрrtЀrtрrtҀrtҀstҀrtҀrtҀstҀstҀstрsuЀsuЀstҀstҀrtрstрstҀrtҀrtҀstҀrtрrtЀstрrtрstрrtрrtЀrsЀrsЀrsрrtрrsЀrsЀrsЀrsЀqrЀqsπqs΀qr΀pr΀pr΀qrπpr΀pq̀oq̀oq̀pq̀oq̀op̀op̀np̀noˀnpˀopʀmoɀmoɀmoɀmnȀlnɀlnɀmnɀlnȀlnȀlmǀkmǀkmƀkmƀklƀklƀjlŀklŀjkĀjkĀjkĀjkĀikÀijÀijÀij€ij€hjhjhigihigifhfhfhfgfgfgfgfgegegegdfdfdfdfdececececdcdbcbcbcabababababab`a`a`a_a_`_`_`^_^_^_]_]^]^\^\^\^\][\[\Z[Z[Z[Z[Z[Z[YZXZXZXYXYXXXXXXWXWXWXVWVWVWUWUVUUUVTUTUSUTTSTSTRSRSRSQRQRQRQRQQQQPQPQPQPQPPOPOPOONONONONONONOMNMNMNLMMNMMLMKLKLKLKLKLKLJKKLKLJKJKIJIJIJIJIJIJIJIJIJIJIJHIHIHIHIHIIJJKJKJKJKKLKLKLKLKLLMLMLMMNMNMNNONONONONOOPOPOQOQPQPQQRPRQRRSRSRSSTSUSUSUTVTVTVUWUWVWVWWXWXWYWYXYXZXZY[Y[Z\[][]\^]^]_^_^`_`_a`b`bacaccdcecedfeffhfgfigihihjikikjlklkmlnmonpnqoqprqsqtsutvtwuwvxwyx{Ày{Āy|ǀz}ƀ|~Ȁ|ɀ}ʀ̀̀΀πЀҀӀԀր؀ـڀۀ܀ހ‏တ】䀒倓瀔耖逖뀗쀙퀙񀞢󀞢􀠤UXVXWYWZX[Y[Z\Z][]\^\_]_^`_a_b`cacbdbecfdgehfhgihjhkiljmlnlompnqorprqsqtrutwuxwyxzy|{~|}~ÁƁɁˁˁ́Ёҁԁׁہ݁tvz}ȷǂςڂu諃g|Εެh͛aઅǘydܡ|lƜv҂Կ|}uw聿偸ہفցӁсρ́ɁǁƁŁÁ}|{~z}y}x|wzvztxtxswrvquqtpsornqmqlolokmililhkgjfiehdgcfcfadad`c_b_b_a^`]_\_[^Z]Z\Y[X[XZWYVYVXUWUWSVSU􀚟򀙝򀘜퀓뀓뀒ꀐ耐瀏倎倌䀌​߀߀ހ܀ڀــ؀րՀՀԀӀӀ~р~π}΀|΀{~̀{~ˀz}ʀy}ʀy|ȀxzǀwyƀvyŀvxĀuwĀuwÀsvrtqspsproroqnqnqnpmolnlnkmkljkikhjhjgifhfhegefdfcececdcdbdbdacacab`b`b_a_`^`^`]_]_]_]_]_\^\][][\[\Z\Z\Z[Y[Y[YZYZXZXZWYWYWYVXVXVWVWVWUWUVUVTVTVTVTVTVTVTVSUSUSURTSTRTRSRSQSQSQSQRRSQRQRQSQRQRPRPRPRPQPRPRPQPQPQPQOPOPPQOPOPOPOPOPOPNONONOOPOPOPOPOPNOOOOPOPOPOPOPPQPQOQOQPQPQPQPQPQPRQRQRPRPQPQPRPRPRQRQSQSQSQSQSRSRTRTRTRTSTSUSTSUSUSUSUTVTVTVTVTVUVUVUVUWVWVWVWVXVXVXWXWXWXWXWXWYXYXYXZXYXZXZYZYZY[Y[Z[Z[Z\Z[Z\[\[\[\[\\]\]\]\]\]\^]^^_^_^_^__`_`_``a`a`a`aabababacacbdegegegfhfhfhfhghgigigigihjhjhjhjikikjkikikjljl€jl€kl€kmÀkmÀkmÀlnĀkmŀlnƀlnƀmnǀlmƀlnǀmoȀmoǀmoȀnoɀmoɀnpˀmpɀnpˀopˀopˀoqˀoqˀoq̀oq̀pq̀pr̀oq̀pr΀pr΀pr΀ps΀psπpsЀqsрpsЀqsЀqsЀrtҀqsрrtҀrtҀrtҀrtҀrtҀsuӀsuӀruҀsuҀsuӀsuӀsuԀtvԀtvԀsuӀtvԀtvԀtvԀsuԀtvՀtvԀsvӀtwԀtvՀuwՀtwԀuwԀuwՀuwՀuwՀuwՀvxրuwՀuxՀuwՀvxրuwրuwրuwՀvwՀvwՀuwՀuxՀuxՀvxրuwՀvxրuxՀvxրuwՀuwՀuwՀuxԀuwӀuwԀuwԀuwԀuxԀtwԀuwՀuwՀuwԀuvԀuvԀuwӀtwӀtwӀtvӀsvӀsuӀtuҀtvрtvӀtuҀsuҀstрstрrtЀrtЀrtЀrsЀrtπqsπrsЀqsπpr΀ps̀pr̀pr̀pr̀oq̀oq̀oq̀op̀oqˀnqʀnqɀnoɀnpʀnoɀmoȀmoǀmoȀlnǀmoǀlnǀmnȀlmƀlmƀkmƀkmŀklŀklĀkmĀjlÀjl€jl€jk€ikikijikikijgihjhihigighgifhfgfhfgegegegdfdfdfcecdcecdcdcdbcbcbcacabababab`a`a`a_a_a_`^`^`^_^_]^\^\]\]\][][][][\[\Z\Z\Z[Y[Y[YZXZXZXZXZXYWYWXWXWXWXVWVWUWUVUVUVTUTUSUTUSTSTRSSTRSRSRSRSQRQRQRQRPQPQPQPQOPOPOPOPOPNONONONONNNNNNMNLMLMLMLMLMLMLMLMKLKLKLKLJKJKJKKLJKJKJKJKJKJKJKJKIJIKIJIJHIIJHIIJJKJKKLKLKLKLKLKLLMLMLMMNMNMNMONONONONPNPOPOPOQPQPQQRQRRSQSRSRTRTRTSTSTTUTUTVUVUWUWUWVXVXVXXYXYXYYZYZY[Y[Z[Z\[][]\]]^]_]_^`_a_a_a`babacbcbdcedfdfegfgfhgihjijikjljlkmkmlnlnmompnqoqorqsrtrusvtwuxvywz€x{Ày|Àz}ŀ{~ƀ|ǀ|Ȁ}ɀʀ̀̀̀πррԀՀՀ׀ـۀۀ݀݀ထ〓倓倔倖耖耘ꀘ뀚퀛󀟤󀟤󀠦UXVYWZX[X[Y\Z]Z]\_\_]`^`_a_b`c`cadbecedgegfhfigjhkiljmknlompnporospsqtsusvtwvywyx{y}z~|}}ÁāƁȁʁ́΁΁ҁց؁ہ݁vw|~ɵЂقr|ȟpɏ\tőa򤆄Xޗpﰅbďyfܮ~rւĂö|~vysuꁼ偷ہׁՁӁсρ́ʁƁƁā~}|{y}x|vzvzvytxswsvruqupsosnqmplpkojnimilhkgjfiehdgcfcfbead`c`c_b^a^a]_\^[^Z]Y\Y\X[X[WYVYVXUWUWTVTV􀛠􀙞򀙞񀘝퀖쀔쀓뀒递耑瀐倏䀍‍》ည߀ހ܀܀܀ۀــ׀ՀԀԀӀҀЀ~π~΀}̀|̀|̀{~ʀz}ɀy|Ȁy|ȀwzǀwyŀvyĀvxÀtvtvtvsururtqspspsoqnqnpnplnkmkmkmjlikikhjgjgifhfhfhegegdfdfcecebdbdbcac`c`b`b`b`a_a^`^`]_]_]_]_]_]^\][\[][\[\Z\Z[Y[XZXZXZXYXYWYWYWYWYWXWXWXWYVXWXVXUWUWUWUWTVTVTVTVSUTVSUSUTVTVSUSUSUSUSUSUSURTRTRTRTRSRSRSRSRSRSRSQSQSQSQSQRPRPQPRPRPRPRPRPRQRQRQRQRQRQRQSQRQSQSQSQSRSRSRSRTQSRSRTRSRTRTSTSTRTSTSTSUSUSUSUSUTUTVTUTVUVUVUWUVUWUWVWVWVXVWVXWXWXWXWXWXWYXYXYXYXZYZYZYZYZY[YZY[Y[Z\Z\Z\Z\Z\[\[\[][]\^\^\^\]\^\^]^\^]_^_^_^_^`^`_a_a_a_aabababacacacbccdcdcdcddecedededfgigihjhjhjhjhjjkikjljljljlkmkmkmlm€lmÀlnÀln€loÀmoĀmoƀmoƀmoƀmoƀnpǀnpȀnpȀoqɀoqɀprʀoqɀoqʀoqˀorˀprˀpsˀps̀qs΀qs΀qs̀qs΀qs΀rtЀqtπqtЀsuЀrtЀstрrtрruЀsvҀruҀsuҀsvӀsuҀtvԀsuԀtvԀtvԀtvԀtvԀtvՀtwՀtvՀuwՀuwՀuwրuwրuwրuwրuxրvxրvx׀vxրvx׀vxրvxրvx׀vx׀wy׀wz؀wy؀wy؀wy׀vy׀wz؀wz؀wz׀wz׀x{ـxzـxz؀x{ـxz؀xzـxz؀xzـx{ـxzـy{ـxzـy{ـxzـxzـy{ـx{؀xz؀x{׀xzـxzـxz؀x{؀x{؀xz؀y{ـxz؀xzـxzـxz׀xz׀wz׀xz؀xz؀wzրwz׀wyրwzրxzրwyրwyրvyրvyրvyՀvyԀvyՀvxԀuxӀuxԀuwԀuxԀuwӀtwԀtwӀtwҀtwҀtvҀsvрsvрsvрsuрsuЀruЀsuЀrtπqt̀rt΀rtπrt΀qs̀qs΀qs̀psˀpsˀorˀpqˀprˀoqʀopˀoq̀opʀnqɀnpɀnpȀnoȀmpǀmpǀmoǀmoǀlnŀkmĀkmĀlmŀkmĀkmÀkmÀkmÀkmÀjljkjkjkikikikhjikikhjgihjghgifhfhfhfhegegefegefefdfdedecdcdbdbcbcbcabab`b`a`a`a_`^`^`^`^`^_]^]^]^]^\^\^[]\]\][\[\Z\Z\Z\Y[Y[Y[YZYZXZWYXYXYWXWXVWVWVWUVTVUVTVTVSUSUSTSTSTSTSTRTRSRTQSQRQRQRQRQQPQPQPQPQPQOPOPOPOPOONONONOMNMNMNMNLNMNLMLMLMLMLMLMKLLMKLKLKLKLKLKLKLJLJLJLJKJKJKJKJKJKJKJKJKJKIJJKJJJKIJKLKLKLKMLMKLLMLMLMLMLMMNMNMNNONPNONPOPOPOPPQOQPRQSQRQSRSRSRSRTSTTUSUSUTVTVTVUWUWUWVXVXWXWYXZXZYZY[Y[Y[Y[Z\Z\[\[]\^\^]_^_^`^`_a_a`aacacbccdcedfdfegegfhgihihjhkiljlklkmlnmompmonpnqorprqtrususvtwuxvywzx{y|Àz}ŀ{~ƀ|ǀ}Ȁ~ɀʀˀ̀̀΀ЀрӀԀԀրր؀ڀ܀ހ߀߀–䀕怖瀗逘ꀙ뀚쀜퀜򀟤󀡦󀡦VYWZXZY[Y\Z][^[^\_\_]`^a_b_badadbecfdgdgehfigjhkilknknlompmporosptqtrutvtwuxwyw{x|{~{|~āŁȁʁʁˁ́ρӁՁځ܁߁prvw}̺ǂԂqz٩b|՚bǎi顆ądpVkᦅvĎsʟzނԂł{}wysu끿灼⁶݁ځׁՁӁс΁́ʁǁƁāÁ~}|{z}z}y|w{vzuyuytxsvrvptptosnqmploknjnimilhkgjfieidhcgcfbfaead`c_b^a]`]`\_[^[^Z]Y\Y[Y[WZWYVXVXUWUWTV􀜡󀛡񀚟퀗퀖뀕ꀔ递瀒們倏䀏‎߀߀ހހ܀܀ڀ؀؀׀րՀԀӀҀЀЀπ~̀~̀}̀|ˀz}ʀz}Ȁy}ƀx|ƀx{ŀwzĀvy€vxÀuxtwsvsurtqtqtpsoroqnqnpmpmolnknknjmjlikhkhkhjfififhegegdfdfcfcfcebebdadadad`c`c`b`b`b_a_a^a^`]_]_\_\^\^[^[]UWNPVXVYY[Y[YZYZYZYZYZYZXYXZWYWXVXVXVXVXVXUWUWUWUWUWUWUWUWUWUWUWVWUWTVTVTVTVSUTVTVTVSUTVSUSUSUSUSUSUSUSTRTRSRSRTRTRTRTRTSTSTSTSTSUSUSUSUSUSUTUSUTVTVTUSUTUTVTVTVTVUWTVUWTVUVUVUVUWUWUWVWVWVXVXVXVXWXWYWYXYXYXZXZXZYZXZXZYZYZY[Z[Z[Z\Z\Z\[][][][][]\^\^\^\^\^\^\^]_]_]_]_^_^`^`^`^`^`_a_b_a`b`b`b`cacacacbdbdbdbdcecedfdfdfegegdgegehfhfhfifhgijljljlkllmlnlnlnlnlo€mo€mo€npÀnpĀnqĀnqĀnqŀoqƀoqƀoqǀorɀorȀorȀpsɀprɀprɀqsˀqsˀqt̀qt̀rt̀qt̀su΀rùru΀ru΀su΀suπsuπsuЀsvЀsvрtvҀtwрtwҀtwҀuwӀuwӀuwӀuwԀuwӀuxրvxրuxԀvyրuxրvxրwy׀vy׀wy׀wy׀vy׀wz؀wy؀wz؀wz؀wz؀wz؀wz؀x{ـy{ـy{ـx{ڀy|ڀy|ڀy|ڀy|ـy|ـy|ڀy|ڀy|ڀy|ڀz}ۀz}ۀz}ۀz}ۀz~܀{~܀{~܀{~܀{}܀z}܀{}ۀ{}ۀz}܀{~܀{}ۀ{~܀{}ۀ{~܀{~܀{~܀|~܀|~݀|~܀{~܀{~܀{}܀|~܀{~܀{~ۀ{}܀{}ۀ{}ۀ|~܀{}ۀ{}ۀ{}ۀz}ۀ{~ۀz}ڀ{}ۀ{}ڀ{}ۀ{}ڀz|ڀ{}ۀ{}ۀ{}ۀz}ـz}ـy|؀z|ـz|؀z|ـy|ـy{؀x{ـy{؀y{׀x{׀x{׀x{րx{׀wzրwzրwyրwzՀvyԀvyԀuxӀvxԀuxԀuxӀuxӀtwҀtwрuwрtvрtvрtvЀsuЀsuЀsuπsu΀ru΀ru΀ru΀rt΀ru΀qt̀qt̀qt̀qs̀psˀqsˀpsˀprʀorʀoqɀorɀoqȀoqȀnpǀnpƀnpŀnpŀnpŀmoŀmoĀloÀmnÀln€ln€kmkmjlkmjljljljkjkikikikhjhjhjhjgigifigifhfhegegdfdfdfdedfcebdbdcdbdacacac`bab`b_a_a_a_`_`^`^`^_^_]_]^]^\^\]\][][\[\Z\Z[Y[Y[Y[Y[YZXZXYXYWYWYWXWYVXVWUWVWUVUVUVUVTVTVTVTVTUSTSURTRSRSRSRSRSQSQRQRQRPRPRPQOQOQOPOPOPNONPNPNONONONOMOMONOMNMNMNMNMNMNLMLMLMLMLMLMLMKMKMKMKMKLKLKLKLKLJLJLKLKLKLJKJKJKJKKLKLKLKLKMKMKMKMKLLMLMLMLMLMLMMNMNMOMNNONOOPOPOPOPOPPQPRPRPRQRQRQSRSRTRTRTSUSUSUTVTVTVUWUWUWUWVXVXWYWYXZXZXZY[Y[Y[[\[\[\[][]\]\^\^]_^`^`_a_a`b`b`bacbdbecedfdfegfhgigihjikiljlkmkmknlomonpoqorpspsqsqtrusvtwtwvxwywzx{y|€z}À{~Ā|ŀ|ƀ~Ȁ~ɀʀˀ̀΀΀ЀрӀӀՀ׀׀ـڀۀ݀߀߀ဓ〕倗怘瀘耙通뀛쀝퀞񀟤񀡦򀢧󀣨WZXZY[Y\Z][^\^\_]`^a_b_b`cadbecfcgdgehfigjhkhjiljmknlompnqorpsqtqurvtwuxuywzw{y|z}{~|}~āŁǁɁʁ́́΁ЁӁԁ؁ف܁ށqtvx¸Â̂؂vϣn͎oƈƉᑈkUކmiu֙i⛄w͜{sقȂĹ{}vy避䁻ၶށځفցԁсρ́ʁɁȁƁŁāÁ~}|{z~z~y}x|vzvyuxtxsvruqtpsnrnrmqloknjnjmhlgkgjfjeiehcgcgbfaead`c_c^b]a]`\_\_[^Z]Z\Y\X[WZWZVZVYVYUX󀝢󀜢񀚠쀘쀗뀕逕耓怓倒䀑【』ဏ߀݀܀܀ۀڀ؀؀׀ՀՀԀҀрЀ΀΀̀~̀}ˀ}ʀ|ɀ{~ǀ{~ƀz}ƀy|ŀx{Àx{Àwy€vx€uxtwtwsvruruqtpspsoqnqnpnpmpmolnknjmjlilikikhkhjgifififhegdgdgdgdgdfcfbebebdbdad`c`c`c`b_b_`_`_aY\_`~}||||bd}nq~lpZ\Z\Z\Z\Z\Z\Y[Y[XZXZXZXZXZWYWYXZWYWYWYWYWYWYXYXZWYXYWYWYWYVXVXVXVXVXVXVXVXVXVXUWUWUWUWUWUWTVTUTUTVTUTVTVTVTVTVUWUWUWUWUWUWVXUWUWVWVWVXVXVWVXVXVXVXVXVXVXWXVXVXWYWXXYWYXYXYXZXZXZY[Y[Y[Y[Y[Y\Z\Z\Z\[][][][]Z]\^\^\^\^\^]_]_]_]_]_]_]`]`^`^`]`^a^a^a_a_b_a`b`b`b`bacacacacbdadbdcebecedfdfdfegegegegegfhehfhgigjgihjhjhjhjikikikikmomompmp€npÀnq€nq€orĀorĀorŀorŀpsǀpsƀqsƀpsǀqtǀruȀruɀruɀruɀruʀruʀsuˀsv̀sv̀sv̀sv΀tw΀twπtwπuwЀuwЀuxрuxЀuxрvxҀuxӀvyҀvyӀwzӀvyӀwzԀwzՀwzրvzՀx{րw{Հx{׀x{׀wz׀x{؀x{ـy{؀y|ڀx|ڀx|ڀy}ۀy|ۀz}ڀz}ڀy}ڀz}ۀz}܀z}ۀz~܀z}ۀ{~܀{~݀{~܀{~܀{܀{݀|݀|݀|݀|܀|܀|݀}݀|ހ}݀}ހ}ހ}ހ~߀}߀}ހ~߀}߀~߀~߀}߀}߀~߀}߀~߀}߀~~߀~߀~~߀߀߀~߀ހ~ހ~߀~߀~߀~߀~߀ހހ~߀~ހ~݀ހ~݀~݀~ހ~ހ~ހ}݀}݀}݀}݀~݀}݀}݀}݀|݀|܀|ۀ|܀|ڀ{ڀ{ڀ{~ڀ{~ڀ{~ڀz}ڀz}ڀz}ـz}ـz|؀y}؀y|׀x|׀y{׀y{؀x{րxz׀x{րwzԀwzՀwyՀvyӀvyӀvyӀuxҀuxрuxрuwҀtwрtwЀtwЀtxЀtvЀtvπtvπsv΀sv΀sv΀rùrùrtˀqtˀqsʀrtˀpsʀpsʀpsʀprȀpsȀpsȀorǀorƀnqŀnqĀnqĀoqĀnpÀnpÀnpÀmoÀlo€lolnknknknknkmjljljljljlikikikhjhjhjgigigifhfhfhegegegdfdfcececebdbdacbcac`b`b`b`b`a`a^`^`^`^_]_^_]^]^\^\^\]\][\[][]Z\Z\Z[Y[Y[Y[XZYZXZXYWXWXWXWXWXVXVWVXVXVWUWUWUVTVTUSUTUSUSURTSTRTRTRTRSQSQSQRPRQRPQPQPQPQPQPQOQOPOQOPOPOPOPOPNPNONPNPNONONOMNMNMNMNMNMNMNMNMNLMLMMNLMLMLMLMLMLMLMKLKLLMLMLMLMLMKMKLKLKLKMKMKMKMKMLMKLLMLMLMLMLMLMLMLMLNLMfh^_`almNPNPNPOPOPPQPQPRQRQRQSQSQSRTRTSTSUSUSUSUTVTVTVUWVXVXVXVXWYWYWYXZXZY[Y[Y[Z\Z\[][][]\^\^\^\^]_]_^`^`^`_a_a`bacacbdcececedfdgehehgihjhkililkmkmlnmompmpnqorprpsqtrususvsvuwuyvywzx{y{z}{~€{À|Ā}ŀ~ǀȀɀʀˀ̀΀πЀҀӀՀր׀ـڀڀ܀݀߀߀န〖䀗候耚ꀛ逜ꀝ뀞퀟񀣨󀣩􀤩XZX[Y\Z\[][^\_\`^a_b`c`cadbecfcgdheieifjgkhliljmknlompnqospsqtrusvswtxuyvzw{x|z~{|}}ÁŁƁƁɁˁ́΁ЁӁՁցׁځ܁߁orruvx~ǸƂς܂wӠ^sۖa҉qkkPeT텍kYazTeݨtق̂ʾ{}vxsu䁻ၶ݁܁ځׁӁсρ́ˁʁɁȁƁŁÁ~~~|{~z~y|x|w{uytxtxrvruqtptosnqmqlpknjmimhlhlgkgjfieidhcgbfbead`d_c_b^b^a]a\`\_[^Z^Z]Y\X\X[W[WZVYUX󀟤󀞤󀝢񀝣퀚쀘ꀗ逖耕瀔倔倓䀒‐တ߀ހ݀݀ۀڀڀ؀ՀԀԀӀҀЀЀπ̀̀ˀˀ}ɀ|Ȁ{ǀ{~ǀ{~ƀz}ŀz}ŀy|Āx|€w{€wzvyuxtxsvsvsvquqtqtorornqmpmpmploknknkmjljmilikhkgjgjgjgifhfhfhehdgegdgcfcecebebdad_ast~}|||||||||ko~ruUW[^\^\^[][^[]Z\Z\Z\Z\Y[Y[Y[Y[Y[Y[Y[Z\Z]Z]Z]Y\Z\Y[XZXZY[XZY[Y[Y[XZXZXZXZXZXZXZWYWYWXVXVXUWVXVXVXVXVWVXWXVXVXVXWYWYWYWYWYXYXZXZXYXZXZXZXZXZXZXZXZY[XZY[Y[Y[Y[Z[Y[Y\Z\Z\Z\[][][][][^[]\^\^\_]_]_\_]`]`]`^`^a]`^a^a^a_a_a_a`b`b_b`b`c`c`cacacbdbdbdbdbdcebddfcecedfdfdfdgdgegegfhfhfhgifigigjhjhjikhkilikiljljmjmkmkmlnlnlnmoor€psĀpsŀpsŀqtŀqtƀqtǀruǀruǀruȀsuȀsvɀtvɀtwʀtẁtwʀtwˀtwˀuw΀uw΀ux̀uy΀vyЀvyЀvyЀvzЀwzЀvzрw{Ҁx{Ӏx{Ӏx|Ԁw{Ԁx{Ԁy|Հy|Հy|րy|Հy}րz}׀z}؀z}ـ{~ـz}ـz}ـ{~ڀ{~ڀ{~ڀ{~ۀ|܀|ۀ{~ۀ|܀|ހ|݀|݀|݀}ހ}ހ|ހ}߀}ހ}߀}߀}߀~߀}ހ}ހ߀~߀ကကကက က    、。、。。䀁、   、 、。。 〃。    。  。。 ခ  ဂခခ߀߀߀߀߀߀ހހހހ~܀~݀~݀~݀}݀}܀}ۀ}ۀ|ۀ|ڀ|ڀ|ۀ{ڀ{ڀ{~؀{ـ{~؀z~׀z~׀z}րz}րy}րx|Հx{Հy|Հx{Ԁx{Ԁx{ԀwzӀwzӀwzӀwzӀwzӀwzрvxрvxрuxЀuxЀuwπuw΀tw΀tw΀tẁtv̀sv̀sv̀suˀruʀruʀruȀqtȀqtǀqtǀqsǀqsƀpsƀpsƀpsƀpsŀpsŀorĀoqĀoqÀnqnpnpmomomololnlnlnknkmkmkmkmjljljlikhkhjgjgigifhfhfhehegegdfdfcfcfcebdbdbdbdacacac`b`b`b_b_a_`^`^`^`]`]_]_]^]^\][][][][]Z\Z\Z\Y[Y[Y[YZYZYZXYWYXYWYWYVXWYWXVWVWVWUWUWTVTVTVTVTVSUSUSTRTRTRTRSRSRSQRQRQSQSQRQRPRPRPRPRPRPRPQOQPQPQOQOPOPOPOQNPOPOPOPOPOPOPNPNPNONONOMOMNMNMNMNMNMNMNMNMNMNMNMNLMLNMNLNLMLMLMLMLMLMLNLNMNMNMNMNMOMONOMNMNlobf~~}}}}}^~a~~OQPRPRQRQSQSRTRTSTSUSUSUTVTVTVUWUWUWUWVXVXVXVXWYXZXZY[Y[Y[Y[Z\Z\Z\[][]\^\^]^]_]_^`^a^`_a_a_a`b`bacacbdbdcecedfegegehfigjhkhkiljlkmknknlompnpnqprprqsrtsusvsvuwuxvywzx{y|z|{~||À}Ā~ŀ~ŀƀǀʀˀ̀̀̀΀ЀрӀԀրրـۀ܀݀ހပ‖〖䀘怚瀛耜ꀝ뀟퀟򀤪􀦪WZX[Y\Z]Z][^\`\`]a^b^b`cadaebfcfdgdhfifjgkhlimjmknkolpnrorosqurvswtxuyvzw{x|y}z~{|}~āƁǁȁˁ́΁ЁӁց؁ځ݁ށpstvvy|~ǺƂ҂ނxۡ_tؗcЈpmoRhۇiZb}U~gݮx݂͂̿|wysu偺ၷ߁܁ځՁӁҁρ΁́́ˁʁȁƁŁāÁ~}|{z~y}x|w{vztxtxswrvptptosnrmqlplpkojnimhlgkfjfjeidhdhcgbfaeae`d`d_c^b]a]a\`\_[_Z^Z]Z]Y\Y\X[WZ󀡧򀟥񀞤퀝퀛ꀚꀙ逘瀗怖怕䀔〓–‑ހހ܀ۀۀـ؀؀րՀӀррЀЀπ΀̀̀ˀ~ʀ~ɀ}Ȁ|ǀ{ŀ{ƀ{~Āz~Àx|x|€w{wzvyuytxtxtwsvruqtqtpspsornrnqmpmplplokokojnimimililhkhkgjgjfifififiehehY[~}ƿ}}|||||||||||}}{}fh}~^a\^]`\_\_\_\^[][]Z][]Z\Z][][]Z]Z]Z\yy~~~[~}sq~dgNP[][][^[][][][]Z]Z]Z\Y[XZWYWYWYWYWYWYXYXZXZXZXZX[Y[Y[Y[Y\Z\Z\Z\Z\Z\Z\Z\Z]Z\Z\Z\Z\[]Z][]Z]Z][][]Z][][][^\_\_\_]`]_]_]`]`^a^`^a_a_a_a_a_b_b`c`c`b`c`cacacadacadadadbebebececfcfcfdgdfdfdgegegehehfhfhgigigigigihjhkhkililililjmjmjmknknknlololompmpmpnqnqnqnqor€or€svĀsvƀsvȀtwȀsvȀtwɀtwʀuxɀuxʀuxˀvỳvỳvz̀wz̀wyπw{πx{πx{΀x{πx{рx|рx|рy|Ҁy}Ҁy}Ҁy}Ԁz~Ԁz~Հz~րz~Հ{~ր{ր{׀|؀|؀|ـ|ـ|؀|ـ}ڀ}ڀ}ۀ~ۀ~ۀ~ۀ݀~܀ހ~ހ~ހހހ߀က  ခခ  、。。。䀂。。 〃倃䀄䀄䀃〄䀄䀄䀄倄倄倄怄怄怅怅怅倆怅怅倅倅倅怅怅怄怅倅䀅倅倅倅䀅倅倆倅倅倅倅倅䀆䀅䀅倅倅䀅〄々〄    䀄〄〄䀃。     ဂခ က݀݀߀߀ހ݀݀݀~ۀ~܀~ۀ}܀|ۀ}ۀ|ـ|؀{؀|؀{؀{~׀z~׀z~׀z}րz~׀y}րz}րy}րx|Ԁy|Ԁw{Ҁx{рx{Ҁx{Ҁw{рw{рwzрwzЀwyπvzπvyπvz΀uỳuxˀux̀twˀtwʀtwʀtvɀsvɀsvɀsuȀsvȀsvȀruǀruƀrtƀruƀqtĀqtĀps€ps€ps€orororornqnqmpnpmolnlolnlnlnkmjljljlikikikhjhjhjgigifhehfiehdgdgdfdfcfbecebdbdadacacac`b`b_b_a_a_a^`^a^`]`]_]_]_[^[^\^[^Z]Z]Z]Z\[]Z\Z[YZYZYZYZXZXYWXWXWXVXVWVWUWUWUWUWUWTVUVTUTUSUNOwxSTRTRTRTRTRTQSRTQSQSQSQSQSQSPRQSQRPRPRPRPRPRPRPRPQPQPQPQOQOQOQOQOQOPNPNPNPNPNPOPNPNONOMONONONONONOMN~wz~sv~~hjjlMONONONOy|]_df}||͜}}Y~{U~]~}}}jkPRQSRTRTSUSUSUTVSVTWUWVXVXUWVXVXWYWYXZXZXZY[Y[Z\Z\Z\[]Z\[][^\^\^]_]`]_^`^`_a_b_b`b`b`cadadbdcecedfdfdgegehfifigjhkhkjljmknknmpmpmpnqnqorpsqtrurusvuwvxvyvywzx{x|y}{~|}€}À~Āŀǀǀɀʀˀ̀̀΀ππрҀԀՀ׀׀ـۀ܀݀߀߀‗〘䀙怚瀜耝ꀟ쀠퀢񀥫󀦬􀨮􀨮WZX[Y\Y\Z][^[_\_]`]a_b_b`c`daebfcgdheheifjgjgkhljmkolplpmqnrosqtrurvtxuyvzw{x|y}y}{|}~~ÁÁŁƁǁɁˁ́ρсҁՁׁځہ߁ruvyz|˼ƂЂ܂vۢrϔ`ZےLJܑhS昉sntےk을{dஃyôп÷~z}vysv聾䁼⁸߁݁ځׁՁӁсρρ΁ˁʁȁǁŁāÁ~}|{zy}x|v{uztytxswrvquptptosnrmqlpkojnjmimhlgkfjfjeidhdhcgcfbeae`d`d_c^b^b]a\`\`\_[_Z^Z]Y\X[X[󀤪󀣩򀡨퀞쀜ꀜꀚ通耘怘倖〖―န߀݀ۀۀۀـ؀րՀՀҀҀррЀπ΀̀̀ˀʀȀǀ~ǀ~ƀ}ŀ|À|À{~Àz~y}x|x{vzvzvyuxtwtwrufi}{~dgnqmqnqmqlpehgkknjmimjmjmimjmfjWY~}}ס}ʙ}}||||||||||||Œ}}||||}dhNQUX\_]_[^~}y~}}}\^~il~}u}Չ}ۈ}ڊ}}׉}{}q}v}~fgnqTV]_\_\_X[TVX[X[Y[Y[Z\Z\Z\Z\Z]Z]Z][][^[^[^\^\^\^\_\^\_\_\_\_]_\_]`\_]_]_\_\_\_]`]`]`^`^`^`_a^a_b_b_b_b`c`c`bacadbdadbdbdbebebebfcfcfcfcfcgdgdgdgdgehdgeheheififjfigjfigjgjhkhkhlhlilililimjnjmjmjmknknlololpmpmpnqnqnrorospspspspspsqt€ruÀruĀruŀruŀvzȀvzɀvzʀvzʀx{̀x{̀x{̀x{̀x|̀y|̀y|΀y}πy}Ѐz}Ѐz~рz~р{~р{~Ҁ{~Ҁ|Ӏ{Ӏ|Հ|Հ|ր|ր}׀}؀}؀~ـ~ڀ~ڀ~ڀ~ڀ~ڀڀۀ܀݀܀݀ހ݀ހ߀߀߀ခဃ   〃  䀃䀃䀃䀃〃 〄〄々䀅䀄䀅倆瀆瀆瀆瀆怆瀇耇耇耇逇耆怇瀇瀇瀇耇耈逈逈逈耈逈耈耈耉耈逈选选选选耉瀉耉选耈耉耉耈瀉耈怈耈耈耈耈瀈瀈怈怈怈怈怈倈怇怈怇怈怈怇怇倇怇倆倆倆倆倅倅䀆倅々䀄〃  ဃဃ ߀߀߀߀ހހ݀ހ܀܀܀~ۀۀ~ڀ}ڀ}ڀ}ـ}ـ}ـ|׀|׀{ր|Հ{Ԁz~Ԁ{Ԁ{Ԁz~Ԁy}Ӏz~Ӏz}Ӏy|Ӏy}Ҁy}Ҁy|рy|Ѐx{πx{΀wz̀wz̀w{̀wz̀vyʀvyʀvỳuxʀuxɀtwȀuxȀtwǀtwǀswƀsvŀsvĀsvÀsvÀru€ru€qu€qu€qtpsqtososororornqnqnpmomololoknknjmkmjljlikikhkhkgjfififhfhegehegdfdfdgcfcfbebdbdbdadacac`b`c_b_a_a^`^a^a]`^`]`\_]_\_\^\^[^[][\Z\Z\Z\Y[XZXZXZXZWYVXRS}~{{xx{zzy[\]`~|Ř|~W}s}~A~Q~ؠ~SUSUSUSUSUSUSUSURTRTSURTRTRTRTRTRTRTRTQSQSRSQSQSQSPRPRPRPRPQPRPRPQPQPQPQPQPQOQPQPQNPNPx{XZ~~y|}||||||||}}ux~os~sv}|||}¹}˘}}W~|U~X~X~~W~Y~}}deRTTVTWUWUWUWVXVYVYWYWYWYXZXZX[Y\Z\Z][][][][^\_\_\_]`]`^`^a^a^a`b`bacacadadbebecfcfcfdgehehfhfigjgjhjhkikjljmkmlnmonpnqorprpspsqtrusvswtxuywzw{x{x|y|z~{||}€ÀĀŀǀȀɀʀˀ̀΀΀πрҀӀԀր׀؀ـڀ܀ހ߀ယ〚䀜怜瀞耟ꀡ쀢퀣򀦬󀨯󀩯Y\Y\Y]Z^[^\_\_]`]a_b_b_c`daebfcgcgeifjfjgkhlimimjnkolpnrosptqurvswtxuyvzw{y}z~{||~āŁǁɁʁ́́΁ρсӁՁցف܁݁svtxvyx{~ɸ͂؂u֢\nNjj͊eŅftZuՐ]~]ˋn̠zn΂Ǹz}vy恾ほ߁ށہׁׁԁҁсρ́ˁʁȁǁŁāÁÁ~}|{y~y}x|w|vzuytxswrvrvquososmrmqlqkokojnhmhlgkgkfjfjdhdhcgcgaeaeae`d_c^b^b]a]a\`[_[_Z^Z^Y]򀥫󀥫񀤪퀠쀟쀞뀝逜瀜瀚倘〘ဗހ݀ۀـ؀׀րՀԀӀҀррπ΀̀̀̀ʀʀɀȀȀƀĀĀ~Ā}€|{z~z~y}[^~~ÿ}}}pp}|^`}}}~~~or~mo}^`}}_b~WZ~cg~ej~hk~gi~}Ơ}Ԥ}}}Ϝ}՜}ј}}|||||||||||||}}|||||||||uz}~}|z{|rs|tv|wy||xy|}~|~|mn|mn|qq||||tt|uv||~||}||||}bb~}}vv}||}rr}XZ~QTZ\[^[^[^\_\_\_\_]_]`]_]`]`^`^a^a^a^a^a^a^a_a_a^a_b^a^a\_Z]^a^a_b_b_c`c`c`c`cadadaebebebebecfcgcgdgdgcgdhdhdhehfieieifjfifjfigjgjgjgjhkgkhlhlililimimjmjmjmknkoknlololplpmpmplpmqmqnrnrorospsptqu€qu€qu€rvÀrvĀsvĀswĀsvŀsvƀtwŀtxŀuyƀtxƀuxǀuyǀy}ˀz~̀z~̀z~΀z~΀{π{π{π{π{π}р|Ҁ}Ӏ}Ӏ}Ӏ~Ԁ~Հ~Հ~ր~րրր׀؀؀ڀڀڀ܀ۀ܀݀݀݀܀݀߀߀ހ߀߀င 〄   々々〆䀅倆倆倇倇倇怇怇怇怇怇怇怇怈瀈怉瀈瀉瀉耉逊选ꀊꀉ逊ꀊ逊逊逊ꀊ逊逋ꀋꀋꀋ뀋ꀌ뀌쀌뀌뀌뀌뀌쀌쀌쀌쀌뀌ꀍ뀍쀌뀌뀍쀌뀌뀌뀍뀍뀌ꀌꀌ逌ꀌꀌꀌꀋ而ꀋ逋逌耋耋逋耋逋ꀋꀋ뀋ꀋ逊耊耊耊耊耉耉耉耉瀉瀈瀈瀇倇倇倇倇倇倆䀆䀅々々々〃င  ငဃ߀߀߀ހ܀܀܀ۀۀڀـ؀~׀~ր}ր}ր}Հ}Հ|Ԁ|Հ}Հ}Ԁ|Ԁ|Ԁ|Ӏ{Ӏz~Ҁz~Ҁz~рz~Ѐy}΀z~΀y}̀x|̀y}̀y|̀x{ˀw{ˀw{ˀwzʀwzʀwzȀuyǀuyŀuyƀuxĀtxĀtxĀuxŀtwĀtwÀsvÀsv€rurururuqtruqtptpspsorornqnqnqmpmplplnlnkmkmjmililikikhjhkgjgjgifiehehdgdfcecfcecfbebebebdadacac`c`c`b_b^a^a^a]`]_]_\_XZSU~rtbd~~~}s~Z~Ї}j}o}_}d}h}]}V}Φ||{{{{|l}I~P~T~ŸW~T~U~ʣ~UWTVTVTVUWTVTVTVTVTVTVTVSVSVSVSUSUSUSUSUSUSUSURTRTRTRTRTRSRSQSQSQSQSQRQRQRPRPR~|||||||||||||||||||}}ϖ}Җ}˓}ؙ}И}}Œ}ɔ}}}}}}q~~WYWYWYXZXZY[Y[Y\Z\Z\[][^[^\_]`]_]`]`]`^a^a_b_b`c`cadadadbececebecfdfdgegehfifigjgjhkililjmjmkmlnlolpmqnqorpsqtqurvrvswtxuyvyvzw{x|y}z~{~{||~ÀĀŀŀǀɀɀˀ̀΀ЀрҀӀԀՀր׀؀ـۀ݀ހ߀‚〜䀝怟耟逡ꀣ뀣쀥񀩯򀩯[^[^\_\`\`]a^b_c_c`daeaebfcgcgeieifjhlhliminkokolpmqnrosptrvswtxtyvzw{x|z~{|}~ŁƁȁɁ́΁ρρЁҁӁցׁځ܁߁txw{y||˷ÂЂozͤ쳃cuבZrꛅeΉT^𝆺𝅥}V˅j󣄵}eૃzs؂̂ȹ´}z}tw⁾݁ہ؁ցԁӁсс΁ˁ́ɁǁƁƁāÁ~}|{zzy~y}x|v{uztysxrwqvpuotosnsmrlqkpkojoinhlgkfkfkejdhdhcgcgbfae`d`d_c_c_c^b\`\`[_[_Z^􀪰󀩯򀨮񀦭퀣쀢뀡造逞瀝倝倜〛‚’߀ހ݀ڀـ؀؀׀րԀӀӀҀЀπ΀̀̀ʀʀʀȀǀǀŀĀÀz~Z]~pq}|}}}}}}|||||||||||||||||||¼}ǔ}Ε}֗}ȓ}}||||||||||||||||||||||||||st|no|xz|kl|ef|jk|tv|qr|su||jk|gh|gh|ee|dd|]]|]^|ff|hh|oo|tu|rr|rs|tu|rs||}|||||||||||\^~qtX[]`]`^a^a^a^a^a^a_b`b`c`c`c`c`c`cac`cacac`d`c`c[^~з}s~~rtY\`caebecfcfcfcfdhdhdheheieififjfjfjfjgkgkgkgkhkhkhlililimimjmjmjmjnknkokokololplplpmpmqnqnqnrnrosososptosptptququrvrvsv€sw€twÀtxĀtxĀtxŀuyƀuyǀuyǀvzƀvzǀwzɀw{ɀw{ɀw{ɀy}ʀx|ʀx|ʀ|΀}π}Ѐ|π}р}Ѐ~р~р~Ӏ~ӀӀӀӀԀԀրրր؀ــــۀۀ܀܀܀݀ހ݀ހހހހ߀ဆဇဇ〇 〇〈䀈䀉䀉倉倉怊怉怉瀉瀉瀊瀊耋逋逋逋逋逋逌ꀌꀌꀌꀌ뀌逌뀌쀌쀌뀍쀍뀍쀎쀎쀍쀎퀎쀎퀎퀎쀏퀎쀏퀏퀏퀏퀐퀏쀏쀐쀐퀏쀏쀐퀏쀏쀏뀏뀐쀏쀏퀎쀎쀎쀎쀎쀏쀎뀍뀍뀍뀍뀍ꀍꀍꀌꀌꀊ逊逋逊瀊耊瀉瀉怈怈怈怈怇䀇䀇䀇〇〆 စင߀ހ݀݀ހ܀ۀۀڀـــ؀׀׀׀׀Հ׀׀~ՀՀ~Ԁ~Ӏ}Ҁ}Ҁ}р|Ѐ|Ѐ{π{΀{΀{΀z~΀z~̀y}ˀy}ˀy}ʀx|ʀx}Ȁw{ƀw{ƀw{ƀw{ƀw{ǀwzƀvzŀvyŀuyĀuyÀtxÀtw€tx€twÀtwsvsvsvruquqtqtqtpspsornrmqmqlpmplololoknkmjmjmililikhkikgjfifiehehegegdgdgdgcfbeceadadad`c^`~~~~~}}q}n}Y}ן|ɞ||||Z}V}|X}`}{T}͒|x|{{{{yy|Ü||||Ʈ|{|S}l}X~S~^~øW~c~^~¸^~VXVXVXVXVXVXUWVXUXUWUWUWTWUWUWUWTVUWTVTVUWTVSVSVSVSUSUSUSURURTRTRTRTQSPR~fg}|||||||||||||||||}z}}ʔ}̒}}}|Q~~R~|Q~R~{Q~ח}}}Ñ}}¶}ȿ}ޟ}W~Z~_~~ooY\Z\[][][^[^\_\_\_]`^a^a^a_b_b_b`cadadadbebecfcfdfdgegegehfifigigjgjgjhkiljmimknknlompmqnqnrosptqtrvsvtxtxuyvyvzw{x|y}z~z~||}~~€ÀĀŀƀȀɀ̀̀΀΀πрӀՀՀր׀ـڀ܀܀ހ߀ဝ〝〞䀠瀡耢連ꀦ쀧򀪱򀫲󀭳\`]a^a^b_c_c`dadaebfcgcgdheifjgkglhlimjnkolplqmrosotpurvtxtxuyv{w{x|z~{|}}ÁŁǁȁʁ́΁ЁсҁԁՁՁׁف܁߁w{y}{IJӼǂӂpzѥh{흄bsΎX^hYᕅy\螄̈p^ӣvoӂƂǸ~{~vyၾ߁܁ځׁցӁсρ́́ˁɁȁƁŁāÁ~}|{zy~x}w|v{tysxsxrwrwqvpuosnsmrmqlpjojninhmhlglgkfjeidhdhcgbfaeae`d_c^b^b]a]a\`\`􀭴􀬳󀫱򀩰񀩰쀥쀣ꀣ耢耠借䀞〝‛ယ߀ހۀڀڀـ؀؀ՀԀԀӀрЀπ΀΀ˀˀˀʀȀǀƀw{wy~}}||||}|ŕ}ϙ}}|||||||||||||||||||||||||||||||||||||||||||||x}||no|no|pq|pr|gh|su|tv|mo|op||qs|ij||no|pp|ff|ee|aa|]]|YY|^^|YY|YZ|ZZ|^^|dd|cc|__|jk|ij|wx|yz|y{|~||||||||~}~Z\QS^a_b_b`c`cadadadbebeaebebecfbebebe_bu~}ÿ}ɝ}}}}}}{z~cgeheheifjgkgkgkgkhkhkhlimimimininjnjnkokolokokokplplpmqmqmrmrnsnrosososototptpuptpuquqvrvrwswswswswtx€tx€tx€txÀuyĀuzÀv{Āw{ŀx|ƀw|ƀx}ǀx}ǀx}ɀy}ɀy~ɀy~ʀzʀzʀ{ʀ{̀{̀{̀|΀ЀЀҀҀӀӀӀԀՀՀՀրր׀׀׀؀ـڀڀ܀܀݀݀݀ހހ߀ހ߀߀߀ဈဉ 《《䀋個倊「怋個個怌怌怍耍耍耍逌耍耍逍逍逎ꀍ逎뀎뀎ꀎ쀏쀏뀏뀐쀐쀐쀐퀏񀔙񀔙񀔙񀓙񀔙񀔙񀓙񀔙񀔙񀔙퀒퀓퀒퀓퀒퀒퀒쀒쀓퀒퀒퀑퀒퀒퀒퀑쀑퀐쀐퀐쀐퀐쀐뀐뀏뀎뀍逎逎逍逍逍逍而而瀌瀋個個倊倉䀉《〉  ဈဈဇဇ߀߀ހ݀݀܀܀ۀڀڀڀــڀ؀؀؀׀րրԀӀԀҀр~р~Ѐр~Ѐ}π}π}π|΀|̀|̀{ˀzȀzȀz~ǀz~ǀy}ǀy}Ȁy}Ȁz~ǀx|ŀx|ƀw{Āw{Āw{Àw{Àw{Àvz€wzÀvy€uyuytxtxtwswrvruquptpsosororornrnqmpmplplolnknjnjnjmilhlhkgkfjfieiehehehdgdgdg]_UX}i}ȏ|||{|t|w|{{{{{w|{||ƿ|y|{{{{{{{z||||`}ӛ|||O}h}Y}\}P~l~I~`~}=Er~}?`XZWZXZWZWZWYWZWYWYWYWYWYWYVXVXVXVXVXVXVXUWUXUWTWTWTWSUSUil~~~rv~{}}|||||||||||||||||v}}œ}}~Q~S~R~}}ߖ}ѓ}Ǝ}͐}ɐ}}}ǒ}ʕ}}}}}}W~zT~Y~Z~\~~Y\[^\_]`]`^a^b_b_b`c`c`cadadadbebecfcfdgdgehfigjfigjhkhkhkhkjmjmjmjnkokolpmqnqnrososptqtqurvswuxvyvzvzx|x|y}z~z~{|}~€€ĀŀƀǀȀɀ̀΀΀ЀрҀӀՀՀ׀؀ڀ܀܀݀ހဟ†䀠倡怢瀣耥ꀦ뀦뀩񀭴򀯶􀰷]a]a^b_c`daeaebfbfcgdhdhdifjgkgkhliminkolplqnrnrototpuqvsxtyuzv{w|x}y~{{}~ÁāƁƁȁɁˁ́ρсӁӁցׁ؁ځہ݁vzx}{~ƴҼĂЂnszƜᧃ^rΎ񤄋`wĊȎޚl^٘~kbΟtn҂ǂȸ}x|uyⁿ߁܁ف؁ցԁҁЁρ́ˁɁȁȁǁƁÁ~~}{zy~x}w}w|u{uztysxrwqvpvpuotnsmrlqlqkpjojohmhmglfkejeidhchbgbfaf`e`d_c^c^c]b]b􀰸󀮶񀮵򀬴뀨逦耥瀣怢䀠〟”ဝހ܀ڀڀــ׀րՀԀҀҀрπ΀̀ɀ}in~ɯ}}›}ʜ}}|||||||}ü}||||||||||||||||||||||||||||||||||||||||||||u}}m}}~|ij|cd|_`|lm|ij|de|uw|qs|no|hi||oq|ef|ln|ef|ij|dd|fg|^_|XX|UU|XX|ZZ|_`|WW|ST|XY|YY|[[|cd|`a|hh|cd|de|jk|hi|hi|op|no|rt|tv|wy|~|}~||||SU~~~~Y\_bcfcgdhdgdhdhehdhcg~ާ}Ȟ}}}}٠}}}}}}}tw~fjgkhlhlimiminjnjnkokokokplplqmqmqmqmqmrnrnrnsosototptptpuquqvrvrvrvrvswswswsxsxtxuyuz€uzuzvzÀv{ĀvzÀw{Āw{Āx|ŀw}Āx}ƀy~ƀy~ǀzǀy~ǀzɀ{ɀ{ʀ|ˀ|ˀ|ˀ}ʀ}ˀ}ˀ~̀~πЀπЀрҀҀҀӀԀԀՀրրր؀ـــڀڀۀ܀܀݀݀ހ݀ހ߀ဌဌ‌䀌」倍䀎倎怎怎怏瀎怏瀐耐耐逑뀐ꀑ뀑뀑쀑뀐뀑뀑뀒쀑쀒퀒퀒퀓퀓򀖜򀖛򀕜򀖜򀖝񀗝񀖜򀖜򀖝񀗝򀘞򀖜򀗞򀖜򀗝򀗝򀘞򀗝򀗝򀘞􀗝򀘞򀖜򀖜򀗝򀗝򀘝􀗝񀗝񀗝񀗜񀗝񀗝퀔퀓퀒쀑쀑뀑뀒쀐ꀑ뀐ꀐ透耏耎耎耎耎瀍倍怌倍倌個》》》ဋညဉ߀ހ܀܀܀ۀڀڀڀڀۀۀڀــــ؀׀րՀՀՀӀӀҀрЀπ΀Ѐ~ππ~̀~ˀ}ʀ}ɀ}ʀ|ʀ|ɀ}ɀ|ɀ|Ȁ|Ȁ{ǀz~ǀ{ǀz~ŀy~Āy}Āx}Āx|Āx|Āx|Àw{€w{€v{v{uyuytxtxswrvquqtqtquptptososnrmqlpmpmplokolojnjnimbeeg~~~s~ŭ}||wx|op|~|ϕ|Ƈ||dž|v||ƾ|{y|w|y|~|ý|x||{{{pq|{{{{{{{{{z||||\}Y}d}r}ƾa~Hh}j}R~C~=~B~OP]_Y[Y[Y\Y[X[Y[X[X[X[X[X[XZXZX[WZWZWYXZVYVYUXUXUW]`sv~[\}|||||||||||||||||||y}}ƍ}ޖ}}ޔ}P~~O~R~T~R~T~V~U~R~O~}O~}ґ}Ź}}˒}}}}}}}ݝ}}˘}Ö}•}}}}~VY_b_c`c`cadbebebebfbfcgdgdheheifjfjgkhkhlhlilimjnkokolplplpnrmqnrnrosququrvsvswswtxuyvzx|x|y}y~z~{{}}~ÀĀĀŀŀǀǀʀʀ̀πрҀҀԀՀ׀ــۀ܀݀ހဠအ‣䀣急耦耧逨뀩쀪퀬񀲹􀳺^b_c`daeaecgcgcgdheifjfjgkhlimjnkokplqnrnrospuqurvrwsxtxuzv{w|y}z{|}~Áāāǁȁʁˁ́ρρсӁԁՁفہ܁ށvzx|z~}ǵӼɂтnv}ӟiz͍pzpۖl^譃Ԡ–~sقЂƂͻĴ}{x|ށف؁؁ցԁҁсρ́ˁɁʁȁƁǁŁÁÁ~}|{zy~x}x}w|u{tzsysxrwrwpupuotnsnsmrlqkpjoininhmglfkejeidichchbgbfaf`d`d_c倬逮퀬뀫ꀩ逦怦倣〢‡ဠ߀ۀڀڀـ؀׀׀ՀԀӀҀрȀej~}}}_`}kk}}}}}}}|||||~}|}}}||||||||||||||y{||||||||||||||~||||||||||||||t}y}|||no|jk|_`|WX|ab|bc|_`|mn|ln|\^|^`|km|jl|hi|km|]]|hi|gh|de|bc|de|^^|^^|XX|]^|Z[|\\|QQ|UU|]]|^^|]]|cd|_`|\]|ef|cd|qr|jk|oo|np|tu|rs|qr|uv|rs||~|||||||wx}~X\dgeifjbf~}}Ľ}ƿ}}}Õ}}{V~}}\~]~b~թ}sr}~RVimjojokpkplplqmrnsnsnsnsotosototpupupuqvpurvrvrwrwsxsxsxtxtytytyuzuzuzv{v|v|v|w|w|€x}€x~€y~Ày~ĀyÀzĀzŀ{ŀ{ŀ{ŀ|ǀ|ǀ}Ȁ~ɀ~ʀ~ˀˀ̀̀̀̀΀πππЀррӀԀԀԀԀրՀր׀׀؀؀ڀـڀڀۀ܀݀݀݀ހހ߀ဎဎဎဎ‎『‏䀎䀏倐倐怐怑怒瀒耑瀒耓逓逓逓ꀔꀔꀔꀔ뀔뀖쀕쀕퀕퀕퀕퀕퀖퀖񀘟񀙠򀙠򀘞򀙠򀙠򀙠򀚠򀚠򀙠򀚠򀚢󀙠򀚠򀚠򀛡򀚠򀚠􀚠􀛢󀛢􀜢􀛡􀜢􀜢􀛢󀜢􀛡􀜢􀜢􀜢􀜢􀜢􀜢􀛡򀜢􀜢􀜢􀛢󀛡򀛡򀛡򀛢󀚡񀚡񀚡񀚡񀚡񀚡񀚠񀚠񀚠񀚠񀙠񀙠񀘞퀕쀔뀔뀔ꀓꀒ递耒递瀑瀑耑瀐怐倐䀐䀏』䀎䀎』‎」‍‌ހ݀ހ݀܀ۀۀۀ܀ۀ܀܀ۀۀۀۀۀ؀׀׀׀րՀӀӀӀҀррЀЀπ΀΀̀ˀˀʀʀʀʀ~ʀ~Ȁ~ɀ~ɀ}ǀ|ŀ|ŀ|Ā{Ā{Ā{ĀzÀzÀy~€y}y}x}w|v{vzuztytytxswswswrvququjn~~dfmqmqlplpbeim~|_}_}||tu|st|{xy||||ԡ|||[}a}e}|||}||||||~||{{{{{{{~||||||Ȱ|Žv}N~V~}P~}x~Yj}U}t}X~TOT~g~Hz^`Z][^Z]Z]Z]Z]Z]Z]Y]Y]Y]Y]Y\Y\Y\X[X[XZWZehY[~||||||||||||||||||}}}}}Ō}͎}}xL~R~ݒ}}{N~T~~O~yL~}}Q~R~P~T~U~R~ْ}Ȏ}Ō}Α}Î}}}}}ݛ}|S~}S~ܝ}̙}}ſ}Ĝ}ף}}qradbebecfcfdgdgdheieifjgkgkhlhlimjnjnjnkolpmqmqnrnrnrosptquququrvswtxtxuyvzw{x|x}y}z~{{|}}~€ÀĀŀƀƀƀȀɀx|tx|`dwzЀ׀؀ڀ܀ۀ݀߀ဣ․〦倧怨耩逪逫ꀬ쀮򀳻󀵽􀶾`daebfcgcgdheiejfkglhliminjnkolpmqnrnsotpuqvrwrxtytzu{v{w|y}y~z|}~ÁŁƁǁʁʁˁ́Ёсҁԁׁ؁فځ݁߁y}|ɵֺƂ͂lqy˚᥃l}衄hx~gݞ}qaꩃҙ|smтǂپͶŲ|z~w{ށہځ؁ցԁӁҁρρ́́ɁȁɁɁǁŁŁ~}|{zy~y~w|v{v{uzsxsxrwrwpupuotnsmrlqkqkpjpiohnglglfkejejdidichbgafae݀~uu}ww}}t{~`frxҀ퀱퀰뀮逭耫怨〧…ဥ߀ހۀ€|{|ǀ̀Ҁрŀae~rs}}}|||}}}~}}}|||||||}}||||||||||||||wx|st|~||||}~|||{|||}||||zz|{||}~||}||||z{|~|||||y}ù}̸|hi|aa|^_|\]|jk|lm|cc|WX|\]|bc|]^|YZ|WW|UV|{XZ|]]|YZ|VW|{[[|bc|`a|cc|__|``|``|]]|XX|[[|XX|{TT|\\|``|]]|bb|VV|RR|\\|YY|gh|jk|rs|kl|kl|rs|jk|rs|kl|vw|tu|xy|xy|yy|{||~||||||}~Z]}}}||}ݢ}}|}}}ϛ}}\~a~e~b~Y~}ll}~hlmqmrnsnsototoupuquqvrwrwrxrxsxsxsxtytyuztyu{uzu{u{u{v|v{w}x~x~x~yz{z€z€z€{À{À|€|Ā|À|Ā}ŀ}ŀ~ŀ~ŀǀɀɀʀʀˀ̀ˀ̀̀΀΀ππЀррҀՀԀԀՀՀՀրրր؀؀؀ڀۀۀۀۀ݀ހ݀݀߀߀߀ထထဒဒဒ‒〓倓倔倔倔怕耕瀕耕逖逖逖ꀖꀗ뀗쀗쀘쀘뀗뀘쀙񀛣񀚢񀜣򀜣󀜣󀜣󀜣󀜣󀝤󀝤󀞥󀝤󀝤󀞥󀞥󀞥󀞥󀠧󀟦󀟦󀟦󀟦󀞥񀞥񀞥򀞤󀞥򀞥򀞥򀞥򀞤󀞤󀞤󀝥򀝤򀝤񀜣򀜣򀜣򀜣򀜢񀛢񀛢򀛢񀜢񀛡퀘퀘퀗퀗쀖뀖逖耕途瀔瀓倔怓倓䀓䀓䀒䀑䀑】‑တတ߀߀ހ݀ۀ܀ۀڀڀڀڀۀڀڀڀڀۀڀڀ؀؀؀րրՀԀՀԀӀҀҀԀЀЀπ΀̀̀̀ˀˀˀʀɀȀȀǀǀǀ~ƀ~ŀ}Ā~Ā}À}Ā|À|€|{z~y~x}x|x|w{v{uzuytxtxrv~޲~˩~»~˨~Ũ~~wy~e}|||~|xz|xz|uw||ʦ|a}|V}[}Ü||Ý|ޜ|ʘ||{mo|{{{{{{{{{{{{np|{{|||{{{|||i}J~T~K~ſp}J~`~\i}E~W~O~d~S\QF@|X[\`[_\_\_[_[_[_[_[_[^Z^[^Z^Y]Y]Y\UWiluv}|||||||||||t}}ɐ}}ٔ}|N~yM~ˎ}ļ}ź}ɇ}ߎ}}ݓ}}Ê}}}|}|}}Ƹ}ݑ}yK~}zL~zL~zM~֎}}|N~P~P~}ڒ}}Š}¸}ˑ}ǽ}{}w}|}}}T~U~}}Ř}¼}}ƙ}ǝ}͠}̤}~rtxzcggkilimjnjnkokokolqmqnrnrnrosospuquqvrvrwsxsxtyuzvzv{w|x|y~z{{}~~€ĀŀŀƀƀǀȀy{}jl}oo}}}}}}~π܀߀߀„׀Հ倫怭耭ꀧ‑ŀрံ򀷿󀩱―ǀ怽aebfcgchdheifjglglhminjojokplqmrmrototpuqvrwsxtyuzv{w|w}x}y~{|}~~ÁāŁŁȁɁʁˁ́΁ЁҁӁՁցفځށ{~ȳշۼĂ͂؂pu~͚ঃasΒ_u|vÔscԝ|xrl΂łܻβí~|y}߁܁ف؁ցՁӁӁсρ́́ʁɁȁǁƁŁÁ~}|{{yx~x}w|w|u{tzsyrxrwqvpvpuotntlrlrkqkpjoimhmhmglgkfk耑qv~vw}pq}op}lm}lm}jk}fg}de}bc}bc}jj}t{~w~chsxrwhm~}np}mo}jl}ik}ln}qs}~~}}fg}yx}}||||ut}}}}}}||||||||}~}||||||||||}~|{||z{|}~|vw|rs|~|||{||vw||}|xy|vw|xy|~||~~|zz|qq|pp|ss|||||||xy|ux|no|xy|||y}p}|bb|ab|__|YZ|YY|aa|ff|ef|cc|WX|XY|_`|\]|[[|WX|{{{{WX|{{^_|\]|{YZ|\]|^_|^^|]^|ZZ|XX|{{UV|^^|]]|[[|{VV|]^|__|ff|cc|ll|ee|jk|op|hj|lm|jj|z{|mn|oo|qr|rr|xy|}~|}~|~~|||||||||||||Ȗ}}||՚}Œ}ܟ}~V~]~d~c~]~_~Y~Ž}}ij}}z~~ae^bpuqvqwqxrxsxsytztztztztzuzu{v|v{w}v|v|x~x}x~y~x~zzzz{{{|}}~~}~€~€~€ÀĀĀŀƀǀǀǀʀˀʀˀ̀̀΀πЀрЀрҀҀӀӀӀԀӀՀՀր׀؀ــ؀ڀۀڀۀۀ݀܀݀܀݀܀݀߀߀နဖ‖〖䀖䀖倖倖怘耘逘耘耙逘ꀙ뀙ꀚꀚ쀛퀛쀛뀛쀛뀜퀛퀜񀟧񀟧񀟧񀠨􀞧󀟧􀠨􀠩􀣫􀣫􀣫􀣫􀤬􀤬􀣪􀢪􀢪􀢪􀡩󀡩󀢪􀡩󀢪􀢩󀡩󀡨􀢪􀡨􀢪􀡨􀢪􀡨􀡨􀠨􀟧􀠨򀠧򀟦򀟦򀟦񀟦򀟦񀞥퀛퀚쀚뀙ꀙ這這逘耘耗瀗怖倖怕倕䀖䀕䀕倔〔—ဓ߀ހӀՀ݀ۀڀ؀ŀtwknor}}Ӏڀڀڀۀۀڀۀڀڀ؀׀׀׀ՀՀӀӀҀҀҀҀрЀπ΀̀̀ˀˀˀʀɀǀƀǀŀŀĀÀÀÀÀÀ€~~}{{zzy~x}w|v{uz_cmrcc~^ǡ~~~~~ü~»~}}}߉}q}]}]}||~||~|||™||}\}üy}ž|}|||{{{{{{{{{{{{{z{|v|{||ю||{վ{{Ǿ{{{Ƴ{|~A~U~|}f}F~}:CS~G~D}?~Y}j~Q@^bZu]`]`]`]`\`\`\_Z]\`\_[_[^Z]be}|||n}u}}}ޑ}P~Y~[~X~W~~L~M~O~Q~[~T~U~Q~xJ~{L~}}֋}ӊ}~}r}o}||j}ƒ}؍}Ԍ}ъ}}}}ސ}zL~}}zM~}֐}Ԑ}Ⱦ}}}y}||||}ח}Z~|R~ٙ}֙}}}•}}}}}}}ƿ}}}{_~~jnjokomqlpnrnrosptptqurvrvrwrwtxtytyuzv{w|x|x}y~zz{|}~~ĀĀĀŀƀŀ}|||}}}||||||}~twmq|tu}st}~~}mn}oq}np}st}}{}uv}}w|nsǀdifjgkgliminjojolplqmrnsotpuqvqvqwrxkpjpw|x}y~z|{}~ÁāŁƁȁɁɁɁ́΁ЁҁӁԁՁցف܁݁y~|~˰յٸʂ҂otzϛf{͝eaңdৃȕ|vqnk˂łܽѳë~|䁫ր}ru|ɀuz~퀤nrrwŀ~}|{zyy~x~w|v|u{uztysyrwpvpvpunsjo~v|cf~|}}uv}pq}no}no}mo}jk}kk}hi}ij}fg}`a}`a}`a}ab}ce}gj}np}lo}jl}jl}gj}eh}cf}ce}df}bd}ac}ab}||||||||||||||||||}}}}|||||w}|}zQ~||||||||||||{||vw|z{|pq|mm|rs|wx|xy|st|st||}|{||z{|}~|||~|{z|yx|rr|rs|wx|yy|ww|kk||y}v}||mn|`a|bc|dd|ee|aa|^_|bb|bc|ef|dd|ab|YY|WW|[[|ZZ|Z[|{{{{XY|YZ|ZZ|^]|__|cc|aa|[[|[[|\\|cd|dd|cc|YY|{UV|[[|[[|_`|\]|WX|[\|]]|cd|ee|aa|dd|gg|oo|cc|bc|hi|jk|kk|nn|mm|pp|pq|qr|pp|rr|rs|xy|xx|ww|||||||||||||}}|||||||||~}ڝ}Z~^~Z~^~b~^~zV~}V~Y~{Y~ž}}}_a~mssysyt{u{u{u{u{v|v|w}x~w}x~yyyyz{{{||}}}}~}~€ÀÀŀƀŀǀȀɀʀɀˀ̀΀΀πЀрррҀҀԀԀԀӀԀԀԀր׀րʀ{~twornqjm|~lodg׀’〙䀙䀚倚倛怛怜瀜瀜瀜耝ꀝꀞ뀞쀞뀟쀟쀟퀟쀠퀠퀟񀢪񀢫񀢬񀢫񀢫񀢬򀢬򀣫󀣬􀣬􀣬􀤭􀤭􀤭􀦯󀦮󀦮򀦮󀦮򀥮󀥮򀥮󀥭􀥮󀥭򀦭󀥭􀥭􀥭󀤬󀥬􀥬􀤫󀤬󀤬󀣫񀢪򀢪󀡩񀡩񀠨퀟퀠쀞ꀝꀝ逜耜逜耛瀛瀚怙倘〚候䀙倘〘〗ဖ܀quj_}Z}R|SWZdtxـ׀ڀڀڀۀڀڀـ׀׀؀׀րՀԀԀҀҀҀрЀπ΀΀̀̀̀ˀʀʀȀƀƀƀƀŀĀĀÀ€€À~}|{zy~qvUX^b}}}Ӧ~Ž~~~~}~}ڎ}u}s}x}~}}}~}m}|||b}^}|||{{{{{{ձ{p|{|Lj|Ɋ||{{{{||\}{|O}O}{|s|k|{l|j|w|o|M}|}ɑ|X}N~`~Ah~t}I~F~P~[~H?l~ļWDǿY^a\_\_~}~~~~aa~}qr}||s}z}}φ}}K~Q~U~T~W~d~Ƹb~S~P~wH~ߊ}}}}xG~}׉}ʂ}}}L~ً}}yI~҉}||||h}u}{}ø|}̄}ǃ}~}}}א}}Њ}ܓ}֍}}ݖ}ʌ}ù}}w}|||||||}T~R~Д}}Ж}»}ڞ}}}|{}ww}}}}}}}̪}}~|[_nrhljnnrqvqvrxrwnssyv{v{w{qwmrswvzuynsgk[_[`sxz~}z{sx~fg}|||}}||||||||||||||||||||||fh}gh}fg}fh}gi}gh}gi}ij}jl}kl}ln}pq}fi~x~fkjokplqmrmrnsotpuqvqwqwrxŀns~vy}wz}}v{~ÁāŁǁɁʁˁ́ρсҁԁցׁفځ݁|©ˬӱصགł͂lptz|Ӟcq‘xeߣқÎ~wqljς˂łٺӲǬ~{~恹Áy}{~~~~~~~~~~~~~~~~~~~~“~~~~~~~~~~~qv퀜ـ~}|{zzyx~w}v|u{tzrxڀrv~}}}}z{}tv}mo}jl}jl}ik}ij}fg}fh}fg}ab}de}cd}ef}|||||df}fh}eg}ef}df}|||||||||||||||||||||||||||}¸}}|||||x}|ɿ}}|||||||||||||||{{|uv|op||}|~||}||}||}~||||||||}}|{z|vv|wx||o}Ł}|I~߉}|rr|jk|ee|cc|cc|bb|cd|dd|de|de|cc|ef|eg|ik|df|WX|{[\|]]|{{{YZ|\]|Z[|YZ|``|ab|cd|bb|bc|ab|[\|``|cd|ij|jj|Z[|XY|[\|]^|`a|^^|[\|Z[|[[|_`|de|fg|dd|ee|ff|hi|ee|kl|kk|mm|ll|no|jk|jk|pq|qq|ii|mm|oo|st|vw|vv|vv|vw|tt|vv|ww|uv|{{|{||||uv|ss||||||||}}}}}}ܥ}e~d~c~}lqpvu|v}w}x~xxyyzzzz{z{||}}~~~~€ÀĀŀǀȀɀˀˀ̀̀̀΀πЀЀрӀҀӀӀӀӀӀӀӀ~z\{YzWyV~}Y}X~yV\[~~~}~~ab~~^cǀရπހ倞借借借瀠耠造耡ꀢ뀢뀢뀣뀤퀣쀤쀤퀣퀤񀧰򀧰󀨱󀧱򀨱󀧱򀨲򀨱􀨲􀩲􀩲􀪲򀪲򀪳󀩲􀪲􀩲򀪳􀩱󀩲󀩲󀪳􀪲􀨰􀨰󀨰􀧯󀨰󀧯򀧯󀦮򀦮񀥭񀥭퀟「ˀ̀〠耟瀟瀞瀞怞思䀝倝怛〛䀛〗׀ƀx}mo~~zN~~{OTURTU~ޖ~|R~XgvyҀ؀؀ـ؀׀׀րրՀԀՀԀӀӀҀррπ̀΀̀̀̀ˀʀɀǀƀƀƀƀŀÀÀ€€}}z~}}}}~ƽ~»~|~}k}p}c}s}v}u}т}}}h}u}j}k}x}̅}i}c}|x|{{{x|q|r|B}W}O}`}3~T}B}{{{|?}Q}V}h|I}I}G}L}P}z|u|i|L}L}L}J}W}|e}}<~L~DȿY~L~V}P~}8ȼT~MOAbR|;E]~ʽ~}pn}}}¹}ϖ}}S~V~ۘ}|q}||o}o}l}~}ֈ}|J~N~Q~S~T~\~[~f~^~\~W~Q~R~S~U~O~yG~΅}ߋ}O~|I~у}ֆ}t}||l}t}r}y}~}}΃}z}}}Շ}}J~ɇ}~}}}ڏ}{N~Ր}Š}}}||||||||||}}}}}}|||||||||||||||||ac}ik}eg}yz}pr}}}}sv}gi}}eiim~mo}ef}gh}hj}fg}fg}de}||de}qs}}aeuzhltz_ceh~eg}|||||||||||||||}}||||||||||||||||||||||||||gh}gh}fg}|fh}ik}kl}jl}jk}lm}mo}tv}~ytzinkq~~uv}mo}ln}km}jl}np}pr}rt}rt}y{}|~u{āŁƁǁɁʁ́́ρсӁҁՁفځ܁ށ~¦ʫӱڴἂĂ}finrtv}ןꨃ\ekjaᡃЗxrnjgɂłÂ߻ֳ˭©ȁЁ~~|{ҁ~{~~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~~~~~~~y~~||pvˀ~~~}}}}uw}tv}su}np}ln}km}jk}ij}gi}cd}|||||||||||fg}df}||||||||||||||||||||||||||||||||||||||}}}}|||||||||||z{|||||{{|yz|tu|xy|~~|{}||}|}~||}|||~|||~|~~||k}х}K~ك}k}l}c}ij|fg|fg|bc|`a|^_|^_|^_|\]|__|bc|_`|\]|`a|df|de|fg|{{aa|Z[|{{XY|{{YZ|^_|^_|aa|\]|_`|``|_`|^_|_`|ee|jk|nn|^^|XX|ZZ|__|aa|[[|Z[|Z[|]^|aa|gh|fg|cd|bb|ff|jk|fg|kl|ll|ii|jj|jj|ee|nn|tt|nn|gg|jj|rr|hh|ii|ii|mm|op|qr|st|mn|vw|vw|qq|vv|kl|dd|qr|vv|yx|}~|||||||||||}{V~}|||dg}afbgv|w~xxzzzz{{z{||}}~~~€ÀĀŀƀȀȀʀˀˀ̀̀΀πрҀрЀЀπЀ{~YWڞ~ޟ~ݠ~~zU~՜~~{V~֞~Ľ~~~~~~~|{~~hg~uv~ѫ~yĀ〣倣瀣瀥瀤瀥耥逥ꀥꀦ뀦뀧뀧뀧뀧뀧쀨쀨쀨쀨쀨퀩񀫴򀬵򀬶󀬵󀬶󀬶󀭷􀭶􀭷􀭷󀯸􀯸󀭶򀮷󀭶򀭶󀭶򀭶򀭶󀭶󀭶󀭶􀭶􀭷󀬵󀬵򀫴񀪳񀪳瀕ȀvxeXXW~Tdpr~ŀπor}ZVT|P~~ّ~̋~ݑ~׍~ш~dž~Ɔ~ߓ~RTSRzOyO~zPRWY\[yx|΀׀׀րՀՀրՀԀԀҀҀрррЀ΀΀̀̀ˀʀʀʀȀǀŀƀŀÀ~rw}}~~~ʚ~~|i~|}|a}̀}K~k}g}t}ƽr}ۏ}P~ž~}|}؈}}}u}x}v}}r}Z}||V}[}I}I}{|O}L}V}.~5~Q}L}{{{c|E}R}V}J}[}M}L}1~S}W}\}X}U}N}-~`}^}Z}U}P}Z~żY~Ļ[~S~p|N~>;\P~w~c~@R~j~_~E~M~N~R~L~R~\~T~Q~Q~ü}||l}v}܎}؈}L~S~R~K~P~U~U~Y~S~Q~U~Ŷ_~]~T~N~Q~R~Q~X~[~}}J~{H~Ļz}|d}||n}o}ǿ}Ž|}}و}}Ԇ}Ѕ}Ї}L~V~ފ}و}}J~}}|L~ܑ}w}||||||||||||}}{}||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||eg}eg}ef}hi}jl}ik}gh}gh}hj}ik}hi}hj}hj}ik}ik}hi}gi}fh}ik}ln}ln}pq}qs}ux}{}}}ÁÁāƁƁǁɁˁ́΁΁сӁՁׁ؁ځ܁݁߁}}~¦̯ղ޵潂Ƃ}ehmnnrv}ďі境[Z夃ԙŎysokh~eƂ軂Ḃٱͫætݾֻx̼̀ہ{x߁ہ؁ʀ}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|~~{}~y{~y{~y{~~hm퀂w}}}}}}}}}vx}rt}su}xy}z{}np}km}jk}jl}jk}gh}ef}|||||||||||||||||||||||||||||||||||||||||||||||||Ĺ}}|||||||||||||}~|xz|z{||}|~|||xy|pq|no|wx|{}|yz|z{|{|||||||||x}Յ}}|}m}e}e}o}|bc|{{{{{{{{Z[|^_|\]|{\^|{{ab|ef|{{]]|^^|\\|{{{{[[|\\|^^|]]|`a|__|^_|\\|{]]|fg|no|qr|cd|{ZZ|__|]]|``|[\|\]|_`|ef|hi|cd|ab|dd|hi|ij|ef|hi|hi|ij|kl|lm|hh|pp|ss|mm|nn|op|ss|oo|jj|oo|qq|pq|uv|vw|ww|vw|uv|nm|kl|aa|ii|rr|vu|xx|xx|xw|xx|}}||||||||}}||||||}~t{v}}~~~~~ms||fkfkpv}ŀĀƀǀɀɀʀˀˀ΀΀π|Y~R~я~x~}u~x~x~z~~~}}~ü~}~~Õ~}}~~~~~~~ɞ~{Y~թ~~gmڀ〦䀧倧性性怨瀨瀩耪耪耩逪ꀪ逫ꀫꀫꀫꀬ뀬뀬뀬뀬퀭퀮񀰺򀱻󀱺򀱻񀱻򀱺򀱻򀱻򀱻󀱻󀱼󀱼󀲽􀳽􀲽􀲽󀲽󀲼􀱻󀳽􀱻􀲼􀳽󀳽󀴾󀳽󀳼󀳽򀲼󀳼􀲼󀲻򀱻񀲼򀱺򀱻񀯹񀱻󀱺󀱻􀱻󀰺󀰺󀰺򀯹򀯸퀤ۀ|]VWXUUS~΍~ˆ~~ɿ~~ɿ~‰~Ɍ~~ˎ~Б~ԑ~~~~ό~Ⱦ~}~w~w~z~º}~~~ā~w~ù~u~x~Dž~Ҍ~yN~~~~yN}OSUY[Q~rрӀӀӀҀԀԀӀӀрЀрπ̀̀̀̀ɀĀy~puwalhky~sxad~}~Ė~Κ~Ә~ʓ~v~ۖ}Z~}N~a~Y~W~V~}_~}ʖ}љ}V~}T~}S~}x}Ļf}W}}Q}X}|f}a}E}K}Q}`}B}R}Q}P}I}+~s|{{V|B}6}J}R}4~S}F}1~X}J}X}E}B}F}M}T}*~Y}E~C~L~s}u~>R~S~Z~\~AT~T~Gi~V~V~J~H~B~H~V~P~K~O~W~P~L~|}||||ŀ}ۆ}{H~I~}I~|G~P~L~H~O~K~N~J~I~R~Z~Z~\~ĵ]~ĵ]~W~X~K~zG~}I~݉}º{}|~|||k}p}l}l}|}ǂ}yJ~K~Ƃ}ԉ}}S~V~}}}M~O~N~ƈ}||||||||||||}g}o}~}|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||ij}jk}|kl}mo}pq}tu}vx}xy}{}}}}}~u{āāƁȁȁˁ́ρЁӁҁՁՁׁځہ݁|z||~ŧְͬᵂ쿂‚Ƃegjloqw~Βۚ梃襃ߞԗǏ~yvqmig}c㵂ٱխΩѮ㺀qжű~|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}~|~~|~~{}~{}~y{~x{~y{~x{~vy~tv~su~rt~pr~oq~lo~mo~km~km~jl~hj~hj~hj~hj~nq~qr~ik~mp~~}}}}}{|}vx}su}qs}pr}np}mo}ln}mn}no}km}ij}ef}ef}fg}||||||||||||||||||||||||||||||||||||||||||||||||y}~}|||||||||||||||||yz|yz|tv|zz|}~||~~|ln|hj|kl|mn|lm|tv|}~|||||u}ڊ}xF~ρ}¶u}d}`}`}m}y}k}|ij|ab|{{{{{{{{{{{{{{{{[\|]^|\]|\]|^^|[\|{{{{{{]]|``|]]|__|[\|{YZ|aa|dd|de|jk|ij|{XY|YY|^^|Z[|\\|_`|aa|cd|de|ab|`a|_`|ee|fg|cc|fg|cc|ff|ii|ff|gh|pq|qr|lm|jj|qq|lm|kk|no|tt|ss|rr|tu|st|z{|{{|zz|{{|yy|vv|xx|ww|ww|st|tt|uu|ww|yy|}||}~|||||||||x{|~|||||||Z\}bd}de}cd}}m~v~u~m~}uv}z{}}~~~}}}|~szdj}€ĀĀƀȀȀɀŀʆ~w~o~}~Ã~Ј~Ј~ƃ~~DŽ~Ʌ~ۍ~Ҋ~}~Ǽ~v~u~~}}}~ő~~~ǔ~Ó~}}~}ў~~~~~}Ҡ~ե~Ф~۩~Ш~~ej܀ဩဪ‫䀬䀬䀬䀭怭怭怭怮瀮耮耮逯逯逯逰ꀱ뀰뀱쀱퀱쀲퀲퀳퀲퀲퀳񀴾񀵿򀵿쀵퀴뀩Ԁ񀴾񀳽퀱뀰ꀡπwzxZ[_^YXTQQ~ю~Ǽ~ù~y~Ɖ~}~y~Ç~Ż~ù~ż~͊~Ԍ~ߑ~~ԋ~~ƻ{~w~w~r~u~~ߊ~}J}IKzG؄~v~t~Ȅ~~zL{L}NN}LORVU~ۖ~~~~Z|ƀππππ΀̀̀̀̀ʀhk~ϲ~ɱ~~~~lk~~ҧ~ڪ~Ϧ~à~»~~~~z{~~}}~Ⱦ~ż~~g~{}h}}X~g~f~Á}m}_}~W}}}||ם||ŏ}T~R~݉}{}g}k}e}j}[}^}X}V}@};}A}-~C}H}G}C}I})~@}r|{g|F}8}S}I}J}+~H}E}V}F}>}B}<}6}0~K}6}+~.~;~þ[}e~b~|9s~n~W~j~b~r~f}eD}~CS~p}B~H~F~O~W~Q~M~O~P~~J~g}|f}p}Ȁ}~J~N~V~Z~T~S~U~S~W~S~O~L~O~M~K~K~P~ʹ]~b~μ^~Ƹ_~W~T~{H~I~zG~x}||k}r}t}w}܇}x}ڈ}}Q~։}y}Ƽ|}ǽx}ւ}}g}i}h}|||}||~}|{{|rr|qp|ts|{{|zy|||{|Ѱ|}^}g}q}yE~x}|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||lm}mn}mn}np}su}uv}vx}y{}z{}|}}|~}}~}}}}}}}}}x|~Ȁz}¼ĽǿÁāƁŁȁɁʁ́ρρсҁӁՁׁفہ܁߁}xyz{}ŢΨ֭ൂ躂Ă~c~dhkmptx{Ë͑єʏËytqojhe‚嵂ܯ{lݴ̯|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|~wz~vy~wy~wy~wy~tv~rt~qs~oq~np~ln~lo~km~ik~jl~km~jl~jl~hk~gj~gi~gi~hj~eg~}}}}}}}}}}}}}}}wy}su}qs}qr}oq}no}lm}ik}km}ln}ln}ik}fh}eg}||||||||||||||||||||||||||||||||||||||||||||||||u}}ȋ}||||||||||||}~|{||mn||||||~|z{|vw|ij|uw|||pq|bd|de|`a|ij|}}||l}x}|I~}}zG~u}ĩ|د||]}Ⱦr}}҃}|lm|gh|jk|de|{{{{{{{{{{{{{{{{{`a|`a|]^|{{{{{{{{{ZZ|[[|ZZ|[[|Z[|{]]|]^|cd|jk|st|gh|{[\|[\|YZ|]]|_`|bb|de|bb|``|^^|bc|gh|ff|gg|fg|fg|ef|gg|ff|ii|kl|no|ii|jk|mn|hi|gh|ll|qq|oo|pp|op|rr|qr|qq|st|uv|z{|||||~~|~~|uu|qq|rr|ut|vv|xy|uu|z{|~||||||y{|su|~|||||||||XZ}[\}}e~l~o~l~j~}}}}Ƚ}¼}||}|}}xy}yz}}}}}~|ouzmqkprwsxko|ztzz|_܍~ۆ~h~β}}}ù|~ŀ~ż|~z~Ⱦ~Ʉ~zKyK~ی~}}}}}}}~~û~Ζ~~~~~~ɗ~֞~~}}ȝ~С~ȟ~Ϣ~~Ÿ~ģ~|[|\tuyƀڀ݀߀ု‰‰′䀱䀱倱倲怲怲倳瀳瀴耴耴逵逵逶ꀵꀵ逵ꀶ뀷뀶뀶ꀶ뀷쀷쀷쀷퀷퀷쀸퀸퀸쀸쀸쀸쀸뀷뀷뀷뀸쀸쀸쀸쀸쀸쀹퀸쀷뀵瀯݀ɀ}{WORPO~K}L쀷퀷쀶쀴瀙[ZSSVUP~ސ~Ս~א~~ݓ~Ր~͊~Ň~ˉ~}~Ɇ~Ҋ~Њ~͆~Â~Ɔ~Ї~{~}~؈~~~ׄ~މ~wGMPRQNOOOPMك~{~Żz~׉~OOM}M}M~LNQS{Nޓ~ڑ~~ޖ~~|Ocúw{tz~ܳ~~խ~Щ~ʧ~~yw~}~Ԥ~ܥ~Ҡ~º~~~~~~~~}}}}~~~z~ޭ}r}|Ѣ|i}g}}o}^}|l}N}ڑ}m~b~k}||t}߳}}a}^}}½}k}j}^}|Q}N};}V|P|7}F}:}?}1}G}0}O}3}>}@}B}6}^|>}C}<}(~A}Z|D}J};}D}7}0}D}C};})~O}/~0~B~;~V}F~v}@~V~Z~C~I~b~E~Dq~V~;~?~A~I~F~J~M~U~W~Q~R~O~R~P~Q~L~H~O~R~J~I~M~U~Q~L~J~H~{}ƹv}˾z}s}t}÷w}چ}N~O~X~Z~ɻ`~R~L~K~}J~i}ٻ|||j}m}b}|ʧ|||||||ww|zz||||xx|zy||||||||||||ǹ|Ѱ|߯||^}|b}d}{}}~}l}|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||ln}no}pq}op}qr}pq}qr}rt}op}rs}qs}tu}uw}uw}{}}}}~}}}}}}}}~ĹźǼʿ̿ÁŁƁǁɁˁ́ρЁҁӁՁׁف؁ځ܁݁ށ{t~wwxz|~ĜʢѨ׬᱂赂bdgjloprtw{~~{upnlieb춂͞|trfҮȪƧ}y{{~}|}z|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}~{|~wx~tv~rt~or~oq~pr~or~mo~kl~hj~hj~ij~gi~ef~eg~}}}}}}}}}}}}}}}}}}}}}}}y{}z}}}y|}vx}su}qs}pr}np}lm}ik}jl}ln}ln}km}hj}fh}|||||||||||||||||||||||||||||||||||||||||||||||̐}U~Q~ғ}}||||||||~||||{{|qr|lm|vx|wy||||}}|ww|lm|km|np|vw|xx|st|||˴|h}v}ӂ}F~N~F~G~O~}|ij|{dd||Ͷ||jk|dd|{de|ab|ff|ee|]^|`b|{{{{{{{{{{{{{{{ab|cd|{{{{{{{{{{Z[|{{{{{{[\|_`|_`|ef|hi|ef|\\|{Z[|Z[|^^|``|__|\\|[[|^_|cc|cc|cc|bc|de|cc|cc|gg|gh|gh|hi|jk|ij|cd|de|cc|ee|kl|kl|ij|ll|lm|nn|qr|qr|wx|z{|||||||||||vv|pp|no|oo|qr|wx|zz|}}|||||~|qs|ln|su|}||||||||||^^}ֲ}f~c~q~l~d~}}hh}|z}}zy}nn}oo}tt}uu}xx}}}}}}}uw~}}}}}|~~~~~~Ż~ޮ~~~xK{L~ͅ~y~~}~հ}֯}c~}}|~ƻ|~¸y~~yIxI߇~~s~gh}fg}xw}}}}}}~{~~і~֙~ż~~}~њ~ٞ~Ӝ~Ɨ~~Ҡ~ܤ~~~~Z{W{X|Z~ت~~}^~~lsӀހ߀߀ဳဳဵ〳ဴဵ〵〶䀷倷怷怶倷倸怷怸瀸怹瀸怹瀹瀹怹怹瀹瀸䀵ိӀÀ{x}~~w~ۈ~~}I}K~~zIMNQLO}KOOOތ~USTR~QVW~M|L~ގ~Ǿ~x~r~v~}~x~}~~~{~z~ź}~Ʉ~yJNTYZZUSQOPMMOQSROOOLKMM~H~́~o~ۍ~|MyJ{KzKyJyJ}JNOzK~~~~}OyMސ~~}O~۳~Ž~~ƽ~yY[|Yީ~~~~~~ȝ~~۠~ѝ~˜~~~~}}}}}x~}}v~x~}}x}{}w}m}T}|J}L}||g||S}΃}ό}e}]}r}U~|~ӓ}}ޚ}j}p}^}ʙ|}N}l|6}<}7}.}4|`{Y|Q|N|1}H}C}B}4}:}\|0}V|7|L|P|5}B}/~M}2}4}7}9}3}L|=}>}<}O}R}+~R}@}<}0~S~]}q}Q}M}|B}e}l}^}?~?~A~A~A~A~@~A~I~L~D~N~ǵV~]~V~O~J~K~R~S~ŴX~ȶX~O~M~O~N~K~G~G~K~}F~}}|}k}o}m}k}n}o}o}u}~}̃}}}m}|||||||||||||||||||||||ʫ|۱|`}a}|||ϻ||||j}|k}|}|}݀}ɿt}xB~}E~ǻz}s}||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||op}qq}pq}rs}tu}ww}yz}z|}wx}xy}}~}}}}}}}}¿}ɹ˺ͼоҿԿÁÁŁƁȁˁˁ́΁ЁӁӁՁցׁ؁Ѽށ|q}qsuwy{}~wƛ͠Ҥ٩ᬂ겂`cfhikklnqtxy~~}ytqljhc֠ÁjÙ}zyq߸ذ˨¢~{w~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}~~{}~xy~uw~rs~mo~km~ij~jk~hj~}}}}}}}}}}}}}}}}}}}}}}}}}}~}{}}{}}{}}{~}y{}vy}}wz}xz}vy}tv}pr}np}op}ln}mo}np}mp}no}ln}ij}fg}||||||||||||||||||||||||||||||||||||||||||||}}}~}q}t}w}r}|||||||||}|{||zz|}}||{|mm|fg|np|st|tu|y{||||tt|pp|ݿ|q}l}j}b}p}}N~V~c~_~ļf~|΢|ts|ii|{{{{{{{{{{{aa|bc|gh|_b|{{{{{{{{{{{{{{bd|df|jk|kl|{{{{{{{{{{{{{{{{{{{{ee|fg|``|{{{{{{WW|XX|]]|^_|cc|aa|ab|ab|cc|aa|ab|ee|gh|hh|kk|fg|dd|__|``|dd|ff|ff|ef|ee|ee|ii|ee|ll|ii|ll|qr|ww||}|~~|}}|~|~~|op|ik|mn|qr|tu|ww|xy|z{||}||||}|ij|mo|pr||}|~|y||z}||||||||||}}`~c~^~\~^~\~}}}b~f~i~m~·s~p~f~}}}}ο}}z~{~{~¶}~ƻ~~Á~˄~ʂ~̃~ԇ~׈~~|Jρ~w~~~̈́~х~m~e~Ϭ}h~a~e~|`~m~v~~~چ~~t~}}cd}gg}lm}pq}}}uu}}y~~~~Җ~ݙ~~~}|~“~ޝ~ϙ~~͘~М~מ~zWyU|U{U~WYY~}[~~~_`ժ~||~~vy~~u{~sloi~~~~~~~~~ث~Œ~v~s~w~|~o~r~x~x~܂~~~yF~~w~Ǽw~v~y~~~JJINNNPSQPMONOP{H{JLyJNUN~PWSSUSNMOPLOQRUSTYZYWSRPOMMKIIMMIzFHJLQOQQNK~HJ~H~~p~i~}~zJLLL|I|I|JyIyH}IK~~~…~ԉ~~~~ו~~զ~~{W{W}X~~~~~ҝ~ˉ~~yEyDwC~~v~p~u~|~u~|~z~v~s~u~i~}Ә}y}d}T}|u}|M~Á}T}K}}J}V}úf}}}ϊ}؃}}Y~Q~ԁ}و}V~]~|N~g}{}|q}O|G|2}N|J|2||>{C|1}6|N|1}A}A}@}7}>}Y|O|`{4|?|6};}(~=}6}9}<}:}F|=|a|=}?},~;}@}L}C}I}:~_}4~4~q}R}\}]}?~d}j}m}>~?~B~F~F~H~I~F~J~K~y:~I~U~g~d~n~n~h~e~p~j~_~W~P~M~P~}i}]}”|{|Ø|Ŀ|r}v}F~V~O~}|||||||||||||||||||||||||||~X}`}d}o}m}o}q}q}v}t}j}n}a}l}q}t}m}t}}}|F~P~P~O~y}zd}||||~~|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||op}||qr}qr}rs}uv}uv}vw}xy}~}}}}}}}}}}}‼ðѺԺ׼ؿÁŁƁǁȁɁˁˁ́ρЁҁҁထӹxu䀌{ϲδǨ”Ș͝ҡڥ㨂諂tڣefcfhd򫂱餂pvvrnjaɔfg➀מxqniղŦƣȤž}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}~~~~~|}~xz~uw~rt~mo~ln~ij~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}{~}z}}x{}xz}wy}y{}y{}y{}wy}tv}pq}oq}mo}mo}np}oq}oq}np}mo}km}hi}||||||||||||||||||||||||||||||||||||||||||}}Џ}ĉ}}p}|||}}z}þ||||||||ww||jl|jl|}~|{{|bd|`a|np|ln|st||||p}t}l}l}h}n}ĺw}S~T~zM~X~y~o~}|{|op|{{{{{{dd|{`a|{{{{{`a|^_|{{{{{{\^|bd|{{{{{]^|pr|rs|ij|op|qr||q}q}{{{{{{{{{[[|ab|_`|{{{fg|ll|ij|gh|\\|{YY|{{{{XY|\]|__|WV|{\\|[[|{\]|bc|bc|cd|`a|fg|aa|`a|YY|[\|aa|]]|`a|ef|ff|ff|mm|kk|op|mn|oo|uv|yz|}~||||||st|mn|qr|xx|zz|{|||||||pr|kl|pq|xy|wy|tu|pq|vy|||||||||||}̛}}Y~Y~f~h~d~f~f~f~ʽp~k~i~g~_~ٟ}—}ʚ}]~Y~a~g~÷q~~~q~p~ʿ{~~~~؆~|J~Ĺu~p~˿z~ӂ~~҂~_~b~`~}f~˧}j~}i~߇~~yI~ߊ~r~b~}hh}kl}nn}lm}rs}ww}qq}ss}}}~~~~R}Sč~~{Rٙ~~{S~»~~~~~}W[][~Z\\_^^ͣ~~~~UU~K؈~~MMR~~MSM~~Iք~e~~~{I}KN߇~Kźr~b~}~{~~MM|JJT[ýh\OQ~م~}~~K}GE~~DHGNHI~|D}GJPPOROMONPWYTSTOTTWY\RPRQPPQSSYPRSQSSRPNMKKKJJJGGHJI|EF|EGJI~GGMKML~H}IJ~j~i~ˁ~~~xH|JKJMPPM~|~ʄ~Ѕ~ڈ~~~xNvS~vT~ѝ~͜~~~~º~t~U~̻c~h~i~p~s~s~n~n~q~j~j~n~i~l~h~~G~˷|z}}}}a~s~ł~{~b~}}}¼}j}M~}Q~Z~W~n}c}p}v}Q~I~K~H~}D~{N}T}`}G|~6|W|S{?{D{D{E|6}a{D|/}+}6}G}:}5}D|Q|J{_{7|S|S|9}4}4}A}B}0}X|X{N|[|:}S}3}J}[}H}I}4~0~-~9~=~A~i}{>~m}s}j}j}f}r}=~@~B~G~ͺN~ͺP~˹R~M~A~l}ž\}N~L~G~J~zA~N~N~Q~W~ù]~P~T~K~k}Z}͔|{{{{r||e}L~J~a~L~g}{{{ss||||||||||||é|ԯ||ȟ|˟|ϝ|џ|||||ľ|~X}Y}_}]}`}d}h}c}j}p}ʼn}p}j}f}ɝ|Ҥ||}a}r}u}}H~E~I~}g}|~|{z|~}||||~~||||~|||||||||||||||||~~|||||||||||||||||||||||||}~|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||qr}xz}st}rs}rs}uv}z{}}}}}}}}}}}}}}Ϳ}~~uγٺں޼߽῁῁ÁÁāƁǁȁʁˁʁӀ€~d{jөు賁€ʀir{țќ|uukks~ᛁƌqܞ]孀٤ԙɎ‹umifް˦~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{~~wy~su~}qs~rt~vw~no~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}{~}z}}z}}}}|~}z|}wy}vx}tv}qs}oq}np}mo}no}pr}pq}mo}oq}mo}np}ik}|||||jl}|||||||||||||||||||||||||||||||||t}yO~}}z}}}|||~}z}p}||||}}|||||}}||st|ln||~|pq|fg|jk|||q}y}u}r}r}q}n}t}}}݉}ڃ}}H~}x}|{{{{{wy{{{{{{{{{{{{bd|ce|{{{{{{{\^|\]|{{{{{np||gh|{{`a|{wx|||{{{{{{{{{{{{{{bc|tu|tv|z||ef|`a|]^|\\|{{__|]]|\\|``|ab|YZ|UU|^^|ab|ab|ee|kl|kl|ff|cc|fg|\]|\\|bb|ll|dd|hi|ef|qr|tu|pq|xy|rr|vv|wx|yz||||||||||||~||||||yz|}}|||||~|xy|qs|wx|tv|qt|su|~|||~|||||||||||}Κ}Ř}֝}j~ɾp~b~h~m~j~l~b~ڝ}Ř}Н}}}W~^~t~i~h~k~Ÿp~s~w~܁~x~p~l~c~q~~}~s~d~}`~a~{\~g~`~l~d~~xH~~ވ~ۆ~؄~~`~}qq}op}ll}ij}mn}rr}vw}tt}ww}}}Օ~ߚ~Ď~û~ؔ~|RTSݘ~ڛ~~~~W{V~cacgߣ~gY~|X`[Q~I~|~v~k~¸o~݄~~~S\~edRT~s~ׄ~~~Lއ~փ~ނ~v~`~̀~l~SV_me\[VYO\]{HYSRULIIOMU\YfRLTQPVTRQRP[POTWUNLMTUZZ[al^XURYWPROLJPKMHHGIIH}FGGGFHJMG~}H{G}GJ~yF~HGGIIJJHK~ك~m~|~xGJMMMMLLLKև~ӄ~ƀ~Ѕ~׈~~{L~~~û~~~~|~˾i~h~ͽc~p~k~i~d~j~ʹa~Ǹa~ȸ]~Ͼb~i~b~;`~ȸ]~d~}L~||l}}|o}}f~a~י}ʎ}~}i}m}üq}l}ƿk}]}Z}Z}d}q}u}x}q}x}µ^}Ƚd}s}U}U|K}D|9|I|\zyYzlz9|A|X|4}:}E|N|9}7}4}N|.}X{Q{9|D|?|/}.}C}F}3}P|7}Y|4}C}C}F}5};}P}=}>}&~N}L}1~|3~5~~/~{<~?~C~E~A~m}u}z}@~A~B~G~D~H~̼R~пV~O~ȷS~Q~S~Q~r}z|{{{{{ÿ{{{{{{us|{{{{{̿{|}S~H~P~G~ʽg}ֆ|{{||||||||||||Ȗ|zV}\}[}`}_}f}c}^}^}Z}]}d}Ⱦn}p}s}Žm}t}Ļl}q}v}l}t}s}i}b}_}ݧ||z}k}y}zE~M~W~N~L~d}|~||||vv|zy|yy|zz|yy|xw|xw|xy|yy|ww|yy|~||||||~~|yy|xx|zz||||||||||~}|}}|||||||||||{{||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||no}op}qq}||qr}tu}wx}vw}wy}}}}}}}}}}}}}}}}}}~u۶߸߹⻁仁㼁彁⻁Ӱƻ׀ĩʫѭϩ֭巀𼀀dmpnd嵀跀bgjoqmibߤݜƐ~togدϧž}z|~{~~~~~~ļ~~~~~~~~~~~~~~~~~~~~~~~~~~~~xz~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}{}}{}}}y|}}y|}ux}uw}su}sv}or}mp}mo}mo}km}km}km}np}oq}rt}hj}|||||uw}np}|||||||||||||||||||||||||||||||}[~}||}}||||x}ۘ}}||||||||{||ww|xx|vw|vw|mn|vv|}~||۸|t}u}u}o}p}˿v}x}ĺt}y}}}w}w}p}ү||tt|{{{{{{{{{{{{{{{{{vx{{{{mo|{{{{{{{{{{{{{{{_a|km|{{{kl|mn|uw|ó|np|km|bc|{{{{{{{{{{{{`a|||pq|{{\\|bb|pq||uv|ZZ|[\|{cc|`a|YZ|\\|cc|cd|cd|kk|nn|kl|ff|ff|``|ab|bb|cd|cd|ff|kl|no|st|no||zz|||yy|||||||~||||||||y}}Ŋ}{}|n}t}|||||||{{||||||||||||||||ܟ}Z~l~i~b~d~i~e~]~՗}}}U~]~`~]~a~]~]~a~g~i~ǻk~j~e~|U~ǘ}_~ʾz~{~t~^~}[~h~c~}}`~~{J~~Mф~~PJNՆ~z~p~}mm}op}st}uu}ss}pp}ww}zz}~}}}r~~Zmeļ~ӑ~~~~۔~~ؖ~W˕~~UbX~WJY}KŻn~`~c~g~{~ڀ~ۀ~܄~Kżq~r~ۂ~Ձ~x~x~R~P~~]}I~KPP|KQL\gpile~IseWPQJ{Ȁ~Zlvpqa]SQVRTLMSPFNPR[XW[ax\TEISUOXjtgoxuaYKNRSNNOWQTNUZT`YPPNJ|EHLNPM}GGIIKIHHHIzGJIJ{FyGM~x~o~}~GGJKJKJI}Hއ~у~}~Ѓ~~|IzJڍ~ˑ~~~m~`~n~j~d~f~p~n~}A{~~D~t~l~u~}~t~e~h~Q~|}}|||||}q}}k}}{}֋}Y~d~P~Q~b}m}z}}u}׈}ۃ}k}\~L~F~|a|g|9}5}]|R|H|@|yyUzA{9|T{0}5}4}A}2}?}7}B}N|9}/}~1|O|F|c{9|F|0}=}?|U|9}8}D}I}=}E}7}Z|@}:}?})~O}D}.~3~/~~6~x}p}zC~H~F~C~F~K~J~M~D~z>~@~s}y<~y;~p}o}A~J~\~`}?~]}{{{{{yy{{{{{{{{{{{{|y}R~P~M~M~D~{}{{{{{{{{{{||ǽ||^}h}e}h}]}j}p}m}j}h}ȿk}e}i}|}zC~}}}{A~C~}}f}»i}p}}P~R~O~G~B~z@~G~@~v}q}X~K~R~f}|wv|xv|yx|}||yz|{||zy|{{|~~|zz|xx|||||zy|vu|yy|ww|vv|sr|vv|}}||{||wx|zz|~|}}|xx|vv|sr|~}|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||qr}z|}xy}yz}uw}tu}wx}uv}vw}y{}{|}tv}}}}}}}}}}}}}}}}}}~װߵߵുְyyy}Šˤ֪bᦀՠԦШݪ^bdgid_ϝzrsuhhݳѬʨȦƢ|~~~~~~~~ý~~~~~~~~~~~~~~~~~~~~~~~~~~~}~{}~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|~}|~}|~}y{}}y|}}wy}tv}ru}ru}pr}mp}mp}oq}mo}ln}mo}ln}jk}|||||||||||||||||||||||||||||||||||||||ˍ}|M~}}||z}}u}|||~}΋}x}k}|ӿ||||vv||||||z{|oo|op|pq|gh|||m}s}t}g}||^}||d}n}t}u}e}a}|{no|op|{{{qq|{{{{{{np|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{_a|{{ik|{{{{{{{{{{{{{{{{{jl|fg|de|{{{{{hi|fg|ZZ|[\|ac|hi|^^|Z[|Z[|YY|]]|__|hi|np|nn|cd|``|_`|\]|aa|^_|bb|ef|gg|hi|vw|no|no|st|jk|np|vx|||||~|rs|||z||||y}X~u~x~k~]~P~^~[~`~e~h~j~a~e~l~[~\~~}|||||su||||||||{y}`~r~n~c~{~y~e~h~W~}|}W~~Q~\~Z~i~Ƽm~ɿn~p~y~o~o~q~`~Z~`~}}]~m~~}~}}}}~{IL~~O~GXcV`¶YgRMy~h~׼}oo}pq}pq}ss}st}uu}z{}z{}yy}}o~~ؗ~~ݕ~۔~û~~R[YW~~~~~t~q~ɿn~v~h~a~^~[~[~_~`~c~ƻk~\~W~X~W~[~a~}~JKJH{F~~~~JyA~~~MYZTV[ǽ^jVE{?Yd]WPPRWhkrpJTTMUNLIOOJHUQRiobOQI~BJOȾcmnndoȾ^Qf_Uýdr]ZWNLHRSOLUUVRVMOVXJIHJFLO}FHIIJKIJIKRRRQJK}~]~Ⱥs~t~~|~~~~w~Ÿm~̾o~r~p~u~~~~xF~π~x~x~h~ɽc~[~S~l~d~d~b~x~x~t~h~t~p~n~u~t~d~o~ɂ}|zz||||}̔}{}v}n}}}}b~T~D~z}}~D~C~E~H~O~M~v}v}>~y<~k|l{Z{M|A}:}E|\{6||zyrz=|:|mzM|5}9}O|H|1}7}?}M|H}=}B|S|?|e{g{4|N|/}O|O|2}2}:}A}C}7}6}1}B}0};}A}<}4}>}-~'~0~}s}k}|}r}x}E~v}B~E~B~}@~C~Q~V~}K}|||Z}t}@~F~`}j}A}|{{{{~{{{{{{{{{{{|j}I~N~N~H~~>~^}{{{{{{{{|||||ӥ||[}d}h}k}Ƽr}o}i}a}`}[}o}`}}}d~J~ſm}zC~d~}}}}H~}H~K~L~C~D~C~D~G~ȼ[~J~G~P~}ð|rq|ss|tt|wv|xx|yx|yx|zy|wv|zz||}|yx|wv|yy|yy|yy|ww|vw|vw|ss|ss|zy||{|vu|wu|yx|{{|ut|rq|xx||||}|~|||{||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||tt}uv}yz}}~}}}}}}}}}}}}}}}~Հŀuwzz{ƙ֝㩀ᧀ͛}Z}^^Yܦztph~ejΪ£zw~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~}}}|~}{~}|}}|~}ux}wz}wy}tv}uw}tv}su}or}or}qs}oq}oq}oq}np}no}mo}km}||||||||||||||||||||||||||||||||~|~|||˿}|M~ƽ}o}||||p}h}|{||}}||m}պ|ʲ||||||||vv|uu|tt|ll|fg|ee|jj||e}b}_}k}|}u}|||ý||Ш|S~DŽ}|yy|{{{pp|{{qq|yy|rs||{{{{bd|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{~{{{{{{{{{{z{{{{{{{{{{{{{{{{{hi|`b|cf|{{{{E}|{|y|u|{[\|{{WX|{YZ|``|jj|lm|^^|ZZ|\]|[\|[[|`a|ef|ee|ab|cc|hi|pr|no|rs|op|vx||xy|kl|qr|y{|vw|su|uw|pr||~||}Z~S~\~e~݋}у}͂}ۉ}}}~}R~O~}}^~X~i~}||||||||||||||zy}}}ٝ}m~u~}d~}}|}||~}}^~c~g~s~s~l~i~f~j~x~{B~~~q~_~_~i~j~úh~a~}q~LL߂~v~}HIfk=CN~р~q~م~~~Ƃ~}oo}oo}op}rs}qr}op}qr}}}~}}}Æ~΋~Ć~~_^Q~LHHXVN~`~V~}}U~b~a~~܂~~O~~~|DQG_f_O~M~܀~~FJIx~QWOJE~~ENL~}EO~EHLNILRWZPRSZZMRRJHIRZVRfG~>V~C~DDOMJOQcnfûaYNXVLLhrJFXLHH{CLIFFF|D|D~|DQP~~~~FFI~EGI~}FJJJIHIIL[Q`^QI|C|Fo~ƺn~~~~~{~u~z~x~x~w~z~|~t~s~o~j~g~g~Ʒ`~ô]~˽`~ɽ`~o~j~d~_~z~{?Cz~h~f~j~l~y~T~ճ|ĵ||}f}d}f}`}[}m}}l}}@~z}G~V~P~K~L~F~E~H~W~V~y@~>~u}z;~r}k}D~K}M|6|S|C}D}[{S{T{b{I{\{A|U|8|K|=}F}G|9|O|=}.}@|A}1}R|i{[{j{l{N{I|U|H|J|-}8}4}+};}8}/}N|C}7}N}7}8}9}7}G}.~>~C~}}S~P~a~q~n~T~m}x}y}u}z}g}{{|{||d|M}L~_~^~|9R~K~j}Ո|{z{{{{{{{{{{{{U}T}C~@~F~J~D~k}{{{{{||~|{{{{v|||Й|]}m}Z}||h}n}s}}}}|}|}W~Z~S~V~X~p~}z}o}ſ}̈́}҂}ׁ}O~\~P~U~Z~X~Y~L~D~E~~@~B~p}|ut|xv|vu|wv|uu|tt||||||rt|{||||ss|lm|qq|ss|xx|rr|on|xx|ut|{{|xw|||}|zx|yw|wu|po|pp|xw|xw|||||||zz||||yz|yz|}|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||uw}||tu}rs}tv}|tu}wx}yy}{{}}}}}}}}}}}}}}}}}~mmqvy}ÔyԜ裀墀륀塀̞vmli~eް֬}vs~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~x{~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~}{~}}}{~}}}{~}vx}x{}xz}yz}xz}wx}tw}sv}ps}qs}pr}qr}rs}rt}qr}op}mo}||||||||||||||||||||||||||||||||||v}|}ٍ}҉}u}||||»|m}|||||u}||Ļ|||||||||z|vu||}}|yy|||n}|s}ߐ}T~}}ܲ||{|||Žs}|xx|uu|{{{{{{{{y|~}|rs||{{{{{hj|{{{{{{{{{{{{{{{{{{{{{z|o|t|u|>}f|~b|{{{{{{{{{{{{{{{{{{{{{{{|}|op|{{rs|ތ|D}L}D}<}t|B}H}>}o|{{{{{{bc|[\|lm|no|lm|]^|ZZ|VV|VV|XX|WW|ZZ|`a|hj|km|df|de|{||ij|rs|uw|lm|qs|pr|eg|ln|jk|np|||W~\~c~>ø`~т}h}k}p}}}}҆}~}q}{}}}O~w}|{}|~|||~|||||}|}||||||||||}|Đ}ʐ}}w}r}v}}^~l~l~ƾf~j~r~y~~~y~v~x~o~f~l~h~k~v~|?Eb~||M~>u}a~[~֟}}J}KOYVL\TXQ؃~t~jj}nn}nn}st}tt}uu}uv}wx}xy}}}ƽ}҇~|INSǽaQPMK~NLF~~]~W~[~d~MDr~úp~h~`~^~m~Fȿ`eûbSV9cƼ]EUSN~LJOv~Ki_ļaegZ~GC~zB}C}D~F~E~~HIHGIIEFKXi``QVĹ\[WTNNRVRWŻ_e_^ɿcWWURULYUVZXVRKFHILI{BINJEHE~GFD~zB|CwA|C|CyA{BEF~wCzEzD{E}FFGHG{CyCPMMS{Ez~QV\W~{~Ǿu~ùs~¸s~m~~p~t~p~v~n~b~a~ͽ^~ξ^~_~]~ͽ^~ʼ^~ʻ\~̽\~ĴY~W~̾`~x~f~q~m~ĸg~^~Ӗ}ޗ}}^~p~~O~Ձ}{}o}x}w}B~l~j~\~ǿb~^~U~c~G~]~S~@~r}a}¼^}k}Ǿc}e}T}W}:}E|^{T|:}3}8}Y{ozmzH{~?{:|P|J|6|;}A})}4|8|-}=|0}:}=|B|I|H|5}N|:|@|f{D|+}@|.}-}N|:}?}2}-}@}B}=};})}7}-}I}'~A}7~s}]}ݒ|]}p}}^}i}Л||x|~|{vx|{{{ʪ{||ƚ|W}:m~[~i~X~οQ~U~U~M~B~_}}|{{{{{{{{{{}}O~B~|;~g}¹|{{{{{{vu|vv|{||{||g}g}d}p}z}z}F~J~G~~}Z~e~b~{J~O~[~P~{E~S~L~}K~Q~m}ür}s}q}u}z}y}s}}|G~M~T~P~U~P~O~I~M~G~n}|sr|vv|{z|||vv|||||||oo|lm|ww|ss|nn|rs|kk|kk|xx|xw|vu|sr|rp|rp|tr|rp|sr|vu|uu|kl|jk|rs|zz|||~|zz|{|||||}||}|}~|||||||||||||||||mo}||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||rt}st}pr}vw}wx}|tu}tv}{}}z|}|~}}}}}}}}}}}}}}}}}ü}}~~µ˽~lmnqqu|{xy}}{sqrtyӓԕҔٚϗ|mefղױ֨}|vtq~~~~~ɾ~~~~~~~~~~~~~|~~z}~y|~~~~~~~~~~~|~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~}~}y{}wz}y|}y|}wz}uw}vy}z|}{}}xz}vy}vy}tw}rt}rt}su}su}rt}pq}||||||||||||||||||||||||||||||||||q}}ɏ}{}û}ȿ}r}|||m}¾|ƾ|m}|~||||щ}ڵ||||޽|||||||}}||݌}l}q}k}}T~U~}y}^}||{|q}|}|xx|||{||{{{{|zz||{{{{ts||{{{{{{{{{{{{{{qt{{{{{s|U}=}=}Y|i|y{|{z{{{{{{uu||||z||{{{{{{{{{{{{{{{pq|~|N}V}O}t}Y}@}j|L}O}j|S|{{gh||{{{\\|cd|lm|vw|cd|YZ|XY|{{\^|mn|^`|bd|vx|^_|mm|cd|mn|kl|wx|hh|lm|hi||||||||}x~P~~9Y~~d}ٽ|a}h~Uz~~e~f~q~~~q~~Օ}||~|xx|vw|mn|no|yy|}}|yz|xy|{|||||~||||||||~s}}}̎}ˌ}}S~\~\~[~\~T~U~R~P~Z~ƾf~b~`~b~{~q~C{~IEźf~Ս}||V~ь}|P~}v~z~~p~~~R^wdQ~}wv}mm}kk}no}sr}uu}»}~GTSPMMRɻWRV~q~~I~_~Y~d~}}o~PTXTNKFw~I^fbXKYUVTWGOr~}]]I|BWwAElsnjOx?~Az@s~r~}B|BFE~OIFJROG~NVMLǽa__X>jtücju}9gnz7olBHTaY[VdVN]NWJOMGIXJPPOSISZWUWKSSQOWXNJMMQLGLL~E|D~DGHF~E~xA{CIzB~~߆~݃~t~~~~~x~y~u~|~w~a~k~i~a~a~_~ŵV~±T~о\~_~ǸZ~U~U~}k}z^}ˮ|||~T}c}g}b}v}}m}Վ}}}|}Q~L~t}U~J~=~|<~@~H~r~@ü`~{}]}S}c}R~q}B~O~}l}u|E|[|~1|N{?|5},}T|O{dzPzlz;{8|/}0}O{O|+}8}D}>|E|Y{<|~+}]{4|A|M|?}/}K|:|M{h{R|I|)}D|/|>}*}M|;};}?}A}5}2}0},}2}8}J}Q}{`|@}E}մ{{|||}|{{{{{{{ҽ{{{üd}=ĺKTH98?c~ƻV~?~]~ŷU~K~W}{|{{{{{{{|j}|{{{{{{{{{{{{{{{{{{{{~|{{|||||}]~}}G~w}x}q}u}~}s}w}x}}|}|A~|}}}z}|@~{?~z?~y?~D~G~I~O~J~M~J~P~X~U~I~j}~|ii|cc|{dc|xw|lk|nm|qq|tt|ii|dd|YY|ll|ee|aa|]]|gf|sq|sr|ji|ji|ih|nm|on|qp|ts|po|kk|de|mm|ss|xx|vu|vv|xy|su|vw|ww|{||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||pr}sv}rt}su}xz}uw}wy}|~}~}}}}}}}}}}}}}}}}}}}}ȷ}}}~ƾ~~ĴŲ˷Լ~knrpomkskfilinxtg~a߬بǟТʞ|{xu}r~~~~~~Ͽ~ʺ~~~~~~~~~~~~~|~~y{~wy~ux~vy~wz~vz~uy~wz~xz~tv~rs~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|~}y{}y{}~}z}}y{}wy}{~}|}y{}y{}y{}wy}vx}tw}sv}su}ps}ln}jl}jl}||ps}jl}||||||||}}||||||||||||||||||||~}ü}q}k}v}܏}{}n}t}|Ҽ|||m}j}|pr|qs|mo|ln||r}||b}||||||||O~V~i}t}{}}}ʀ}m}q}|͜|||{n~a}|v}~|Q}m}a}a}a}Q}R}||||||sq|{{{km|gh|{{{{{{{{{{{{{vw{]]{ff{cc{ff{t|U}P}G}7}7}]|[{B|M|m{}{{st|ux|qs|ts|~~|{{{{{{{{{{{{{{{{{dd|kk|l};~i}j}V}i}c}h}|g|ӡ{{m|m|ׅ|_|{|{qr|lm|uu|kk|rr|ff|^]|{{VV|bd|qs|ij|ll||ab|hh|fg||rr||aa|`a|tv||||||Ҿ|HFg~d}θ|ɱ|˼r}N}~Z~}}l~|~]~W~|||}|||yz|uv|vw|no|yz||||||||||||||||x}¼}ˊ}}X~]~f~p~W~t~T~^~^~ºf~u~[~x~n~PL~?N_Iʻa~|||||W~W~|||``}rr}c~~N}?x:ǻaUp~^~}qp}kk}mm}tt}|~GIOHH|~p~y~DUSHv~MKLEB{~Ǿk~EPJFEOT{7gLNWlvgTRNZc_WJ~||}~OR`ƿfeVEHlaNG}A|@BWy>|AEECBBCCBEI{DJQCź[bN[cg`__bA@A@[?jBRs½iviVOP[JVPNHFIGGNK~Mº`mm@^nOivga]aaZknZYQ\SQWLJAJKHHHKTPMFDDICAA~=>BCt~_~ǸX~b~g~b~b~d~`~[~ȸV~ƷV~ƷV~T~n}~||z|}|||V}u}s}Ӟ|T}|V}|f|{{s|}L~B~=~`~}8>F~M~h~R~u}f}ʽg}t}L~N~s}û]~U~C~_~O~H|N{;|F{bzE|6}1}8}*}C|;{J{`z2|H|=}M{;|P|.};}=|l{J{I{=|B{9|C|;}=}6}F|b{O{K{4|O|-}J|1|S|O|A|P|<}>}H}:}B}6}:}A}B}Q}K}B}@}(~C}}{Ě{{{{{{{{{{|~||ظ||r}ža~IKMLF?z39X~H~O~D~f}S~y;~}|{|z|{{{{U}Θ|{{{{{{{{|{{{{{{{{{{{{{{{|{ɶ{{x||f}i}S}l}e}`}T}؊|DŽ|Q}Ԓ|ɒ|ԗ|b}s}y?~u}g}{}y@~x}A~@~|?~{?~z}}}y?~C~M~ƺ]~S~V~F~_}hg|ff|kj|lj|ml|ml|ji|ih|hh|cc|{[[|^_|dd|oo|wv|po|lk|ii|qo|nl|nl|pn|ih|kj|gf|ee|nm|rq|uu|xy|xx||{||y{|z||{{|wx|||||}}||||||||||||qr||||~~||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||pr}||uw}||xz}z|}xz}wy}z|}z|}}}}}}}}}}}}}}}}}}}}}}}}{~~~~²Ͽcepopplsurrj`޴ү΢ʚ}~|y~~~~~~~~~~~~ƴ~~~~~~~~~~~~|~~wy~ux~tv~ux~ru~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|~}y|}y{}yz}xz}tw}x{}{~}y|}y|}xy}xz}xy}vy}sv}ps}oq}oq}ik}kl}|||km}mo}||||||||}}}}x}|}}|||||||||||||||v}û~}x}q}ƾ}}y}p}w}i}g}ɸ||j}f}޼||}~|qs|mo|~~|ÿ|||||||||||ڱ|j}w}p}|b}r}p}o}ƿ~}x}|ܢ|»|||a~e}L}S}\}`}g}Z}?~e}C}O}_}N}||z|||{{{{{{{{{{{{{{{{{{{{{oo{|{{u|O}W}8}G}7}X|~{j{B|~{v{Z{{ab|mp|jl|kl|mo|``|{{{{{{{{{{{{{{{{|U}^}\}V}:~9~Z}=}Q|r{{{{a|I|R|y|E}W}`}Ɔ||||cc|||dd|{{gf|~||mn|qq|bc|jk|aa|ij|rr|xy||qq|^^|^_|`a|no|~|yy|||}OW^y}|˷|u}@f~W~v}m}|J~u~~Eg~Y~||}}|no|xy||}|}~|mm|lm|_a|qr|lm|ss|tu|}~|uv|{||{|||||~|xz|||||p}Ȉ}}z~b~r~q~c~j~p~t~p~w~LX~RCI@E@D:v~|||}|q~}||||||}}}`~ȼq~˿r~ʽq~m~`~}}pp}nn}@Fx>c~T~v~\~V~r~ǿcnpLLkrvoeRCYSQNgKFLL}=g}>Žcs@Nx~}~HFGFL}B[~|}EGC}ybbUek[dR{?|?GFIC@BGCCBBCFFv~x~v~E}~RVT_pstbbfmmletvľf¼kjg]ifNNMGM{DHERKN~H}~MOK[R`dþjľku}|@vhdlfMNSOLVLOOXMECBL\VY_WSKL?y<z~POGIDC~>~?]~_~d~]~`~|>UD[~U~E~||s}N~J~S~k~X~D~B~}}h}|pq||S}G~b~`~Q~}:ENn~Z~S~w}r}r}q}E~H~T~o}û`}U}i}O}a}E~||X{V{F{\{I|I|I|5}N|~'}H|6|z0|4|H|@}]{H{G|E|H|9|W{0|`zL{K{>|C|5}D}A}D|X{`{U{¾L|]|<}P|3}O|A|F|5}<}R}F}8}=};}@}9}C})~)~H}H}>}K}>}@}c|{{{{{ɼ{{y|||||T~g~R~h~LM\YYA}7I=E~S~Q~I~L~J~~>~L}{{{{{{{{xz{{{{{{{{{uu{~~{{{|e}y|{{xw||yy|no|ww|||{||ϛ|}P~N~|}h}[}\}a}|Њ|b}k}}b}h}o}s}r}}}F~F~I~G~C~|@~~B~G~P~T~K~M~S~N~a}{x|ut|nn|ll|no|ll|hh|{{ed|hg|bb|ll|a`|ba|ff|kj|ll|mm|nn|oo|lm|bb|fg|oo|ih|kk|ee|hh|nn|lm|qr|ln|qs|lm|~||vw|{||||||||||||z||||z|||{{|wy|jl||z{||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||tv}z|}||||vx}|uw}y|}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~ĴԺ߽ӵԵװگfggdcihޫ˥ȥ~xx~w~~~~~~~~~~~~~ɺ~~~~~~~~~~~~~~y{~tv~su~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}y|}xz}wy}wz}wy}tw}ux}x{}|}}|}|}{~}yz}y{}wx}tv}rs}mn}|||||||km}mn}jk}|||||~}}}}~}|||||||||||||||||||s}{}ļ{}Ž|}~}k}t}~g}ʽ||||j}|||vw|st||e}ͱ|ϴ|pp||z||||||۰|K}[}X}O}F}a}q}^}]}d}q}e}|]}|[}y}?~V}K}>}g}T}5~<~Q}L}E}=}W}D}y|~f|v|||{z||||{{{{{{{{{{{{{{{{{{{{m|P}`}f}R}S}F}V|Z{c{z{L|y{M|[|{{|~|ll||w|{{{{{{{{{{s||՛|||y|0~T}\}\}<~U}E}}7}~>}Q}_|ſ|x}a}W||{D}S}\}S}A}K}L}x|ǂ|́|m}{|||̣||bb|cc|ff|aa|kk|ee|aa|kk|gg|{{]^|hi|pq|yx||||m}?C}|u}d}AT~||p}m~A@^~O~Ÿ||uv|ij|fg|fg|no|pq|bc|cd|lm|mo||~|vx|tu|xy|xy|vw|~|wy|ux|yz||{||{||}~|||}}~j~XTn]z~YXkpPri~PutkcW_~}q}|}|b~|||||||||||}ۥ}]~e~i~٣}}\~}v~˿i~`~X~Q~R~P~W~M~Q]|Gc~~n]XTU»digW~{~{FP\o~»bz~MQPME{B}AEE~{BDC{>v~f~A|BQWUZffnn[YR}@BBCCDCCCBEEEGGQGF~EDMOKLSci\KIQ[fy?UTS]^aǽc\VRQIMDISQHNKFy@KRWigcQK~NNMNGWXfSTľiýfZOKDINLMF|>z?B}=BDA{~}>C><>:{9>ADRRz<v~u~}:N~~BX~L~}n}|e}}}P~R~q}U}r}}}Y}R}|o|۲{r}·X~@~O~`~[~=Q~g~U~G~H~R}C}{|I}R}G}b~v~M~Y}b}^~j~i~`~N}U{mzN{J|G|?|;|1}J|/}.}1}'}+}.}5}G{hzK{h{t{B|_{/|]zN{2|?|H|/}3}:}E|=|F|-}4}X|/}+}9}K|P|\|3}6}5}@}F}5}8}?}2}9};}1}D}6}9}=}9}H}X|uw{{{{z{{{{||m}b~CJNFaX_ûZND|8>9d~k~Z~@~g}H~L~?~Ӈ|{{{{}}{xz{ii{zz}|{yx{{xw{wv{{{{{{{{{xx|{||{{{˿{¿{{ww|{{{||T}q|{{|DŽ|؋|O}N}R}S}f}}Q}P}\}w}r}d}m}o}D~L~F~E~}}}}]~[~}H~Q~J~|}||mm|de|{{ed|cb|hh|jk|{{ee|{{gh|ii|kk|fe|{gg|fg|mm|tu|mn|nn|vw|rs|rs|qr|mo|op|yy|op|gg|||z{|wx|xy|tu|||{}||~||{}|||vw|xy|||lm|no|vx|pq||||}|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||wy}|~}|tv}|}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}w~~~~Ǧɥ¡ġ¢Ǣӧի޳ܰ`_^cҨɟš}yzzt~~~~~~~~~~~Ŀ~~~~~~~~~~~~~~~~~{}~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|~}y{}vy}uw}su}sv}ux}pr}ps}rt}y{}~}~}{}}y|}y{}xz}xy}uw}su}km}|||||||||||||||}}|||||||||||||||||}|wz||||||ſ|v}o}i}p}|||||h}|ư|||yz||i}b}b}|wv|hi|{{x|||y||R}M}@}O}C}V}b}X}Q}P}Z}_}T}W}Y}k}]}i}b}N}R}F}Y}W}W}W}Z}L}B}C};}{:}j|o|y|{{{{k|B}|j|{{{{|{S}{R}}|Z}Z}b}¶f}D~x}C~H~H~A~q}M~?~P}Ǿ[}~;~ĻY}o}~@~i}J~a}]}j}|}}}|A~v}^}||ٛ||~W}h}f}u}I~D~[~d~P~V~F~r}B~j~\}Z}e}o|~;~I~o}k}|8~\~e~j}I~P}j}_}P}G}7}e|{߂{]||}T}l}Ϟ|^}`}T}||||V}|hf|gf|cc|{{\]|Z[|aa|lj||zy||z|||M~g}}|ٍ}Ǿ~}a~M~ɲ|܁}}W~Q~ͷ[~f~I~i}|fg|df|`a|cd|de|jk|de|^`|[]|df|jm|hj|hj|np|qs|rs|or|or|rt|wx|ps|su|wy|z{|~||}||}ӌ}Raڙ}}g~c~~~s~~bKtR~}^~IULKg~b~T~}||||||||||||||||||ƽ}}}Bk~Z~Z~X~^~R~´^~Q~N~O~o~n~¹d~n~N}FIQQLCZVPHCCGIGKIBFG~CD~DDBE}D~D~Cx?w~z~y?u~x~FGCDSSQMGDD}@@A?BDHED}?BACGECFFFCE|~KFGKMPQQNW\UTVUgUZ]PIBC~B~B~BDDy@~C{A{@CDCBDD|AEM|~~}B|D}~[~k~|Di\Xj`XYOPTTLGTJIFµNLLAC??:==7B@EE;;6>øQLT~|}l}ή|||||_}^~ܵ}}ڌ}J~߈~И}P~{~m}|{{{{O~`~=`~d~@>j~U~y}q}M}݀|?}o|B}i}I~w|e|K}I~t~~~~W~}Q}N{y8{F{A|?|=|8|/}A|7|O|3}5}5}6}:}R{azRztz{zR{E{L{^z;{4|6|J|3}_|1}K|@|-}9}9}9}R|+}/}-}(}P|0}1}7}D}C};}?};}<}>}1}<}:}L}A}>}<}7}{su|||{zz{{|۵|U~q~z?HX\»\bbOZXCGHNi~F~G~U~N~p~?p~Dh}{{{}|{{zz|{{{{{{{{{{{ſ{{|{{~|tt|{{{{{{{{{l}|f|{{{{{{{{k|z||Y}f}Ƚl}w}y@~D~D~F~x@~x@~F~D~E~U~S~N~Z~[~}Q~|~n~M~T~H~ܧ|{{{aa|dd|ef|{{{{{{dc|fe|{dc|gg|lk|mm|kk|mm|qq|qr|rs|st|rs|mn|qr|pp|{{fh|{ef|hi|no|hj||pq||||~|vw|xy|uw|op|||op|jk||vw|rt|tv|y{|ln||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||vx}wy}vx}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~Ġʦ׭۰ޱڲרǟyyvt~s~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}~vw~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}z|}xz}wy}x{}{}}ux}qt}wy}}}}}|~}{}}y{}y{}xz}xz}vx}su}op}mo}||||||||||||||}º}}||||||||||||||||uy|sw|sw|||||||j}h}k}t}ĵ|||||d}e}|||||k}i}~[}^}դ||~|{}|Y}ޑ|ܠ|s}\}K}L}K}O}h}I}V}:}d||S}T}W}H}D}Q}G}?}?}?}E}E}?}B}M}9}i|l|e|O|K|{n||{v|F}d}O}E}D}t|z|O}h}B~E~I~ȹa}µb}y<~L~F~s}G~E~H~|=~G~E~k}D~´b}N}[}l}yE}U}h}X}x<~]~`}~@~N~N~¹Y~U~P~u}Y~Ļ`~Z~U~e~T~ȾX~N~v}c~Q~K~G~e~D~8r~ɿf}]|b}A~A~F~A~i}5e~?~O~M~~=~E~ȿk}O}|A}d}K~o}Ç|{Z|փ|i}\}a}j}Y}g}o}i}]}a}F}x|ee|bb|__|{{{bc|ee|yw|ut|zy||}{|||wv|h}n}a~}}m}x}ؔ}j}H~U~Q~п||``|`a|]^|bd|`a|bc|`a|ac|]`|^`|eg|^`|jl|ik|cf|_a|Z\|`b|il|~|vx|wy|yz|oq|rt|vx|||||||||||v}}}Q~O~}ց}ԁ}~H~I~N~_~T~X~L~}|||||||||||||||||||W~g~Z~T~Y~W~X~f~k~z~ELNGFJHJIKJGIEHFDJLTRXJ{Ar~r~x~yA{BEIQG{~{~zA{~{~w>v~~JEp~JJG|Ax?zAIDGHFAAAFUKJD~?B}?ADECHGHLKFDIGISRVQMOQQVTaļ^Zº[fMDD~B|B|B|A}AH}A~Aw?}~w>}AD}?CCC{>{>~@GEs~~HLHKLQUZI~BLKSTƻTOOǹM̾MQX˽OOĹNKR\RPQͽLƺODʻI?8=79@Vm~||ݡ|Z}C~G~D~~@~z}b~~[~}~c~n}?~a~}k}|||k}r~i~{~o~g~@i~K~_}U}E}L}V}D}[}O}T}I}a}z}Y~}x~݂~M~||X{K|gzJzM{C|=|4|C|7|O{9|5}0}/}6}8}9|K{Q{pz<{N{F{Pznz@{Z{_{J|*}K|L|+}H|/}<};}B|X{9|0}=|B|2},}*}A}F}6}/}>}?}4}H}4}=}B}K};}5}C}`||uv||~||{}{{}~|||ԉ}W~Z~¼a~NTM]XS=y5_\J<ǽS~HKǽT~>~L~BV~W~~=Ǿ^~l|{{yy{{{z{y{z{{{{{pp||~|{||{{{{{{{{yx|{{{{|||rs|{{}{{{{{{|||||}ҵ||ؾ|X}p}҅}}V~H~F~M~M~I~W~i~U~½z}f~q~~|~}W~_~n}{{{{kl|{{{{{{{{{ff|{{gh|wy|jk|cd|ab|cd|hi|{{{{{de|wx|z{||{{{hh|hh|vw|kl|yz|{{jk|{qr|xy|kl|cd|~|mn|tv||y{|~||||}~|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||wx}}~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}˺}μ}θ}}}~~{}}}Ϊݳٰ߱ݥ٥ެ˥~yw~~~~~~~~~~˺~ʶ~ڻ~ؿ~~~º~~~~~~~~~~~~~~~~~y~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~}~}}~}z|}xy}wz}|~}vx}uw}}}}}}|}}y{}wy}wy}xz}wy}uw}rt}np}|oq}np}oq}oq}oq}qr}oq}||||||}Đ}ߖ}¹}u}|||||||||||||||sw|x{|{||||||˸||d}h}r}|}~|{||ѫ|a}|©|||||`}b}^}]}T}||̎|ѐ|P}^}Q}s}g}v}g}e}D}E}O}E}6}O|d|L}|p|c|c|Y|e|j|g|U|c|`|d|Z|f|}8}}8}Y|Z|_|I|R|~?}]}Z}M}F}Q}]}Y}=~z:~I~E~y}Z}w=~~>~A~r}{=~@~j}@~@~ȹ_}W}||f}l}I}f|K}R}K}d|e}J}Ͽb}X}T}`}I~S~w}{=~O~R~<~W}]~g~O~ù[~N~K~s}Z~M~L~ɿ]~U~J~m~b~^~X~G~ƹW}E~U~A~A~i~V~86Q~m}?~r}M~W~R~}|ʾX~K~J~B~{{{X||P}q|Q|g||Y}l}_}d}|a|q||ab|^^|{{cd|bc|rq|hg|kk|uu|xw|}}|vv|kk||̦|f~}}v}}~t}|yJ~}g~|~|bb|Z\|XZ|[\|\^|]_|Z\|[]|Y[|UW|VY|X[|\]|fg|`b|\^|fg|_`|mm|qq|fh|hj|mp|mo|ik|il|ln|vx|||||||||||v}Y~~L~؃}x}u}}}S~W~Z~b~X~~K~˄}p}||||||||{o}}|||||}U~c~B`~[~R~T~m~u~u~u~z~|?|~y~y~v~u~s~u~{~{C|D|DFE~D~CDDu~{@t~z~q~l~o~x~ED~CEDz?{~|~x~v~x~y~m~l~Bn~Źh~GFBx>x~~AEIFE@@ABDBFIGCIIHHLLIGLNLODIRRURPNMLMPQNOSLEIKDA~A|Az?{?Az>F{?z~}~{~|@CG{>EHOKB}=AC>@?>?>?A~<=QLKS6y/S}3|2}0[W7z.2}.}-060~/6V1THTѽDRVUWz}j}j}m}p}q}t}s}w}E~U~^~t~P~z:~j}a}i}z}_}t}ׁ}N~]~k~g~p~G~p}o}K}>}A}f}:~C}\}p}h}Z}~N}x|A~z~s~t~r~W~}{ێ{Szzbz|Ez?{1|@|1|A|G{7{8|+}.}.}4}S|D|}+}J|5|Zzkz8{HzYz8{F{_{*}>|W{>|-}2|/}:}/}Z{xzC{+}5|8|.}2}.}4}B};}*}5}'}4}A}3}5}F}B}9}:}=}H}||vw|{{{|{{{||Q~FQ;[~aUXT;p~J<^~S~`~;:`~̾T~C~G~e}D~}@N}{zzzst{xy{{yv{zzzz{{{{{||{{{{y|{z{{{uu|{{~~||{{{}~{{{{{|yy|{{|}|vx|pp|{{oo|{||Ʒ|p}s}۵|h}p}t}u}}}}}o}~K~]~øg}}}~}}}}m}a}{{{{{{{{{{{{{{fg|{{ij|cd|{pq|xz|gh|{{{ef|jk|rs||}|tt|jj|{ij|no|xy|uw|pr|bc|{np|fh|{ij|{hi||no|{|hi|lm||||||||||vw|z{||~|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||{|}||||~}}}}}}}}}}}}}}}}}}}}}}}}}}}±}ϻ}л}}}}}¥~~~~ź~~~~~~uxx|~~ØЦۭޯܮޭަѠ̟Şz{zvt~~~~~~~~~~~ƹ~~ǰ~Ѱ~׸~θ~~~~~~~~~~~~~~~~~~y~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|~}vw}tv}y{}}}}~}|~}y{}xy}xz}{}}y{}xz}wy}uv}pr}||pr}nq}pr}pr}oq}||||||}}|S~|Q~}{}|||||||||||||y}|}|ux|~|||||Ҿ|f}e}e}|ۻ|h}Ŵ|||||||||||||Ъ|ѥ||Y}N}<}||`}a}?}E}P}e}k}[}>}I}9}<}c|^|b|m|S}v|C|s{D|B|{s{R|U|N|A|~>|S|^|E|R|X|X|[|O|k|H}Z}>}g|Z|E}V}|<~F~|9~x8~C~Ƿ_}ǹc}F~A~C~T}o}@~R}T}Ͽc}M}N}\}`}h|I}F}N}Z|`|n|L~k}|a}h}H}ɿ{P~x}M~E~G~;~f~t~ƻZ~R~R~d~A~?~J~?~º\~z<~e~Y~z6:K~A~ƽa}|;~ĻW~?~E~õJ~4673K~n}G~ɻR~V~D~T~P~Y~N~M~d|{{}{z{m|s{u{֗zr{^|Z}`}W}|S|i|^|Ƿ{{{{{hh|ef|cd|ij|hi|ef|oo|mm|qr|rs|~}|u}}}q}g}j}||l}G~T~n}}|Ҳ||a}|\]|XZ|UW|SV|SU|QS|\]|ab|ac|np|_a|`b|km|rt|mo|xz|ps|rt|rt|ps|km|hj|jl|kn|tv|x|||~||||||||؈}R~}ׂ}}~}~}Ԁ}}}P~V~O~R~X~߉}x}|||||||l}P~}T~}O~O~X~j~n~m~o~t~s~z~~~FLCCEGGGAs~z<z~y~z~}~~~zA|B~~BDDt~L~x}}T~}~t~n~p~r~zABTKz?v~r~w~w~t~t~u~o~t~x~y~o~{@}A}?~?CAB@A@AACBFCEFKMJHIIKLKKJIDMQ¶YYUMIFIIIHHEFCCEDJFCAA{>z?|?@{?{>{?y~{~z~??OfTIACBFEJKGIGFIȽVPŷNQDAH˾QͻHʸG>͸EͷC͹D׾DLNKкD?ʷGԿF=KWQORʴAQGdzCSQXJA~~<~v}@~M~ľg~Z~e~c~q}i}|;~H~}x}o}I~v}{}a~O~c~a~P~{~=~d}a}W}K}:S~ƼW~L~F~S~i~n~l~L~Q~~G~o~n~m~X~e~K|nzyقyKzrz<{G{J|B|O|1|I{6|C|I|O|>|K{9|L|M|+}R{Zz~zW{M{bzkzI|+}[{+|D|E|=|/}:}.}?{K{N|<|B|8|G|5}0}8}=}@}/},}6|2}4}$}'}2}=}B}5}4}:}~{zz{|{z{{{{{||w|[}I~[~E^VHUC?@E<:F8BI_~a}z9~H~Q~M~W}@}{|zzuu{z~{{{{{{{{{{no|~||rt|{{{{{{{{ww||{zz|||ut|{ww{zz{{{{}{{}~{{{{{{{{{{||mm|{q|||||ˤ|b}q}v}x}{}d}\}a}v}}y}}k}||{{{{{{{{{{{{{{de|{{{{{{{{{{{{tu|pp|tu||}|}~|xy||~|ww|op|su|lm|ab|{{|}~|pq|mn|qr|ef|y{|wy|wx||z|||~|y||tv|rt|oq|lo|ps|or|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||}~}~}}}}}}}}}}}}}}}}}}}}}}Ȱ}̶}³}¶}ǻ}}}t~|~~ġ~Ҩ~Ĥ~~~ǥ~߷~ܾ~~~~~~~~~kq{ЕØĖėđ{suut~~~~~~~~Ѿ~׿~κ~ó~~~~ʩ~֪~ӯ~Ʈ~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~}{}}~}}}}~}{}}y{}z|}{}}z|}y{}vx}su}rt}qt}ps}or}ps}pr}||||||||}}ך}}S~ԓ}v}|||||||||||||||vz|||||||e}|f}l}f}b}||̲|||||||||||||||c}T};}l|[}e}|}h}X}M}C}B}_}N}E}J}=}>}=}8}a|?}L}o|{{e{s{F|r{p{}H|E|l{T{f{P|I||{|?|K|M|V|\|e|m|?}V|l|S|]|E}I~E~G~x:~N~p}y>~m}Q}j}³`}p}m}z||`}~D}X|H}N}i{J}N}ד{Ȼo|{E}O}x}Y}j}l}Q}t|A}K}x}|~r}e~d~a^VL~O~F~\}F~b~P~E~v}{C~I~?:}8B~F~N~h~F~D~4:99S~R~]~]~\~^~^~O~}|>~h}x}ú{{{x{{l|o{zzzz{Z}\}?}}9}{{s{z{{{ab|cd|{ee|gg|jk|jj|jj|kk|ll|mm|hi|ij|tt||||||no||þ||m}o}t}o}|}Ѱ|de|]_|\]|Z\|Z[|]`|\_|Z\|bd|df|ef|lm|ik|fh|hk|jm|np|oq|oq|rt|su|ln|gi|jm|mp|jm|or|ru|uw|}|||م}}K~J~}}x}y}Ն}م}Ԃ}Ԁ}փ}܂}}J~}J~}W~M~R~}}}|v}r~f~h~`~i~b~º]~C~J~p~o~c~e~FFGG~z~E~JKCk~z~z~j~p9ƮZ~K~T~`~w~}AJHDI~BT~v}O~}?s~k~u~o~E|~IV@s~z?{~|~|~y~y~w={>o~q~q~p~q~AACAA@?A~?B~?~?DCCBEFFFJHJKFJLNLHV`Ÿ[VPRSRMJMGGFJ?BEE@AA~?}?{=z=z=}>A|>AC@EFDCCEH@AED@>=AC=¯DD˽O_ŸNWR;ĴH|1SND°CóJ8:7İAʷEƳAKİBFж>RNOD62̶>652<A>>@JEc~_~N~C~h}]}þ^}i}E~}}H~z~X~C~F~O~?~K~t}[}^}P}b}T}~<~>~[~B~M~X~o~DM~o~b~v~x~ց~X~\~i}ڀ}k|Pz}ykyyWz@{.|9|@|K|,}2{0|G|)}E|={F{2|M|+}P|\{dzN{=|L{Wzdz7|K|E{U{D|:|1|-}1}'}J|E|C|&}:|:|1}.}A|1}C}=})}=|G|-}2}@|)};}G}A}<}2}ך{z{xzux{{{{{{{{|_}S}I~p}S~8a_PQD:DYɽTLU@y4E9A~>~W~b}g}Y}]}V}{zzwyzzzz}z{{{{{{{{{{}{{{{{{{{|{{{~|||ut|{{{{{}{|{rr{ss{qr{{{{{{tt|rr|{{{|{{{{{{{{{{{y||||||||\}Y}Y}z}y}Ȇ}}ߐ}}}k}q}}žt}i}W}||{{{{{{{{{{{{{{{{ff||||qq|ss|qq|qq|jk|ef|mm|ii|cc|bb|gg|~~|ll|{{{{mn|hi|ij|qr|st|yz|tu|wy||pr|qr|y{||}|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||{}}{}}}}}}}}}}}}}}}}}}}}}}}}ȷ}ƹ}}}ƽ}}}}q~w~y~w~w~~~~~ϻ~Һ~׸~յ~չ~ؽ~پ~~~~}ilu}ztvtrlj~mtut~~~~׽~ҷ~Ϋ~ɬ~ϲ~ҳ~Ĭ~~~~Ƥ~ߦ~ޫ~̩~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|~}z|}{}}~}~}|~}}~}z{}xz}vx}wy}y{}y{}xz}tv}vx}vx}vx}vy}sv}pr}||||||||~}}}}ה}}}}}q}||||||||||||||||o}u}r}||||||l}g}|||ֹ||ض|Ү||Ū||޲|ѭ||\}~[}~Y}||ܫ|c}W}Z}r}[}U}}g}g}O}9}a}k}L}X}^}Y}:}:}]|b|d|e|]|p{e{a{y{u{u{{{{;|_{r{m{l{o{d{j{G|X|m|G};}]|U|O|@|E|O|J}A~j}o}D~õS~o}f}k}|F}K}{|P}d}пo|^|`|]|̊{Ǽp||Źv{|C}b|v{ءzR|N|d}_~G~V}{>}{{`}A}U}W~?~g~G~LL?p~ƿ[~e}E}B~¸X~i}}G}߄|z=~?EMX~P~J~s~Z~`~a~e~6a~{3}3w3W~`~R~\~5ùW~N~n~X~r}{~{{{{}|{zz~zvzp{i}^}B}g|ր{Â{ʜ{{{{{{bb|{{{bc|hh|jj|ff|cc|cc|{`a|dd|{_`|{st||mm||̭|e}d}u}k}x}p}Ձ}}a}lm|pq|gi|jl|fi|fi|fh|df|fg|bd|eg|ln|z||mo|rt|pr||z{||||ҷ||l}i}i}t}}}O~}M~}~G~e~f~ºf~H~a}M~]~U~X~O~t}n}y}}}}Ձ}O~M~}Ň}a~^~M~۔}ԇ}}}s}n}Q~}}DFGVNFGFCRI~~}~EFƽj~n~k~}ÿ}N^{D\TQOQz~o~j~s~o~v~q~n~n~v~DAx~{={>y=z>y>{?x>}?@?w<s~o~}?@}?As~u~y=y=y=AAD}@@ABGC~?>??BBGIKMMEEOU^øUTNPLPNM¶RLƻTUȹNIоKTVOOMKFBBAɻPĸPWJA͹GHCPTJx/B@97O9;4<9y19ŻT>NF}5}B}Z|{ٻz{{|{{{{~|{|t}}C?77SKGƽX[FXȻRY9ƹM~H}79~3;H~@~G~P~q}\}>~|C}zzrrzzzzƼz{x{{{{{{{{{}}{{{{{{{{{{{{{{|{{{{{ÿ{{{{po{{zz{{|~{|~{{{{{{{{{}~{pq{{{{{{{{{{{{qr|{pq|~|||~|ګ|||j}h}e}h}n}p}`}n}}}}}|}a}_}||t|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{tv|su|np|sv|xz|xz|vy|z||y||x{|~|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||{}}}}}}}}}}}}}}}}}}}}}}}}}}Ĺ}ú}}¾}̾}}}}}o~{~~~߫~g`~֯~Ү~ݶ~~ܹ~~~~krwz{|w{yuhkʊ{kmjff~~~ة~ת~ͧ~ϥ~~~~~ʛ~~~~~~~~~Ӎ~љ~~~~~~~}~~~~~~}~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}z|}wy}y{}y{}wz}z|}}}{}}wx}vx}x{}y{}|~}{}}wz}su}vx}vx}uw}tv}su}|rt}||||||||||}}|}p}|v}r}||||||||||||m}~j}||||||||ۼ||ϸ|Զ||d}|Ǭ|e}_}|޲|{_}Ş|R}a}a}Z}J}L}b}|}t}}}H~=~O}e}l}h}c}E}J}`}l}`}]}X}>}5}B}V|F|M|S|{w{e{d{V{Z{c{n{g{Q{R{e{f{m{U{`{w{o{N|b|Z|h|H|H|w{=|N|o|f|?}L}n}H~J}|E}[}ƶj|P}[}r|L}v|Y|]|m{m{{yF|y|xB}t|~V{\{ezu|]~K~ۢ{J}o|N}E}ɻY}?~H~Z~X~R~F~H~]}@~@~C~>~I~f}A~ĹR~M@W~M~R~K~x:~m~<=b~9>{6{3|5d~c~U~X~e~=F~Pl~w}¿{zzu{s{{W}|D}k{z~{zzT}b}G}k|{J|t{{{{{{{cd|bc|{_`|ab|{{{{{{``|{{{{__|{ba|rr|wu|||c}l}l}p}ĺu}d}lm|mn|uv|ln|eg|df|ab|cd|`a|fh|or|wy|p}҈}}Ȅ}ͅ}ʀ}̓}ľ}Ž}}}ƽ}ņ}}R~R~R~\~_~\~X~[~T~Z~Y~}q}a}^}^}f}|l}k}}}K~y}`~P~y~U~Y~q~S|Bt~\~Z~X~ĸ_~l~e~k~|~v~h~ƻ_~`~zABy~{A~{~~}@}~s~v~~~{@E~QFNFQISQHIn~i~{~{~t~|>{<r~j~j~j~t~s~x<x;u~q~q~q~w~x~v~w~w;j~P~DDx~w<i~h~^~t~t~v~{>B{?|>|>BB~?DBBDEHGDISWUǼRY|0x2[ULMOŶJͽMOPT]NQNGORPRRSOMTSXPRķLVRLTRU.}.>9ZUVE;O{/5SQX9BIQ749P·J_A82\_ƻO_~V~[~S}H~M~X~<~_~I>ɺIŸFƹI\ŹMR>:N=c~o~h~l~s~q~[~R~O~V~I~D~>~F~y8~>~M~S~?~r~j~ýc~R~W~f~ƿ_~`~U~B~d~~H~Pt~U~}L~¾s~}ň}ϫ|{͟zezPzvy}yՂy{y\yyyPzOz^{B|7|?|<|G|1|G{_{TzQ{;|,}J|(}~&}?|B|+},}nz\zlzM{4|-}2}T|,}5}A|0|=}I|/}O|6})}8|N|Q|,}8};}6}W|T|,})}/}5}3}:}?}{|¼o|{Ȋ{{{{{||{{|||}J25=ĺS~K?~ĹS~ĺTT[WC2^~ȹG~^~9S~JEǻW~L~=~l}Z}}=~u}{C}z{}zz{zzz{{{{{{{{{{{{{{{{{{{{{{}||{{{{{{{{{{{xw{zzzzzzrs{ss{z{{zsr{xx{oo{{||{uv{{{tw{xz{{zxy{{{{{{{{{{{v||||||||ݰ|g}i}y}}z}߂}}|}m}V}R}z|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{kn|mp|qt|vx|ux|ux|wz|y{|vy|xz|wz|{~||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||y{}|~}~}}}}}}}}}}}}}}}}}}}}}}}}}}ͻ}ϼ}ϻ}ͷ}}}j~x~~~~~¾~~Ǖ~ӗ~~~~~~_cozuɁσՄρwu΁{d{~~ywqqng]~~ߥ~ɝ~ޥ~أ~ڡ~×~~Ι~Κ~•~~~~ʘ~~~~~~~~~~~t~q~|~~~~~t~w~~{~v~s~w~~~w~r~}}}}}~l~}}}}}}}}}}}}}}}}}}}}}}}}}}}}|~}}}}{~}w{}vy}vx}vx}vw}z|}y{}y{}xz}z|}{}}}}}}~}}}|~}uw}su}wy}uw}tv}su}rt}||||||||||||v}|}|}{}||||||m}u}x}y}}||||||||||||||d}||_}h}Ĺt}z}r}m}i}e}\}Z}S}N}A}O}@}=}P}o}c}]}>~e}X}?~a}q}k}[}>}L}D}[}Q}B}:}g|P|=|9|~6|[|T|yze{X{N{V{V{Z{`{\{}Q{zY{zm{m{s{h{a|k|]|d{`{^{k{O|º`|f|X|j|n|>}a}V}o}|\}ȼs|j|l{ְzǾ{ٔ{x|n|v{w{Ɖ{V|՜zz|zyyyr|<~d~F}_|S}]}i}P}D~[~g~~:[~P~9~K}|}Ľh}S}w}V}J~H~c~<O~ɾZ~L~ĺV~O~]~=~T~T~]~;o~G~7Z~=~78K~I~v~EON~|¥{i|S~j~f|{w|U}G}G}}|H|_{p|J}Ԇ|[|L|y{֪z{{h|{{{{_a|{{{{{{{{{{{{{{{a_|ba|ge|__|ba|kj|pp|ww|||Ҳ|h}e}|bc|df|df|ά||]`|gi||ݸ|f}q}q}Ҭ||m}l}ݳ|ղ|||f}s}}}}}~F~ʆ}ý}…}}M~}L~`~y~T~k}n}j}i}s}~S~a~HMGCAq~i~g~`~\~Ƹ\~a~c~d~f~i~c~b~h~k~i~i~f~c~g~g~o~s~q~o~p~E}ACDOJHDv~s~ǽh~`~o~~~Dt~W~}K~b~l~q~y~y>s~s~x~o~t~s~l~l~t~w~v~r~u~v~i~g~d~g~c~l~e~g~l~A~?~?GIQQCGM{~z~B|@t~C@y~t~BB>BHRRa^\8721TQ~-s(P2@97;y/{/R־DVw,OMERNNNѾOLǶINPx,v*LLz){,0ĴEu+0{+x-9~.y)L2:P9AE~P~^~D~I~N~d~ǺP~e~ɼR~D~B~A~L~C~M~]~^~Q~G~I~K~ĸT~ƻV~ɽV~Z~_~:~F~;~H~O~[~J~K~J~E~D~N~O~ĸR~U~¶P~G~t}z;~n}]}C~M~żW~O~L~^~Z~i~PPk~b~{B~z}r}D~q~JU~~x~z~|~c~ԓ}c}|{{{yz{zʪz|^z|y~Lzyyy`ydyly߃yI{\{F{9|2|6|F{Mz_zfyN{=|,}C|<|>|C|O|7}-}0|^{A{S{]{O|1}4};}5}5|?|=}=|A|*}(}>}6}W|Z|5}6}:}2}F|[|3}0}'}2}<}9}s|]|C}E}`|n|y|ݭ{Χ||{{||||v}x~y5:]~̿S|6~g}F~k~KZɿXeg_@´L~z4;y6a~H~Q~H~};~{:~{:~>~F~W}zz|{{z{{{{{{{{{{{{{{{{{{{{{{{{{{{ÿ{{{{{{{{{{{{{qp{{{{}{{{{{{zzzqq{tu{{ss{~~{zz{wu{{|{{{{{{x{{vx{{wy{{{{qq|||||{|||zy|{{{D}ߪ|f}y}e}P}]}{{{{{{{{{{{{{{{{{{{{{{{{qs|qs|qs|rt|tv|tv|uw|ru|uw||~|||||~|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||xz}}}~}}}}}}}}}}}}}}}}}}}}}}}ù}º}}}Ű}ȯ}}}ֽ}ܿ}}}}}}o~r~u~{~~~~Ó~ޠ~~\gsssrqlekqka|ւ̅ʁ{rpqni``b`\]~~~YU[\ZW~٘~‰~ć~ĉ~~ʑ~~~~Җ~Γ~ӓ~ݜ~ޙ~ޚ~~ݖ~ڕ~ٖ~ϑ~Β~՘~ט~ٚ~՘~ڜ~՘~ט~Г~՗~ؚ~ט~ט~ΐ~Ɍ~~y~j~}}}}}}}}}|}}|~}|||}|}}}}}}}}{~}}}z|}~}}}}}|yz}}}}}}}}}||~}}||~}}|}|||y}|}y}||~}}}}}z}u}p}q}|p}t}n}r}o}s}|n}|||||||||||ӵ|k}o}g}j}s}v}Ⱦt}w}p}i}f}k}^}i}p}h}G}D}7}7}7}W|{|y}^}^}8~m}Y}a}]}M}E}7}E}<}9}@}<}a|\|@|V{u{X{M{{w{izT{I{qz{M{zS{|{l{`{N{l{}{x{N|{V|x|r|E|m{j{o{a|b|I}S}E}Q}v|F}H}k|{G}P}ɻr|y{p{z{{{züzzzzzzzzzziz҅zzz|A}S}J~|d}i}?~E}_}S~G~h~ǿ[iBu}c}ɿX}g}i}D~F~ƼW~?~\~B@e~z;~G~~}D~W~c~@~O}c~]~c~P~b~`~O~=~k}T~ûY~S~@Q~փ|ÿ{u}·PFP~|Y|O}h}f}y|v{r{q{s|~|P|H|j{?}|O|؟{G|{dd|{{{{{{{{{{{{{{{{{{{{{^]|`_|bb|cb|{{{{gh|{{||gg|{jl|̤||Ѩ|||]}||g}y}}E~J~U~R~`~c~}N~ψ}e~]~_~a~\~a~^~IZ~h~l~g~k~i~L~N~P~W~ĺ`~}>@z<|=q~ɽb~W~U~e~h~Y~]~f~e~e~j~j~j~i~f~h~s~q~o~m~h~e~e~a~e~e~c~O~i~g~e~b~e~^~l~a~^~Ƽc~i~q~v~e~Q~b~u~żd~^~\~~~p~u~n~p~m~m~r~r~p~z=C{>u~|~y~AIHJPUVVW\ļ]adfcebdfddaaddeccejgfa^^QV·O˿QLINg~}3W~LVN>VLDC>71USMIRκDG̹FMLs+KJGTS0Jr&u(JAͽKNv-VMZ~K~>F:\~E~[~[~V~ȻT~[~O~N~b~a~g~Cb~W~b~ǿ\~K~A~@`~b~E~N~N~I~L~K~P~ʿX~ƼW~[~=:w~~<{:e~ǽV~_~Q~_~|8`~ʿX~ǺT~H~D~e}h}~D~L~^~Y~[~b~G~C~L~d~M~R~W~e}d}c}f}Q~y~g~ýj~o~\}X}||{{}~{z{{z{{{{}{{{wzZzJz{ywyqyxޛxhyOza{]zH{`z<{Dz}AzL{Rz9{8|,}8|>|F|}%}I|-};})}3|S{P{_{?|5}0}9}.}A|I|4}.}<|H|*}B}5}@|M|*}/}1}@};}3}J|(}.}4}1}6}|˘|m}:~[}M}^|x|{|a|{|||||}GS|8Y}4}8}C~?~>~9ƻTRge~LO~Q}2~>w7_~A~_}@~>~m}x;~G~G~D~|l{׫{m|{{{{{{{{{{{{{{{{{{{{{{{{½{ÿ{þ{{{{}{¹{Ż{{{{{{{{{{{}{|{{{{{{{{{{{{{{{{{|{|{~|{{{{{z{{{{ts|{{{{{{{{{{Ȳ{{{{{|[}Y}|t|{{{{{{{{{{{{{{{{{{{{{{no|lm|lm|np|tv|tv|st|st|tu|{}|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||}}}}}}}}}}}}}}}}}}}}}}}}}ķ}Ų}}}}ï}ȯ}ë}}}ɲ}Ѹ}ܽ}}}}}}}n~m~n~q~y~~~~ם~~\^_cbff]c~~qytuoljie_ZY[ZZX~~XUUUVVV~TUUSS}Q~~~~~ߗ~~ߗ~֓~ؔ~ג~ʌ~Ɍ~~u~w~x~s~y~~~~}~}~{~~~z~y~v~u~p~}׸}}}}}}}}z}||w}|||||}|}}}|}}}}}}}~}}{}||||}}}}z}~}||}{}x}v}}o}}}q}||||||t}y}|}~}}y}{}{}}z}z}z}|}s}q}p}m}|ɻ||||||ͺ|پ||i}r}q}¼x}Ļw}q}j}q}ķt}r}ɽt}w}n}f}f}k}p}a}m}{}K}N}A}<}<}[|^|O}p}g}E}7~<~`}q}Y}G}C}5}O}J}B}w|g|a|M|=|s{<|^{W{i{s{wzQ{rzzP{a{t{f{u{m{~N{_{}{k{~{n|b|S}j|V|L|X|T|d|E}G}\}g}X}e}e}[}S}\|{ؗ{r{ء{}n{j{Ľzyy}zy{w{ʡzzzzƽzK|Y|C|{W}¶X}S}a}p}f}ƻ^|;~c}o}|9~?CA~h}Z~J~v}C~M~J~A~G~M~o~8Z~m}P}B~M~ƼZ~R~F~U~y~m~i~b~_~Y~Q~h~_~F~X}C}d}}t}^}B~z9~:c~X}L}Q}b}O}J}>}Z||zzƘze{X|Z}T}D}I}{{qr|pq|mm|_`|{{{{{{{{{{{{{{{{{{gh|{{{{{{{{{{{{ou|pw|{|{{rs|||i}ȉ}Q~[~a~a~\~]~g~]~Q~r}z}}}Â}N~g~@N~W~_~a~a~b~ǿi~n~v~BCB{>{>~>Cp~m~s~j~j~p~h~o~f~n~s~r~s~s~s~q~q~r~w~w~u~t~p~s~z~y~y~y;r~u~z={=ACCFEHH}AB{~x~s~~~~CBALNLIMIJNOPSQSWYVXWTTWWTUTRONLLLFFKPNNSUXƼ[øZúYYXŽ\[ac_b]L:<:CA=<<==BlBE~;gc~HdG5Z~:?K~e~^~B:<VJKоH̻I>òE7;H~I~U~K~O~źP~Z~W~÷Q~ƺR~H~S~D~C~N~T~S~X~h~=FJLHL]fecXK]PEL}~|?p~X~k~Y~L~P~\~ƽ[~ɿX~o~n~V~S~Z~Gy~L~U~e~~~`~I~k~Y~]~m}S}u}o}n}h}˿e}Y}|M}k}|¿|П|||{{|||||{{{{hi|st|hi|{kl{zst{uw{{qs|{zSzxIziyjyydyx}Zyuy_zqzyfzXz}?zEzB{_zE{<|K|V{5|1}G|'}M|-}.}2|D{I{4|C|+}9}6}F|B|\{9}6|8|E|,}4}2}G|V|P|)}1}g|8}7}=}:}/}7};}:~`~|}M~ͿQ~ôJ~J~h}t|T~ͻM~j}|\}a}]}b}ā}X~r~S?A~e}T~w2\~@~a~>N~8~9~8~8~D}:}6ľO}L~E~O~H~x7~{9~x<~B~B~A~Y}r|S}׊|{{{{{{{{{{{{{{{{{{{{{{{{{{zvu{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{y|vt|{xv||{}|{{{|{{{{yw|us||{{{{{{{{{{{{{{vw|{{{{s|e}ք}{{{{{{{{{{{{{{{{{{{{{{{{{{no|no|rs|tu|vx|z}||~|}|xz|wy|{}||}|{}||w{|mq|wy|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||}}~~}}}}}}}}}}}}}}}}}}}}}}}}}¯}}ŭ}«}}}ɵ}ʹ}̳}̵}̹}Ӿ}}}}}}}~j~p~t~t~w~~~֢~՟~Ԟ~~~Z~Ջ~lZezpi[^\\~W}S~W~~~~~~~~~~~ؗ~ҕ~Ȏ~ŋ~Nj~Ɗ~~~{~~~}~~~}~~†~~~~…~Æ~É~†~ň~͋~~t~k~g~}t~~~~~ȇ~Ć~|~}q~`~x}|}z}}}}}}}}}}|||||||||qr}z|}~}~}}z}|||||||||||||y}}}}}}n}t}{}u}}}}|}||||||||||||||i}y}y}n}||||d}p}p}r}o}j}k}g}j}m}o}Ƽu}úu}u}x}y}v}s}i}g}i}e}_}|a}|p}¾e}V}m}X}C}M}5}9}M}@~X}T}<~i}\}Q}I}D}@}V}^}e}Y}>}B}n|R|E|~A|q{[{Z{{q{w{[{]{Q{}K{v{[{zW{zQ{}H{zd{J|z|F}x|R|u{U|A}<}Q}B}<}M}E}T}J}Z}V}m|U|¸{z{{{zzzzyzijzzzuzzzzvzڪzzr|Y|r|_|J}Ļ^}a}T}E}R}c~C~`~e~R~Z}s}b~P~b~A~V~P~ɻR~ɺP~]~L_Ci}><l~T~U~ʾ]~j~z~}?m~{}{C~E~}r}s~j}k}O~ƿk~ƿj~g~l~J~ȿj}y}C~{>~R~]}c}5~]}?~E}j{|zzc{n}4~>}L}U}}{{{u{{{{{R}B}G}{{{{{{{{{{{{{}|rw||w{|fj|cg|w{|vy|{~|{~|||||||||p}}՞}_~m~~ILMLIIICDBC@r~n~j~d~c~ƹ]~ɼ^~Z~{Q~R~]~T~X~W~W~p~|?BBCADC}@u~`~v~|~FJJTMCv~A{>z~x~t~i~u~x~z~z~AD~@}A~@GIIHHHJNNOUTRQRQPPQLPPNOPQSRRQRPMFC@A@@DA@?@BCB?CABABGC>?IKMQIKEA?>99?b~:úR5_~>?>9=<8<>@8g~e~8;:;i~}7\~`~|4~5\~f~i~V~Y~L~h~ĺO~ǻR~R~`~W~ȼT~f~~9U~X~Y~d~\~U~ȽQ~|8ɽV~d~`~h~f~V~R~˻P~W~i~z;m~[~Vdf»_mm^Y_ľbU]\Z^f[[@j~W[QB]~b~ɿ\~J~]~R~O~~~_~KZ~}S~׎}|p|s||}}||{{{{{{{{{{{||}~|{{{{{{st||{{pq{pq{{||~{yzSzynyɂyNzUz}KzڃySzQzyޒyyfzMz`z={[zG{:|-}1|5|%}8|:|K|)}6}*}R{Q{>|B|6}4}=}b{[{0|2}-}L|.}5}3}9}Z|j|4}5}4}F}J}@}=}x|q|h}C~F~q~`~V~^~\~>ĸT~?~`~]~S~F~A~^~e~Ⱦ[~c~a~<p~¸XK~ǼL}?~L~E~d~`~f~y1~8~x3~J~M~R~E~`~8}[};~?~G~G~w8~@~H~B~z<~?~J}H~»g}{{{{{{{{{{{{{{{{{{{{{{{{{z{sr{{{{Ŀ{{z{{{|{||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{¾{{{{{{{{{{{{{{{{{{{{{w||҂}X~|{{{{{{{{{{{{{{{{{{{{{{{{{oq|tv|uv|wy|z{|{||}|}|{}|~||||}|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||{}}~}}}}}}}}}}}}}}}}}}}}}}}}¯}}}Ů}}ø}}Һ}ռ}}}}}}}}}}}g~j~l~l~q~w~~~ї~י~ԗ~~~_ޥ~̝~ӣ~~ٱ~`~ٟ~X``~Ŕ~~~~~~~Ɗ~~~RS~~R~~~~~~~Ō~Ȍ~~Å~}~Å~y~~~w~~~~~y~}~y~x~u~x~x~{~~x~x~v~p~g~}ϴ}}ϫ}ٮ}}b~_~ק}}}}}}}|}{}||||y{}|||||||||||xz}||||||||||||||||||||||||y}||||||||||||||ù|p}m}f}ʯ|n}m}ƽ|Ҿ|ܺ|g}s}h}|||e}q}u}r}ýy}}}}}{}y}ľw}r}l}f}g}i}m}s}v}w}j}o}Ӂ}j}h}f}m}R}Q}:}c|`|Z}U}U}U}V}G}M}I}M}C}h}N}@}>}G}=}M|x{d{}M|e{m{]{{̓{{i|{zX{V{ьzi{z|zE{D{n{z{b{~|x|D|c{]{C|=}4}D}G}?}?}T}ûT}O}p}T}b||R||T|z϶zyzzĻzսytzyypz~vzzuzúzϟzɠzzj{i|h|N}|`}s}i}m|y;}?~L~]~b~L~|9~M~H~W~l~b~I~˾T~_~Y~x2]fBv}WH|<|U~f~F}>AZ~D~ºX~L~T}O}f}7~]~~q~J~_~n~m~|A~I~I~P~K~{}J~M~;~b}b|^||ș|p}G~e}>}U|R|]{޾ze|}|t|{{z|]}L}||ɺ{{{ӂ|||p|M}|~|||{jl|z}|||||||||||||y}՚}]~w~~EFEA}?s~a~b~k~r~t~o~n~s~?z;>s~q~d~`~e~b~}>}>z=y~|={<=~=r~p~k~n~q~i~\~[~]~q~r~}BDEFGHHGFF~B~BHIKKGHIHGMJGLKJ|?z~z~{>x~z~ECGJMHEFFEKNMMPNJNMLKIINLHFFEIFCA@>~9=<;;<8=>:=@=g~z5==B;:;:>@@><OLJHEC<9:89:<::c~7:b~e~}5f~i~h~8z4m~N~_~a~_~[~d~i~{8b~Z~X~Z~c~e~ĺV~V~ʿT~_~\~[~õN~;~X~[~C\Hk~]~ʼR~R~ѿQ~S~ɼT~ɽT~ĸR~c~a~ƼV~a~?w~W~K~M~_~x~l~AJALIHIEVs~bNy~HL_~Ѓ}h}`}}|||{||{yz||de|lm|{{be|uw|ln|tw|||{{|~|rs|yz|{||pq||}}|{||{{{{xy{~{{{{{{ij|||{vw{zpzy~yVzy{LzyyeymyhyPzLzMzhz`z/|A|/|7|K|&};|O|.|0|:|B|4}>}J|V|o{F|7}E}I|d{g{M|,}7};}A}7}3}k|Y|w|;}2}7}X}|9~X}^}E}F}d|I}i}Z~m}{8~H~Z~8m~m~p~v~s~?u~u~@w~@t~}}}B~I~`}U}p}H~L~\~I~A~~}M~S~K~I~>~?~A~L~E~C~>~?~E~Q~O~q}=~I~øY~D~{{{{{{{{{{{{{{{{{{{zzwz{{{uv{zqr{||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{û{{ſ{{{{{{{{{{{{{{{{{{|ƿ|M~v~ʚ|{{{{{{{{{{{{{{{{{{{{{{{{rs|su|vw|wy|xy|y{|~|}|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||~}}||}}}}~}{|}}}}}}}}}}}}}}}}}}}}}}}}¯}į}и}ڽ}}}}}}k~p~p~o~n~m~p~u~s~l~t~~~~~~~ː~ښ~~ٚ~њ~~~~~~П~Ǘ~W~ў~ۦ~he~q~o~x~e~}f~n~s~{~y~{~}~x~{~{~}~y~o~r~n~i~j~o~p~q~j~f~e~c~g~e~b~}}۬}ͤ}ͤ}ҥ}٨}զ}}}}}}}}}}}}|||~~}|~~}||||||}}}}}}}||||||||||||||z}v}}}y}p}|||||||o}}i}j}|s}|w}z}|}}}w}l}|}n}l}f}||c}ޱ|ԯ||ά|j}|͸|î|||c}m}j}l}m}g}a}b}v}}O~}}Ј}~}w}}}Ā}҇}u}k}q}z}m}_}e}m}Y}p}u}j}U}L}G}=}P}V}E}C}=};}]|9}K}B}F}^}U}B}@}n|e|y{g{\{d|_{R{h{|{ǎ{З{k|{V{X{V{}{ӆ{c{zzC{t{o{t{L|G|`{}J{Y{W|r|Z}P}B}K}8}>}7}C}p}u|y|x{t{zzzzzzºyyɼyyyz·yyżzuz޺yqzz{|F}K}A~d}K}W}ȾX}V~]~D~U~Q~z}J~K~{}ü`~Dc~X~_~A~HDżYʾV~_}PE:9~D~~SCl~n}N}W~Z~S}ʿ`|U|˾V}W}Y~g~H~B~M~B~;~N~W~i~ĻX~ǾZ~E~<~P}s|k||C~R~i~I~v}D~^~O~{{R}P}a|f|w|_}@~]}G}l|t|?}[}N}S}O}[}B}F}V|{؃|||c}ɿ|||||||||ʍ}Y~Ľg~i~m~k~r~|=x~k~g~l~n~i~m~>|<~?{=q~l~>y;|<w~}=C~=v~o~n~o~o~l~i~i~k~i~l~o~h~m~i~n~r~s~f~i~f~l~t~{?CHIILJHHIJIJIJHKMKLNLLIKCCs~~~\~O~z>HOKGGKMECAA?A>~:~:9v4y5y599~8{6{7z7|9l~|8~:{8{7}8|7|7~9:9<<<<:;<;?@@@?>=@@??B=A@>AEDDFB>?=<>@>@;9:z4}6z4~4~4|3e~w1}2x3a~w3g~x4b~[~b~`~Y~ʽO~a~Y~`~^~U~X~\~U~S~ͿS~V~~:~9<QCMl~ʽQ~ɺP~ȺP~ķL~L~ȻR~L~ŹT~ƽV~S~N~I~J~l~R~K~C~Z~T~U~N~Q~f~RTY|@HPYžcPO~U~l}|{{{{mn{{{{{{{{{{{ik|np|lm|}}|yy|{{mn|op|kl|vv|uu||~|{{{{{{tu|{{{st{z{mm|{{{{{zWzyczvy~ytyWzqzPzjyyZzKz8{vyI{H|*}8|E|@|7|E|;|:|=|V|7}=}S|J|a{/|/}4}b|9|Q|7}~)}*}4};}?}_|o|`|6}A}6}J}>~:~k}{|p|J}~{|L|Z|s|H}W}_}G~j~h~|7p~w~s~z:?JDBp~{~g~Y~[~}K~I~}|@~J~ƹU~F~ȻT~N~F~}V~G~Ⱦn}Ⱦm}}U~A~?~o}k}=~g}T}w<~n}]}Q}·b}N~{{{{έ{{{{tr{zzzzwu{qp{zzzzzzzzzzzzzzzz{zzvw{{{{{{{{{{{{{{{zvs{{{{ws{{ɿ{{{{{{{{{{û{{{{Ľ{{{{{{{{{{ſ{{{{{{z{{{{{{{{{{^}{z}^~{{{{{{{{{{{{{{{{{{mo|{{{{{qr|rt|rt|qt|tw|uw|z|||||||x||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||}}|wz}|}}{|}{|}~}}}}}}}}}}}}}}}}}}}}}}Ů}ֶ}޸}}}k~o~}j~n~q~p~q~q~q~s~v~x~y~y~}~~{~~|~t~m~w~~~n~e~f~e~d~q~~~~~~~~~~~`~n~j~}}ܾ}}}j~g~i~i~m~j~r~w~q~}}}}}}}e~d~b~i~g~e~g~}ȩ}}}}Х}ɞ}ң}ʟ}ɟ}Ȝ}}}}}}}}}}}~z}||||||||||||||||||||y}}Ƽ}}ʈ}Ј}݌}wI~wJ~{J~N~P~O~M~{L~}͈}}}~}}ƿ}щ}։}׆}x}}}҄}Ǿ}}҅}}ߋ}̈́}؊}º}p}j}||߳|||||||||||ճ||e}m}v}g}ݱ|ͨ|Ө||`}l}j}t}d}ش|ʨ|Ь|||||||ơ|դ||V}`}J}J}F}h}j}V}Y}A}:}T}`}F}E}B}G}k|?}N}[}T}U}<}7}o|[|U|n{P{`{X|s{{r{z{W|d|i|{`{Â{u{zv{|{Z|v{p{d|`{{e{W{zzz_{C|F}T}Q}K}<}9}/}:}@}l|K|m|y{p{Ӻzzzyzzzy~zyyyvzxzzz޽yաz˧zzi{ń{P|ľh|>~˼R}O|X|g}µG~H~@~<~y}d}K~G~V~m~öU~Q~=:Y}l~EM]~ȻP~BQR\~M~q~AEGs}x}z<~D~g}E}M}Z}{<}G}y7~}:~E~ĹX~g}>~U~P~_~J~H~\}R}_|E}r}T~\~W~^~N~M~G~Q~}{{{P}R}T}h}A~y}U}C}t|B}5~a}l|M}T}M}=}c|O|~A|{p|J}D}||||n}ʅ}N~X~f~p~k~i~k~m~p~q~w~{=s~??v~p~g~j~g~n~r~p~f~m~o~r~k~f~n~q~l~n~c~b~̾^~X~U~Y~Y~[~ȺZ~]~ʼ\~_~h~d~k~i~k~i~n~u~|={<v~z;e~g~c~d~o~v~u~z~CDGHGFFGGJIHJHEGFGFEDBA>l~l~z7l~j~f~e~e~a~`~v3x4x4y4|69:<@~9y7;;;9<>;;|8{7989:;;<<;9:9:>=>=>A@>=?<;==AEEBIGD?8?:<==>;<:8~69887}3|3{4f~f~e~9@i~_~`~Y~V~Z~\~_~\~\~V~S~X~V~R~Y~Z~e~]~f~s~v~Y~N~M~ʻP~ĶN~M~K~L~G~øP~P~V~n~R~ºZ~EK~g~Kr~O~P~O~M~N~Q~P~i~HEj~r~\~}||{}|tu|{{{x{|{or|{{{{eg|df|{{{{gh|{{{{{{{{oo|{{{{{{{mn||||{bb|ll|{{{{{{{{{{zzdzy}y~y^zpz\z|ykySzYztz;{}4{6|L|,}*}&}D|9|/}c{L|R|6}C}0|^{Y{B|(}(}H|E|B|R|1}3}D}@}Q}H}L}N}[}e}_}b}j}[}Z}J}K}J}\}Ž`}B}Z}M}O}m|\}=~ʽR~U~[~M~N~ĸP~e~;~;?@a~d~žf~_~N~~F~W~l~X~P~ǻY~T~Ƚ\~g~}8f~\~?~o}H~t}]}οe}A~r}c}Z}c}|΀|V}|{|h|T}j}ׇ|׼{e|}`|{zzzz{{zzzzzzzzzzzzzzzzzzzzzyw{}{{{{{{{{{{{z~{zzz{x{z{{{{{{Ƽ{¹{Ž{{{{{{{{¹{{¼{{{{{¼{{{{{{{{{}{{{{{{{{{{{}|ڳ{E~S~z^|w|{|{qw{x~{z{{{{{{{{{{{{mo|tv|tv|rt|{{tx|tu|tv|tv|or|{sv|wz|y||{~|||||z~|y}|||{|{|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||}~}}}}}}}}}}}}}}}̳}۹}}}b~c~b~a~}}d~j~m~o~u~t~s~p~n~q~q~o~r~s~v~|~~~~~~~~~}~z~{~~~y~p~|~~x~~r~v~~̏~|~p~l~y~~~o~c~}۶}}}}e~g~l~i~j~l~h~j~c~}ͪ}ʧ}}ƣ}}}}}}}Ȣ}Ϥ}ب}Ц}˦}۬}٫}ͤ}ˣ}ś}Ɯ}ȝ}}}}}}}}}}}}}z}{}|||||||||w}}~}}}ļ}È}}}ь}Њ}}}ٍ}Ջ}}ޑ}ӎ}}ڏ}Ћ}Å}}|}w}w}x}x}{}}ɇ}щ}y}|}ǂ}p}|}f}߾|||c}|m}|||||||||||t|||Ϙ|{|||ϟ||||||ߧ|\}ğ|||{{{{{{{|X}d}p}p}>~g}Q}E}P}b}U}^}P}_}O}@}W}\}C}G}X}g}S}I}R}g}S}O}G}l|P|O|}F|{d{d{Հ{l{m{V{}{b|Ò|U|o{zp{z^|{C}k|m|\|A}A|{Z{G|N{Z{E|M|{w|C}A}B}@}A}b|T|B|@|a{V|{{n{zzӺzzyzwz۸yǮypzyzzq{zy̥zzyzzϟzza{Y|D}l|[|=~;~=~J~R}k|m|N~G~^}V~N~n}˿X~y38~H~e~a~_~g~CVRE|;|<^~~=x~D~H~p}<~l}F}?}d}e}I}j}>~>~?~{=~H~żW~J~I~y;~I}J}V}D~J~}}Y~D~\~~>~e}h}@}n|{Z}w}|j}x}>~l}N~o}P}C}B}P}d}M}e|<}_}B}U|B||D|s{e|j|e|p||i}T~]~f~d~g~l~s~n~n~s~p~?C@~=u~q~n~k~g~`~_~_~a~c~˿`~ʾb~ǻ]~ʼ\~c~f~f~b~[~h~b~`~s~ú^~ȿb~_~\~Ƚ]~]~`~^~d~b~d~l~k~v~AB>?~?~?~=}<~<s~|<~={<?@>|<h~d~l~{<{<q~c~e~f~e~d~n~n~j~e~e~g~i~e~e~e~g~c~d~c~c~c~a~a~_~\~a~g~e~f~{6|6{6{6}7~9|8y7l~j~j~h~}8:<<?{7x7x7{7}8~9{6|7z6}7|7{5|5}7{6}6~78;<?>;;<?>@ECAAEHEC>=;@>;:??:;;:999|6{5z5c~c~e~_~\~_~a~`~]~Z~X~Y~\~]~`~\~U~X~X~X~ʾQ~L~V~`~GW~\~K~e~c~P~z7ŸR~J~I~öL~I~L~N~[~s~l~m~h~G\~R~z?y~k~`~O~Q~O~R~N~c~f~FL~Ѩ|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{bb|{ii|hh|{{{{{{{zyy}]zӖýyyUzXzsyyEzyKzMzRzQ{=|Q|+}C|4|*}E|H{_|/}2}5}H|8|C|1}3}*}Z|a{B|T|N|A}W}<}n}c}d}c}O}a}k}P}D}˿Z}E~Q~J~G~O}L}M}}5~O}n|d|A}˾S}7~ǺM~P~=~<~G~O~g~l~y59<>q~s~h~d~ɿg~V~j~`~X~Z~b~l~n~c~ǽa~\~Q~_~U~d~Q~O~}Q~J~v}g}A~D~[~T~N~K}d}\}y}F~d}b|_|zzzzzzzzzzzzzzzzzzzzzzxy{zzzzz½zzzz|{}{{{zzzzzzûz}|{zzz{{{yz{{{{z{{{z{{{{{{{{ǿ{{ú{{{{{{{{{{{{{{{{{{{|ݖ|ôY~B~q|{|{{{{{w||{{{{{{kn|{{lo|{{{{{{ux|uw|z}|~|||||||~|||||||||||||||||||||||~|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||}}}}}}}}ğ}Σ}ڨ}}}}}`~b~d~d~f~b~f~i~m~u~w~u~w~x~{~|~w~|~~DŽ~ˇ~ҋ~ܓ~~~~~~~~~~݉~Ă~~~~o~m~o~f~h~`~_~g~i~}}}ֵ}}t~~o~}}ͩ}Ѩ}Ъ}Ƣ}ا}}}`~a~}}}ڧ}Ԣ}}}}ؤ}ߤ}М}}}}}}}}}}}|}}}}}}}}}}}||||{}}}}֐}ň}Ç}׍}ڍ}Љ}ی}ڊ}ݎ}}}ߑ}}}}}͉}ʈ}„}ý}„}…}ҍ}Ɖ}}}}~}~}}}}}ă}ы}ʈ}Ņ}˃}܊}ʄ}û}}v}n}z}̹|j}ׄ}k}o}|m}q}|||||||ʶ|ơ|~|{{{{{{{{{{{{{t||||||||||؛|X}g}ûk}|}~}}}|}n}e}f}Ŀ[}U}Z}Q}W}L}V}S}^}?}I}Y}M}Y}S}_}U}\}d}\}H}D}9};}d|U|U|K|v{~{K|{q{^{R{m{S|v|w{ѻzzɊ{H}~B}||l|L}D}P}S}|S|{p{d{|{|@|F|c|_|^|k|;}>}L|^{\{u{g{I|{}T{yo{ۿzz~rz|zħyܳyyzӹyyzzozxzd{pzSyhyzrzyzH}G}f}|9~Z}?~^}j|X|_|g}O~M~G~r}q}R~N~S~V~?~c}G~?~k}F]EYE?{<~m}G~k}U}Q}m}s}C}|5~<~{9~h}f}?~U~R~^~T~U~^~h}g}~?~X~ǾZ~M~C~M~D~X}Ļb}p}_}J}{b|=~u}\}\}j}N~V~L~k}G}I}J}V}d}\}R}P}B}D}K|`{c{Q|=}R|{W|L|J}n~x~>?p~<~o~}<>?@=}<o~o~i~e~_~a~e~`~_~`~Ⱥ\~ǹY~U~Ź[~Z~õY~ǹZ~c~d~i~j~m~p~v~o~b~Z~ĺ^~_~d~_~`~b~l~{:?@@=?@>z;m~l~j~c~f~c~d~m~t~p~n~`~y9;>A|;o~m~q~o~o~h~k~m~k~}:w7l~l~j~j~h~x6j~c~c~c~`~c~`~g~b~g~e~e~h~y5w5g~g~j~g~e~e~b~b~b~b~j~h~j~h~j~j~g~e~h~f~f~b~d~e~w2w3y3x3|5w3{468~7|69:;79;@CC=;>@CCBDDADDA?@?>=>?=:|88d~g~g~k~h~f~^~a~c~\~^~^~Z~^~c~c~]~[~W~S~W~X~X~U~[~^~U~P~`~^~Z~;_~˾T~ȻR~ĸP~J~H~D~I~J~X~ŻT~ƽV~m~|Ez~Hd~N~O~_~s~XXU~Dr~]~Z~Q~|}s}ò||||lm|{{wz|ef|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}{z}{{~{{{{x{{y{{{{{{|{|}{~{zyڨyyy{yyuyWzxyLz~BzFzMznyTz@{`{J|0}+}?|;|@|;|Z|V|/}@}Q|E|>|M|6}5}L|Q|^|7}B}:}L}<~K~|:}`}B}i|K}µP}j|V|I}i}k}r}L~D~f}>~G~B~a}A}:};~\}E~ʾS~I~z<~H~E~M~>~J~U~c~f~]~T~_~ƻ]~W~f~Z~p~b~f~Ĺa~F~k~k~`~a~]~[~q~]~N~O~}U~|H~ׄ}|||r}E~B~K~t}{}k}d}v}}f}Z}v}}|X|zzzzzzzzzzzzzzzzzzzzzzzzz{x{{{~{{{{{zzzzzz{~{z}{{{{{{{{{|{{{{z{{{{{z{{{{{{{{ź{{{{{{{{{{{{{{˼{yN}[}s}H~b}K}S}~o|{{{||ux||~|oq|oq|mp|{{{{{{{{{{{{{{{{{ps|vz|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||}}ؙ}}}Η}}\~`~a~b~g~c~b~e~d~f~k~n~o~n~n~q~t~s~n~k~j~i~n~m~p~x~{~{~|~~~~~z~x~u~y~~ƀ~~ێ~ڎ~v~ǂ~~~q~w~y~{~q~b~}}}}ј~s~ѣ}Ś}}Ŝ}Ú}Κ}}}}}}]~Y~}}}}}}ő}}}Ð}֔}ʒ}}}}}}}~}}}}}}}}Ƙ}Ҝ}˗}՜}͔}ӕ}Ғ}ؔ}ΐ}ˊ}}z}ъ}~}z}y}x}u}q}q}l}o}r}r}u}u}r}r}q}t}r}s}p}s}t}w}y}w}x}x}y}y}x}v}w}v}z}{}ȃ}º|}r}j}||ڷ|շ||||̴|||||r}v}r}s}o}||||{{{{{{{{{{{{{{{{y||||W}l}u}}s}r}n}h}ǿo}g}ω|}}~}}@~^}d}e}_}l}Q}R}W}T}V}>}A}I}M}A}[}g}d}r}y}t}[}N}p|j|m|h|c|ބ{r{{u{i{|{m{N{l{܉{{{t|i{ߩ{[|I}W|[|k|U|T}O~x|I|J|l{^{h{j{s{w{O|Z|h|?}X|9|c{[{r{a{c{_{qzyzuz}zȱyzݵyyyůyyyy}zyrz֥z{zyXy`zyzzd|T|¹V|U}~7~j}O}m|[}K}~9}{=~R~P~S}ف|ȿg}M~x5@~P}J}E}v}=~l}~5MEQ}V~Q~a}@~N}|>}a}f}^}M}}7~L~B~]}|8~G~o~Kl~K~}C~H~Z~`~k~U~O~A~E~v}y;~E~l}j}E~[}J~{}[}D~~9~i}M~}}}y}9~V}Q}]}K}X}V}9}b|u{L|}|_|K|{s{e|V||K~}T~Q~:~X}m}Q~r~k~h~a~ʽ[~ƺZ~b~e~j~j~n~r~f~b~d~b~`~T~Ǽ[~ǻ\~ƷX~b~j~c~`~ǻ\~]~`~k~g~e~f~[~e~h~g~l~s~q~}<>><{;t~n~o~i~j~m~e~i~n~q~p~g~`~c~k~k~z:k~o~o~j~k~m~h~c~i~c~b~d~b~j~j~x6z6|8{7j~e~`~`~]~_~]~`~c~i~^~b~i~j~b~b~e~d~b~`~]~]~^~`~`~c~h~h~b~b~b~b~b~`~a~a~c~b~a~`~b~d~a~u0c~e~x4y5~78}689788;??=8:<=>@DEIHECDB@@?<;<<:99~7}7}8i~j~k~_~a~d~b~_~b~g~e~c~d~a~]~Y~W~Z~Z~W~X~f~\~b~Y~Z~W~Z~Y~Z~[~W~øQ~M~ŹR~¶P~O~·Q~P~M~ǽU~úU~N~F~D~F~D~H~G~K~Q~R~l~Ns~X~}}}}|{{{{{yz|kl|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{wy{{{{{{{|~{vx{zz}{{{{|}{z|~{}{z١yزyycyxy}yyxyyyyIz^zGzQzxzf{D|Y|Z|=|Z{5|S|@|Z|Z|3}Q|f|M|W|W|:}q|v|A}K}Q|l|D}[}N~b}H~Y}D|D|\|źg|p|c}j}R}L}:~x5~=~N~ʾQ~Z~S~I~A~K~9~V~ʼO~V}BɼW~I~y?~K~G~B~{3U~O~W~N~Y~H~L~T~\~R~S~}E~}}O}Y~z<u~k~a~»e~k~ȿd~h~ǿd~ƽc~N~U~N~ĺm}|A~b~R~}C~J~_}|]}s}D~g~ӯ|D~L~A~z}e}s|ӕ|Ń|q|a}{zzzzzzzzzzzzzzzzzzzzzzzzzzzz¾z¼zzzz{{{{{{{Ĺ{{{{{{pn{{{|{{zz{{{{{{{{{{{{uu{{{{{{{{{ҹ{|f|¸q|}G}ɼ^}W}c}{9~_}`}I}t|f|m|{{{{{{{{{{{{{{rt|{{{{{{{{{{{{{y|w|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||}}|||||}}}}}}z}|}}}}}}}̖}}}؞}}Ѧ}۪}}}b~f~f~i~k~n~o~k~h~i~i~k~k~l~n~q~n~r~s~q~r~o~k~h~e~b~g~i~e~`~c~t~o~n~o~o~n~m~p~p~t~l~g~f~_~}~}~͢}ę}}}}}}}ܧ}X~}ܙ}̐}}}}˛}Ɠ}}}{}}}}Ȑ}Л}ə}Ǘ}}}ם}ϖ}ޝ}}ۙ}Ә}}}}~S~}Ύ}}ь}ʆ}ߋ}ً}}ڌ}z}}}x}t}r}q}o}o}q}t}t}r}t}t}u}t}x}v}w}w}u}t}p}q}m}l}n}w}y}q}q}m}g}|ײ|̪|ѱ|Ե|Ƴ|ò|ǰ||||||޾|Ӹ||պ||̿|||||Т|{{{{{{{{{{{{{{{{{{{{ž{]}½s}p}w}s}m}a}^}U}S}T}_}\}[}h}Y}a}n}j}h}l}Z}V}I}_}M}=}F}K}F}b|Q}W}Y}`}Z}n}N}K}S}K}}|x|a|{t{m{y{e{{o{szozzu|k|Ȑ{{{^|e||{E}m|t{o|~H}M|w{o{]{b{m{ވ{g{k{a{{Q|K||:|z{c{j{}{{f{M|}zyyһzŽz׼yzhzÖyxyyzqzmzʑzƼzŒzĺzvygyyqz{{ܟ{Z{ýY|Y}R}[}T}H}`}Y}H|@~J~d|H}\~x~Y~]~G}a|H}P}Y~h~D~]~b~w}T}z7~t}m}t}x|Q}U}`}c}~?~O~o}@~a}L~M~r~EL~Ⱦb}_~q}`~b~S~K~C~w}Q}^}m}E~A~H~O~x}n}P}p}L~P~g}V}P}Q}K}n}S}g}Y}E}L}`}M}k|c|l{V|d|||a|X|{~{q|L}s|p|a{v|b}K}C}D}L}L}{=~̿[~T~W~U~¶^~\~X~¸\~ɽ[~V~X~Ĺ\~Ǽ\~˾[~^~c~g~d~ȼ[~i~i~\~ƷX~Ϳ[~ƸX~e~c~b~^~`~c~d~`~h~h~m~p~p~t~s~q~s~r~t~t~m~j~j~p~p~j~^~\~b~j~j~m~h~e~c~e~a~c~`~^~a~_~a~\~a~d~f~d~f~l~i~f~g~c~b~b~b~]~^~`~`~`~b~]~\~[~`~`~[~Y~[~X~[~\~]~]~\~^~^~^~`~`~`~^~\~^~a~d~b~b~b~b~`~^~a~`~^~^~b~d~w5z6x3}6{5y4v2~6>@>77679=?CEILGFDA@>?=<<::87}69=~8x3i~i~b~d~b~g~b~b~b~_~`~_~]~\~\~U~X~X~Y~W~[~`~Z~Y~[~i~b~V~W~W~]~øT~N~Q~P~M~]~]~ǼV~R~N~I~F~>~l}^}O}|ݎ|Έ|ˍ|}||||{{qr|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{~{xy{zzz{{{{{st{zvv{rt{zwy{zydzywywybyÎykyryezH{ZzizD{8|M|<}T|C{K{E|X|H|]|7}F|F{Q|>|ļk|B|O{B}c}^}9~?}h}?~h}I~T}A~F~J}F|~@|T|D}¹c}t}k}Q}T}v6~F~L~öL~O~O~>~>~<~;~D~=~f}c}H}u|G}`}?~ǻV~\~X~ŶL~ŷS~T~P~O~N~}E~V~X~V~X~W~T~n~j~`~f~p~i~`~ļf~t~a~f~j~n~\~M~H~Q~R~}K~J~}|}N~a~h}]}|¹o}K~t}ǂ|Ѹ{|{|~}A[|zzzzzzzzzzz~zzzzzzzzz¿zzzzzzzzzzzzz{{{{{{{{{y{us{xw{rp{z}{{{zzy{ts{zz{zy{|{{{zzz{{zrs{{{{{{{zy{z{{ں{z_|ی|~|S}X}˽e}S}i};~c}R}Y}|{|i}{{{{{{{{{{{z}|{|~|||~|{{{{{{{{{{{{{{{qs|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||}}}}Μ}ќ}ӝ}Н}ƛ}}}}}}}}}}}}}Ϝ}}W~ݬ}}}`~c~g~k~n~n~j~i~h~g~h~g~i~k~n~m~r~u~s~s~u~s~p~k~f~j~g~a~}}a~d~k~a~}}}]~^~Z~\~}}Z~}Y~n~~c~Ԥ}Ө}à}}ś}}V~}}}}}}}}}}}}}}}}ś}צ}}}}Ԛ}ɕ}Ð}җ}}}ǎ}ґ}Ê}ړ}}}|L~{L~ڊ}~}z}w}t}r}r}s}q}p}s}r}q}q}q}q}q}q}t}v}o}p}n}s}q}p}k}n}j}l}k}l}h}|ٻ|Թ|||͸|˵|ij||||||||||||||||²|ӱ|ұ|||~||~|{{{{{{{{{{{{{{{{{{{{{{ۗ|_}j}h}f}b}]}W}V}X}V}H}N}]}U}N}X}U}V}S}S}U}O}`}c}Q}l}V}r|p|E}H}V}b}a}a}D}L}O}]}U}{|u|a|~{{Ԉ{}{b|p{[zzzx||{{r|M}Ƚb}E~o}|Y|\|J}{{M|Q|w{j{e{[{O{l{y{׏{m{{B|g{d{j{m{t{i{z{x{}zy}zzzyhzezyyyМyyyy~zwzy֓yyy۳zzz{؛{x|V}};~_}\}a}?}C}i}{;~D[~a}>~~6?AQ~a|ź^}M}N}ûd}k~EMr~M~Ǽ[~C~p}j}]}E}ĺd|R}l}?~r}A~A~B~=~ùV~d~f~S~>~J~H~D~c~|:~z9~>~@~L}b}k}e}D~F~u}x}~}l}]}q}E~K~U~;~i}O}W}O}T}X}`}D}U}W}E}6}f|H|c|g|M}a|N|_|x||N|M|q{~9|g{G}\}S}s|?}H|O}X~ź]~_~X~U~\~V~ƸZ~ȹX~Ȼ^~Z~U~Y~ȹZ~W~U~T~U~U~õX~_~b~_~R~T~T~ù]~U~ǻW~ǻY~`~h~h~k~p~{;|:{9p~m~o~p~r~p~m~n~e~\~Z~øV~a~n~p~k~n~f~_~`~`~b~̿]~ǺZ~ɼ[~ͿY~\~\~\~Z~]~]~d~a~a~_~a~a~]~b~^~_~d~b~g~d~i~_~X~V~Y~[~[~Y~W~W~Y~Y~Z~Z~[~\~_~^~\~^~`~`~^~\~^~_~]~_~b~`~_~_~`~^~`~\~]~`~d~d~d~x5x4b~x4w2c~9?;55~3~456:<=BHõLNKF@A==<=<<976}58}6{6e~d~e~e~b~c~g~c~_~b~]~^~]~`~[~]~\~X~X~U~Z~Y~X~U~̽O~O~R~a~[~S~ȼQ~Y~ȼT~Q~¶P~ȻR~ͿT~ƻT~ȽV~\~Z~O~I~D~i}|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zz}{{{{{{{{{{{{~{qr{z}{{{}{z|}{z{{z{{{{{{zzzzqzVz܍yyyy\zYz{CzA{={R{q{R|3}~;}};}zQ{P|\|g{J|E|E|L{i{h|?}ȵM}Z}g}C~D~C~F~X~8Ax3X}^}v6~A~@~F~j}m}o}F~A~c}b}ö[}l}M~T~̼O~ǸR~D~>~y7~i}~9~o}S}|C}A}E}X|G|r|x9~ͿO~S~`~U~̽T~Q~L~d~t}z}O~H~F~E~e}f}m}>X~D~f~a~n~e~p~ƾl~T~J~f~c~o~żc~[~Z~Z~R~o}l}@~F~@~`}||{{d}G~j}S}N~<>CZ~J}zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzsq{wv{wu{|z{{{{}{}{~|{{zzrp{zzzyw{xu{xu{vs{xu{|{z{z{{{{{{{{{{{{{{r||X}L}X}`}W}S}>~C~@~|{{||֋||s|]|s||||ƴ|i}ǻ|{{{Ң||||||{{{{{{{{{{{{v{|~||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||}}}}½}}}ɗ}ќ}}}}}}}}}}}}}}Ò}ӗ}ݔ}ɐ}W~}}~_~]~^~c~g~f~i~l~n~q~p~m~n~p~r~p~p~p~l~m~m~i~}d~}ް}}֫}ǥ}ơ}Φ}Υ}Ÿ}Ĥ}޷}̮}}}}V~T~}}X~V~}}}}b~~~}Ξ}Ǡ}}[~}}}}}}}}}}˜}}}}}̵}}ř}ƕ}ɐ}Օ}ٔ}}~O~׍}ג}}zL~}ޗ}ؕ}ΐ}}}}}x}x}t}q}s}r}r}q}n}q}v}w}v}r}n}n}t}s}p}k}c}||||||a}|޼|и|ε|ŵ||||||||||||||||||||||||||}|{{{{{{{{{{{{{{{{{{{{{{{{{{{x|Y}X}Ϙ|ȗ|U}a}i}c}Y}S}S}P}^}P}S}G}@}H}[}W}[}J}Y}X}K}E}X}x|\|q||U}Q}^}e}G}N}|a}|e|w|N|X|f|ˉ{s{x{P{T{Ъz[{{||||B~^~M~W}V}Q}B}S}j}Q}N|Q|p{{v{{`{]{z{{V|{m{j{t{u{w{o{c{]{џzwzyyyzzyəyvyy˲yڿy׮y˜yyċzJ{ҟyWzPz~yʦyΫzz|zj{}a{g{x|V}c}d}k}C}ĽX}w~Am~D~c}8CW@@Y}O}|?}Y}R}S|h~R~:n~z8a~e~~At~~U~e}H}A~h}H~q}P~W~[~f~e~H~M~K~{>~S~@~|~@~y=~n}z|\~Q~{:~C~}}U}m}e}E~h}m}ǺW~Ƚb~S~H~p}j}[}H}Z}[}\}I}K}R}W}S}k|`|I|d|I}c|`|n{O|_|M|z{zz[{M}h}Z}D}v|L|^|U~X~·V~ɽX~M~I~N~U~S~S~W~T~P~N~T~N~L~Q~U~´W~ŷX~U~S~T~Q~T~T~T~V~V~h~k~o~n~g~c~h~k~d~d~c~d~n~g~_~ŹW~R~X~f~g~i~k~f~g~d~_~^~˾Y~\~ȼ[~_~\~Z~ϿY~\~ϿX~Z~\~]~`~_~]~]~[~\~]~]~`~]~]~`~`~b~_~[~[~[~Y~]~[~W~Y~[~Y~Y~W~X~Z~Z~Z~Z~[~^~]~[~Z~Z~Z~Z~Z~\~]~]~]~^~]~[~[~]~]~_~_~`~`~^~c~a~`~^~`~`~X~`~ȺR~F~F~>~D~ôQ~_~a~d~78AHKEd~|}ʽY~a~y4~76~56~7~7y4|59}5c~`~d~f~b~`~b~a~_~`~b~`~a~a~^~^~b~Y~U~X~V~Z~;a~ɻM~P~P~Q~^~ͿQ~˽Q~Z~Y~ǹS~T~b~[~ɼV~ŹT~_~e~R~I~A~Y}{{{{{{{{{{{{{{{{{{{{{{{{{{{x{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{vv{zz{{{{yz{z{{zwy{{wx{xz{zzwy{zzêzgzOzTzvz{z|z[{V{Q{e{?|@|<|W|X|c|j{tzzzWzezX{A{Uyy?|M}]}L}`}F~p}p}E~J~U~Z~j~B~G}C}Z}l}C~F~i}U}V}h}ʾb}||{C}y|?~I~O~>~A~|4~Z}[}^}g}\}N}ނ|ǿx|]|S|{ùX|1~Y~g~ȾX~`~R~N~}j}E~J~J~U~O~K~t}z}ǽTQ~I~d~f~w~Ƽg~Ƽi~r~P~U~Y~`~ǽZ~^~Q~N~X~I~{}P~p}@~?~G~y}Y}||w}i}S~~<~>}BOCu~n~d~}Ä|zzzzzzzzzzzzzzzzzzzzzzzzzyw{wu{sq{zvt{xw{ztr{tr{yx{tt{wv{zx{wu{{{us{xv{yw{}{~|{{{zzzz{w{z}{{{x{zx{~|{{{{{{{|{{{{{{{{{||ˈ|e|||R}Z}M}ǻ{||V}p|{{{|a}c}Q}|s|v|~||ɒ||֜|Ǚ|o|ˊ|||Ǵ||||||||||t|{{{{{{{{{x}|z|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||}}}}}}}}}}}¿}}}ĝ}֣}š}}}}}˦}ˢ}}}}›}Р}}}}Y~]~~\~^~`~a~b~}}}}}}޲}ڳ}˭}ʧ}ɤ}Ǥ}Ʃ}}}}ǡ}ˡ}ġ}}Ƞ}}}}}}ϫ}ʥ}͡}Қ}ɔ}}ݙ}}˖}}ՙ}–}}۪}~~c~ݡ}}V~}ʟ}}}}}~}}ĕ}}×}}Ē}ʗ}Ι}֟}Ǒ}Ì}}}}}}ǿ}ҙ}В}ȍ}z}~}z}}}~}}}v}q}o}q}o}m}p}n}n}k}||Ž|ǹ|̷||||ѻ||||۾||Ǹ||||||||||||||||||||||||||{{|{|ȗ|o||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{y|y|t|x|||||N}I}Z}_}U}J}E}?}A}B}>}L}S}]}P}D}I}W}Q}p|n|j|Q}T}U}L}Q}R}K}\}|S|m|~6|F|n|{{o{R{]{~Q|z{ϗ|߉|Q}đ|{{{×{p{]|؅|ז{^{kzM{U{{{zp{{{{{N|{}{q{i{v{]{_{S{ψzszֻyxyyyzizyryyozͶyyzzW|Qzƅyyycyyzϲyƶz{ك{N}]}7~S}Z}=}@}a~<[~p}>JżW>}7Js~E~v}i}B~c~HRC>\~84JEx~P~c}^}ʼR~Y~C~K~I~\~8f~\~T~Z~M~T~E~M}}½j~|C~@~b|Z}u0^~E~V~J~O~a~]~m}m}I~Z~Y~I~y}Y}q}I}\}R}c}E}I}J}K}J}c|]|P|@|M|{c|g|H}߈{T|E|{b|@|U}[}I}a}]|O|3}?}P~S~ɽX~Q~N~K~P~H~I~L~H~K~P~J~I~L~J~K~I~J~K~K~J~J~K~P~öV~V~_~d~i~e~]~ķW~V~V~ǺW~˿X~ǻY~T~R~N~R~S~a~g~g~n~n~g~_~i~]~Y~X~Z~T~ŷU~ɻY~;Y~X~ɺU~ǸS~ȺU~ϿX~X~Z~X~Z~[~[~[~]~X~Z~Z~\~_~b~`~b~c~e~e~[~V~V~X~̾V~V~V~V~W~U~X~Z~Z~Y~W~Y~Y~[~Y~Y~W~V~W~U~W~\~\~`~^~[~X~Z~[~Z~Z~[~]~\~\~]~Z~Q~ϿS~I~?~|s|ۇ|h|Ь{{{{k|߉|҄||_}R}`}b}Z}T}~|ȱ{l|΅|P}i}D~]~}5}5{4b~e~x3e~^~^~`~d~^~^~b~b~d~a~`~_~\~^~^~_~Y~W~\~]~z6{7ɼR~ɺN~N~R~ȸK~ƸN~ͿQ~µN~öP~ŸP~ŸS~{7\~Ej~W~Y~S~N~Q~y;~ދ|׼{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}}{{{{}~{{{{{{{{{{{tu{~{{{{{{|{{|{zzvw{{|{ww{tu{uw{wy{zzzzzřz\zaz?{YzbzeznzI{U{C|B|UzL{4|@|=|J{7{xxxܡx^yx}zT|Y|c|{Ȅ|X}e}|H}}G}`}>~ȶN~ͻR~n}Q}zD}G}~?~H~L}h|l|p|C}ļw|]|r|H}w8~G~=~G~F~<~5~R}V}|:~I~D~X~ȿd~O~Q~H~~<~ķL~R~_~ĺU~R~I~V~R~x}K~P~F~P~J~}F~t}E~D~]~Q~Z~b~q~W~H~O~z~d~Y~Ƽ`~l~R~ƻ]~O~W~c~k~c~^~g~ʿ_~O~}L~G~»t}E~{D~c~e~9A~~Et~UPżb~q~AI}zzzzzzzzzzzzzzzzyw{zzzzz|y{~{{{|z{}|{yw{{z{{z{|{{{{y{~|{~{{}|{}{{{{~{|{{zw{{{zw{|{~|{{~{{}{}{{{{|{{{{{{{{{{{{{{i|f|Ҵ{ܹ{j||P}O}e}d}U}^}|P}c}]}}Q}ڝ|U}S~S~H~l}L}|J}|H}||h|i|e||w|q|||~S}Y}f}׵|||ܾ{|||||{{{{{{շ{ճ{β{h|q||w{|}|x|o|q|||~|x|||||||||||||||||||||||||||||||||||||||y}}}}ٝ|y}}Ó}}}}}}}}}}}–}¾}Ț}ɛ}}}}}ӥ}֤}}}}֪}ǥ}}ӣ}Ó}}}Ý}Y~`~]~`~^~}}]~_~~[~}}~\~~]~]~c~`~_~~Y~~Y~]~^~~X~֧}}ƣ}֨}}}}}ҧ}ޮ}}ް}}}}ң}֥}Х}Ҧ}}ß}}~\~k~~~d~~Y~`~~Z~˞}}}}}}Ø}Ȝ}}}}˘}}}ē}}}|}}ޙ}}}w}w}r}q}s}o}p}|||||||տ||||Ϻ|̺|ɾ|ǻ|л|н|н|Ҿ|ͻ|ȷ|Ҹ|Ϸ|||||||||||||||||||||||||||||z|}|{{{{{{{{{{{{¾{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}|{x|{{؀|ÿU}K}L}R}H}O}B}K}:}:};}^}]}U}|D}I}M}V}||X|||Z}K}Y}V}Q}V}h|U|W|E|a|{h{i{˩zW{|P{zj{z{{|t|{{b||]}_}M}O}{{t{߾zzzzn{t{s{ډ{y{m{{t{q{f{x{\{zقzlzyyyԸzxzѲyyyyyyyzzםzT{zrzXzȿy¹yyڸy}zzz~{ۅz\|[}D~d}Z}ƾR}M|g}83<=GKm~}8G|~`~p~o~<>m~c~Z~d~9t~F:l~W~@~y5~;~ͿT~K~K}F~I~T~~7m~f~^~Q~k~L~_~o~W~S~Y~L~C}i|P~;:?·U~<~>~q}R}h}G~V~Q~}@~Z}y}O}M}T}]}J}M}H}>}B}_|G|D|m|_|H}g|J}O}g|:}B}q|E}S|J}M}Z|K}4}6}G}T|{?~D~D~D~G~J~zA~x@~~}~B~x@~z>~B~A~E~E~G~I~I~G~K~J~M~L~K~U~T~ĹY~j~p~k~b~\~ƹW~ɼW~T~R~N~L~N~÷U~Z~^~g~i~l~j~n~j~g~a~f~[~]~Z~ʻV~ŶU~ĵU~ƸU~Z~ɺU~ʻW~X~X~οX~ͽV~X~οV~X~X~X~]~̾V~X~ȻW~˼W~Z~X~_~`~\~[~`~`~V~X~οT~Z~;V~V~X~V~W~W~U~U~U~U~Y~Y~W~;T~̽R~U~W~W~\~W~\~X~Y~X~T~V~U~X~Z~Z~X~[~X~Y~W~K~d}}G}|q|{{{{{{{{{{{{{{{{{{{{{{{{{ԭ{N}m}>~D~>~A~]~^~x1^~^~`~^~^~`~b~_~b~_~Z~X~Z~X~W~W~S~X~\~ɼR~̿S~ʻP~H~ϻK~˺M~ǸN~j~N~O~O~O~N~N~R~z6\~W~ɼT~_~d~R~P}{{{{{{{{{{{{{{{{{{{{{}}{{{{{{{{{{{{{{{{{{{{{{{{}}{{{{{{{{{{{{{{}~{zz{{{{{{{xy{zzz{xy{wx{zzvx{ux{tv{vx{|~{zzeyߔyUz߇yjyxjy`zlzP{yzpyUz@{.|E{E{<{xߣx٣xzzn{ߦ{W|h|O}n|}|S}[}J}D}˸d||B}Z}^}A~c}ƶc}e}:~v1l}|n||N}>~и]|[|N}M}c}_}`}x4~B~7~X}[}J~L~I~a~R~i~U~`~d~\~[~d~b~¹^~m~Y~f~Ǿf~M~r}|[}|}x}M~V~_~V~l~L~I~m~h~Z~M~ƾe~i~Q~Q~ɿ]~j~ĸZ~]~M~Y~P~{}K~ȺZ~¶\~N~U~N~L~X~T~U~c~o~Ga~|D~L~@M;@;{8m~zzzzzzzzzzzzzzzzzzzzzzz~{{|y{}{{}{~{{|{|y{|{}z{{{{{{{{}{}{|{{{{{{{{{{{{{{{{}{~|{{{{{{{{{{{{{{{{{d|DŽ|}|R}|W}Y}i}d}X}b}||u|<~E~B~_}~}H~g}o}Q}J}J}||||}]}k||܅|Q}S}|ˎ|h|א|ŧ||̓|g|||||ȗ|||~|f|q|͎||{~|{{{{{{o|}n|p||~|z||Ə|||||||||||||||||ɴ|g}||||||k}{}||||y}}}}}}՘|y}Ɋ}}~}x}w}{}}}}}}º}}»}}}}}̛}}}}Ø}}ߣ}}ӝ}ס}}}і}‘}}}ș}}̡}ͥ}̡}Ѣ}}}}[~}̡}}Ġ}ӡ}ԩ}}}}}}ϥ}}}}}}b~_~}}}}۬}ְ}α}}Ϊ}ڰ}ͭ}ܱ}Ȧ}}}}Z~^~n~ޗ~~r~[~Y~}Л}}{}|}~}}}}}}}|}}}}}}x}x}}}|}{}|}y}u}t}q}|||||||||||ҽ||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{t|ٌ|ˆ||O}N}R}O}H}I}C};}D}J}F}[}X}F}B}@}|R}W}V}|n|ډ|Y}w}Z}Q}T}||b|k|`|h|_|x{f{zk{f{^{ݑzz{zU|\|M|h||P~žy|Ľr}[}K}L}o{p{e{ězzzxzלzq{{U||{{{u{a{W{V{T{|L{zhzzzzznzyyέyyyzzzzyy{]z½yz}OzۖyyۥyyͪyuzzzfzzN}ĺS~G~j}c}L}B}?~<r~^~C~P}t}{7D\~ķS~_~j~z8ɿV~Ac~|?n~V~G~~S~]}{1~9~_|B~?~f}f}B~I~z=~z6AV~P~Q~M~7~k~`~J~=~C~9~=~]~};~F~I~\~N~I~e}s}J~M}}|P~=~W}l}r}^}L}M}H}L}:}o|C}T}G}V|M|o|Z|f|J}M}M}D}k|T}L}_|H|W}[}N}V|L}T}E}^|8}f}F~D~z?~w=~C~H~C~w}x}{}{A~B~F~@~J~G~K~O~G~G~R~W~ƺZ~W~P~Y~ʿ_~f~c~f~a~]~\~\~ǻW~O~F~L~]~`~`~c~j~:m~j~i~g~b~[~Q~a~ʾV~Y~Y~ȺU~ķS~ŷS~ŷS~R~õS~ȺU~˼V~ξV~ʻT~ʻU~οV~ȹU~X~;X~X~ͿX~X~]~ʻU~ɹS~;V~Y~Y~\~[~`~b~c~c~c~a~\~V~V~[~W~U~U~οT~ϿT~T~W~̾T~ʻR~S~V~U~S~V~U~X~X~U~U~S~S~S~T~T~Z~Y~V~Y~U~ѿQ~j}ǽx|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ҩ{ʢ{~]|;~U~V~[~\~Y~\~Y~_~\~]~Z~U~W~ͿS~T~\~S~S~ɻP~ʽQ~U~U~L~J~óJ~ȶJ~ĵJ~L~N~K~J~H~{9Y~W~^~ķS~ǻV~µR~[~L~N~\}{{{{{{{{{~~{{{||{{{{{{{{{~~{{{~~{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{z{{yz{yz{{{{{{z{{wx{zy{{}~{{{|{wx{{|{zuw{zztv{vx{xy{zzzdzszҗyycyxxdz`z]zyxyizOzUzKzygyxĩxsy{{Ƥ{{g|»u|\}j}³`}{G}T}S}j}H~A~|r|ɺk|[}k}m}F~ŲJ~b~?~|zF}{|i}ѾR~f}c|}X||^}u}K~Y}:~7~g}H~c~T~K~k~R~L~J~@~L~=~W~g~{7i~]~e~f~`~L~m}||I}ǿi}d}C~~}N~Y~V~d~|9T~K~U~k~r~Z~\~r~Ⱦf~`~]~Q~c~U~Z~S~U~a~N~}E~m}~}ŹW~N~J~R~o~h~m~R~ƽ]~Ǽ\~Ap~n~;{6¶]}zzzzzzzzzzzzzzzzzzzzz~{{}{{z}z{z|{}{{{~{|y{~{{~{zxu{zzz{{{{}{{~}{{{z{{kk|z{|pp|{{{{{qp|{{{{{{{{{׷{m|v|w|p|Ɉ|||||f|{|}c}N~{}e}=~t}w}?~E~C~n}|ܰ|p}j}||v}ܔ}||||P}||d|Ħ{^|h|h|ӧ{{{|͢|w|i||r|{c|t|||{{{{{{{i|i|k||i|{i|{m|ڈ|Ê|||||||ď|ܙ||||||||Z}^}i}||||||||||o}}}}u}t}q}|}ž|}f}r}v}~}}}~}}~}|}{}z}{}z}z}}}}}}}}u}ƾ}ɐ}ŏ}}}}Ƒ}»}Ȑ}Γ}ʖ}}П}}ۣ}}ީ}ڥ}ަ}}}}}}Z~}}۟}У}}ӣ}}ߥ}͟}}}ѩ}}a~}}}ک}ߪ}}}}Š}}}ܳ}`~Ҫ}}_~}}\~_~a~]~T~q~ڙ~_|~a~e~c~}}}}}}}Л}ћ}}}Ñ}Ԙ}՗}}z}{}}~}x}w}|}~}}|}u}u}~n}l}|||||ĸ|ź|ɻ|¹|||||||||||||û|||||ɺ|к|·|ij|ܿ|j}i}|~c}j}e}g}ޱ|в||i}q}||{{{{{ȶ{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{t|T}|o|M}K}S}[}G}D}A}=}4}6}:}M}G}>}u|:}O}||N}|u|v|c}`}O}b}V}ֈ|u|T|c|E|n|e|s{zf{M|G|y{zzzzzz¾y|}C}Q|̀|W|]}\}p}?}\|m{v{zj{{r{j{{{~{{ڣ{ʗ{m{]{]{}V{|K{Q{zyozzjz|czЪysy~yÿyɬyʥzz}zzyʵy{zVz~y]zyyrzqzzzqz|\z{R}l~_~W}\}źU}g}}9~HOR}¸d|G}K}L~9c~i~e~m~<>>b~n~~8f~i~_~[}Q}k}@}úV|I~l}i}@~w}z}M~@z89~`}R}6~U~@;L~T~E~P~~}]}>~C~ȿe}g~h~?~r~[~J}S}t}B~m}j}]}l}K}k|Y}K}M}N}I}O}e|g|]|f|N}h|{{W}K}G}E}]}R}:}<}`}E}L|;}L}G}g|h|5}8}C~A~y=~A~F~w}ǹb}r}|}z}u}}zF~{C~M~R~O~M~R~¸Y~U~ż]~Ⱦ\~ŻZ~Z~ĹY~]~`~ʾ]~h~f~d~d~\~S~Z~_~ȽX~]~c~c~[~c~j~f~f~c~]~Y~Y~S~g~\~ŸS~\~U~ϿT~O~µS~Q~Q~Q~õQ~οV~ƷS~̽V~ƷS~ȺU~οZ~Z~Z~öT~ǹU~̿X~[~X~V~_~`~`~[~V~[~b~X~W~Y~Z~W~W~W~Y~W~W~U~̾T~ʽR~ɻR~̾R~S~Z~U~S~S~V~T~U~S~S~U~U~U~X~T~T~V~V~X~U~O~}8~ܮ{{{{{{{{{{{{{{{{{{{{{{{z{{{{{{{{{{{{{{^|M}_};~K~V~X~[~W~¶R~F~m}Z}A~F~G~I~I~J~I~J~ɻP~L~I~F~E~F~ƶJ~J~K~I~K~N~M~ʾV~öR~L~L~J~B~I~T}x|ӷ{{{{{{{{{{{}~{~{{zz{{{{{{{}|{zz{|}{{~~{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{|}{|}{~~{xy{z}{|}{z{{{|{{z{{xy{uw{xy{xy{{zy{{xz{rt{uw{zzzbzpyyfyybyuyiyQzVzxMzazzXzyynyytyxåxӪxszzq{u{Y|L}<~C~@~I~G~ׅ|`}]}|O}`}]}|øy|{||ͺW}H~W~a~m}?~<~H~O~b}h|F~P}i}m}Q}T}G}l}X~d~\~N~H~^~b~W~T~I~F~c};~ȻO~ùV~}}µX~\~O~~G~}Ԃ}k}n|ƃ|\|W}n}S}q}n}̿V~8<A~A~G~}׃}~}P~V~R~O~W~P~[~^~{F~N~w}}}Q~Q~F~G~x}]~]~J~W~Q~o~{=Z~O~U~D~G~^~O~P~=Q~߁|zzzzzzzzzzzzzzzzzzzz}z{{{~{{{{{{}{~{{{~{{zw{zzyw{{{{{}{{{{{{{}{z{{{{{{{{{{{{{{{{}}|qq|{{{{{{{{{{{||[}X}Ƚ}||]}}<~A~?~>~x}}<~{}||נ}ν|y}~|||ř|w|~||Ն|x|{{{{{Ş{{~]|s|k||{{Ȳ{{s|h|d||Γ||z|{|T}v|ô{κ{{{{Թ{{l|s|ݗ|||}|{|||x|~||||՜|||Ӷ|||_}b}|||ư|ة|g}k}|l}t}z}}}}}}}}|q}|}|}}}}}}}}}}}}}}w}}}}}}}}}}~}}}}}}}}ÿ}ћ}۟}}آ}Ԣ}ҝ}٠}}}}}֝}͝}̞}͝}ǝ}ˣ}С}ś}ŗ}ɗ}ȗ}Ș}×}ɞ}٥}}ޡ}٣}٬}ެ}}}}}}ۥ}Ѣ}ު}}}]~`~]~Z~X~Z~^~[~}}ʗ}x~ˌ~i~f~f~b~}Z~}ޛ}ߝ}}ɡ}}}}}a~\~ۙ}~}q}y}|}}Ҕ}ľ}}}}u}t}r}p}k}p}q}s}o}m}m}|||ݾ||ѽ|Ⱥ|Ĵ|ȵ|||g}g}l}n}o}l}r}r}s}s}x}ż|}ǿ}y}w}s}j}Žz}l}|l}k}e}|ܯ||{о{{{{{s|{||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ѹ{|O}G}G}O}L}E}H}:}=}<}6}@}E}=}p|K}w|G}|||]}|{|W|i}||r}|{|z|S|Z|P|]|{O{ڑzr{V|Q|^{z_{V{zb{~zz{{{{d|O}U}X||{a{b{Y{z{q{y{c{}{{i{{v{u{[{zzf{T{zǿyƾyszyոyyyyyyzz{zӛyykzbzXz[zӘy}az_zyyzykzzp{I~]~i~A~W}ž]~P~n~Ŀ^~D~z1O~|}}7=w/<[~G@HA6Mi~ĺU~d~>E~\~o}}=}b~Z~G}H}W~P~?~E~c}d~q}r{Ǽa{f}S~Q~\~N~h}F~H~C~B~?~I~u}[~n}ļ\~W~g~D~]}P}M}Y}T}U}U}F};}R}[}Q}[}h}U}=}R}X|B}D}f{U{H{^|Z}X}6}:}b|8}~|Q|L|]{i|Y}?}6}X|S|Z|s}D~w}o}s}n}r}o}n}n}n}q}t}@~F~D~B~I~G~R~S~P~X~U~U~T~ĺY~[~·X~\~\~U~^~a~[~U~X~_~i~]~ùW~ƻX~b~h~b~d~`~`~[~\~Y~R~h~]~]~Z~W~ŸS~R~T~R~S~O~P~ʻT~̼U~´S~̾V~X~X~Z~Z~Z~V~a~\~W~\~Y~V~W~˽T~̿V~V~V~X~S~;R~U~U~S~Q~˽Q~ʽR~W~U~ͿS~;R~U~̾Q~ͿS~T~U~οS~U~ϿS~̽Q~οQ~̼P~ϾQ~R~T~X~X~U~V~U~Q~;~Y}΁|{{{{{{{{{{{{{{{{~{~{{{{{{{z{ж{{{{{{{{{{{{{{{ƞ{l|P}{:~I~c}|g|{{{j|o|s|s|v|t|ك|f}C~F~D~E~F~?~?~?~·`}Z}o}<~B~j}^}T}~|r|{{{{{~~{~~{~~{{{{{{zz{{{{z{|{{{{{u|{z~~{~{{{{{{{{{{{{{{{{{{{{{{{{{{|{~{~{{{{||{{z{{{{xx{xx{ww{yz{{{{z{{ww{{|{st{|}{|~{{zz{{|~{wx{z|~{{zzɟyqysyܻyyǍyۊyyez`zڊy~ysytyxyȡzz{{z{{¸{G}ѹO~a~վN~Z~E~^}H~T~N~@~d}Ղ|{j|{Ō|č|V}h}?~G~s}B~H~C~ƺ\~D~q~i~L~T~M~F~z;~s}]}n}ùf}E~u}}q}r}H~i}V}=}V}I~z=~|N}{9~A~B~~A~V~{}E~F~źk}ʺc}k}r|g}t}h~L~ĺ]~<>N~m}C~Ce~}T~n~R~U~}K~P~}}T~}G~N~E~K~b}ƾg}E~N~r}|9~}O~M~t}}k~P~R~M~g}P~Ab~e}{zzzzzzzzzz~}{{}{{{~{z|{{}{{}{{{{{{{|{{~|{{{zzz{{{{{{{{{{{{{zzzzwt{|y{zzz{{{z{{{{{{{{{{{{{{{{{~h|p|v||P}zM}ˌ||X}[}_}U}h}r}|͹{yx|̘}}m}{{{T}[}؞||{{{{{{{{{{{{u|{{߹{g|{{{{{̉|…|{||Ʌ|Q}|{{d|{{Ʊ{Ϻ{{l|~l|Έ|||}|}|||y|}||||˚||ǥ|Ӛ|ڲ|ٽ||k}~}r}޴|ȟ||_}}Ɖ}Ȍ}‹}}}}}}}||}}}}}}}}}}}}}}}}}}}}}}ÿ}Ö}Ŀ}}}ƚ}ș}˘}Ӝ}О}١}}}}}}}}}~X~Z~[~}}}}\~]~^~}}}}}}}^~^~\~[~\~b~e~c~d~d~_~]~}}}}}}]~`~_~[~X~W~כ}}}ʍ}}ԑ}}ܬ}y~x~}}}c~`~}w}l}|||v}}u}}}~}~}ƾ}}Ј}ъ}͈}ǿ}}„}ą}}}z}w}º}}ż}Ž}Ą}ą}Ä}̇}Ї}Ӈ}Ʉ}Ӊ}ֈ}ن}}ӂ}Ҁ}y}źu}y}źt}p}n}s}r}p}i}l}n}l}g}||ز|ر|Ԯ|ƫ||Ϸ|ȵ|Ҳ||{{{{{{z|}u|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{q|r}4~K}D}L}I}A}<}P}:}<}:}C}J}m|@}i||U}~O}̄|\}ˀ|z|_|ޑ|a}|l}|H}π|_|T|S|M|M|p{?|H|C|{~G{g{~S{ڋz`{zβz{zzzz}{{}{w{x{i{ق{f{z{{y{z|~{{{{s{{{{{g{ݚzz}xzòy}zyyvz}zyyyδzzzzdzywyyzmzʼ{yYzozyyۿy~hzlzՙzX|Ž]~k~AO~ȿc}X~o~G~M~z7@7U~D~63<Y~6II]~R~a~j~X~l~^~»\~N~úS~^~WHC~R~kI{}g}d}V}j}Z}N}\~}v}d}k}>H~F~_}n}~@~>~M~G~`}5~b}A~R~I~e}Ŀn}T}V}Y}J}P}b}\}l}Y}Ľc}Y}V}X}C}A}x|X|[{=|:}1}G}K}D}K}G}b|b|Z|{h{a|X}M}A}I|i{{c}R~Ǿ\~Q~|A~|}z?~p}q}j}m}s}s}|>~@~}?~y=~B~G~I~L~÷W~V~V~T~ƻX~ùW~L~B~G~źV~X~Y~Q~·V~`~[~e~`~d~a~g~e~c~e~g~c~`~_~\~Y~ȻR~`~\~ͿV~d~j~Y~ʾX~ȼ[~ǺZ~T~Q~õS~µU~ͿV~[~]~Z~]~a~]~V~Y~Y~[~Z~Y~T~Y~˽V~ʽV~X~̾T~Y~ξR~U~S~T~S~U~U~V~S~S~˾S~˼R~ʼR~ɻR~U~;R~ȺP~ƸP~S~S~S~Q~ξQ~ʻO~пQ~V~S~U~V~S~Q~J~Z}޸{{{{{{{{{{{{{{{{{{{{z{{{{{{{{{{{{{{{{{{{{{{½{Ŀ{æ{ѫ{{k|{½{{{{{{{{{{{{{p|Q}^}c}ŷZ}Ⱦw|^|v|{{z{{n||{{{{{||{{|{}}{{{{z{{xx{yz{|}{{|{{|{|{{{۬{}}{{{Ӿ{{{{~~{~p|{|{{z{z}}{{{{{{vv{||{{{{{{{{{{{{{{{{{{{{{wx{{{|{~{}~{{vx{{{{zz{{{{}}{zz{yy{{|{uu{{{yz{z{{vw{z{{{}{{}{z}{|}{yz{qr{vx{{~{zzzzz~z{{ezfzpzygzvz}[zÖyxyŭyz{{÷{{{{m|ѿe}M~ŵV~пY~|=~C~c}[}O~]~I~¸U~|@~|q~a~b~W~S~܄}_}T}q}w}ǻh}y}Q~}7N~S~ǿ^~n~^~^~M~÷R~J~?~M~K~C~e}r}x}<~b}P}\}y+~I~c}o}d}G~^~M~`}|U}H}˹X}L}L~s}{:~=~Z}E~ûU7Jj~̾`}r}A~ͿX~t}}q~}X~[~W~}U~}~}r}X~I~q}m}n}~>~|}s}e}y?~L~S~d}~}F~Y~U~h~e~E~T~n~NCe~݄|zzzzzzzzz{{{{{{zz{~}{{{{{{{{zz{{{{{{{{{{{{{{{{{{{}{zzz~{{~{}{{{}{{}{}{{{{~{{~{{{{{{{{{{{ư{{Ƚ}|ۍ|r|¹~|n|~i|{|||||x}մ|Ю{|V}|ܢ|||ʜ|`}|||i}{{{{{{|u|{Л{ԝ{{˫{ȩ{Ȭ{{{{׬{ٳ{ij{{{۶{{߀|||{ƭ{{{}f|ѹ{{h|h|w|s|~j|x|ڍ||Ŋ||||||u|}||j}|||||z||Ǒ|t}}}Å}}ƿ}ʼn}}LJ}ʈ}}w|ſ}ƍ}č}̏}ʌ}ʌ}ŏ}ď}Ž}Ď}þ}}}}}ؠ}}}}}}ޣ}}}}}}}}}}}}Z~]~_~a~c~c~h~i~i~h~f~e~b~a~d~c~a~_~^~`~`~_~d~k~n~n~j~k~f~`~_~e~f~d~j~e~d~c~a~c~b~Y~[~]~]~\~\~[~X~W~U~}}}}}}}}}̍~}t}t}y}{}|}x}w}t}w}s}l}r}~}Ȃ}̄}ƀ}}ɿ|}ۅ}yE~H~ނ}}}}}}؈}}}}׃}}}}|}}}v}Ǽv}|}|}|}úu}úu}x}x}y}x}q}k}g}a}_}_}a}}\}Ч| |ǣ|ث|ݰ||Ӭ||||˨|||{{{{{{½{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{D}S}P}I}I}J}D}J}D}?}n|<};}x|M}Q|n|^|Έ|b}`}|||ݎ|ّ|f||]}e}m|K}w|b|=}\|z{q{G|T|?|~E|Z{J{J|J{zy~~zcc{zyzopzyyyyzz{|zz}{{{{ڵ{{{{{{{||{{{zyz~hzyyyzzyzvyzzzyyҍyҘyy̋yTzydzqzvzyߵyyzm{h~K@=o~t|F~^~M~ǾY~?9[~ȻR~8~Y~4Y~|4ʾLN;z1j~=4]~ŹU~Y~H~b~`~e~b~bG]y|Q=~I~h~o~Fs~]~\~E~Z~L~^~Y~l}R}@~C~D~R~=~^}G}I}a}D~K~N~J~\|Q|N}~<~o}J}Q}<~=~x}f}^}e}S}S}E}4}P|7};}6}n|a|j|E}?}]|F|w|g|B|S|p|E}e|N|h|R}G~M~K~o}p}}}C~y}|;~8~7~q}h}}8~{9~B~z?~D~O~Q~S~¸W~W~V~Y~?~?~B~O~˾V~ǻV~R~Q~ƼY~ɾX~ǼX~]~e~[~]~`~b~`~h~i~g~d~e~c~\~a~[~_~Z~Y~[~a~[~Z~b~X~X~X~ʽY~T~ǺU~öU~ȻW~T~Z~Q~;V~V~ƸS~ǹS~[~\~\~W~V~ɺT~̾V~˼R~οT~U~˼R~˻R~̽Q~ͿQ~;R~S~Q~V~T~οS~ȺP~ʻP~ƸP~T~S~̾R~U~˿U~U~S~ͿS~˼P~ǹP~ƸN~ǸN~ͿQ~Q~Q~Q~S~J~O}{{{{{{{{{{{{{{{{{{{{{z{{{{{{{{{{{{{{{{{{{{{æ{ɧ{ͫ{ׯ{ݰ{{{{յ{{{{{{{{{{{{{{{{{{|{|{{zzzyy{{}}{{zz{qq{tu{rs{vv{vu{ss{pq{tt{vv{xy{yz{}}{{ww{~{{{{{{vu{{|{{yy{zzwv{yy{zvw{yz{{{{{{{{{{z{{{{|}{z{{{{{{{zz{xx{ww{}~{{{{{{{{}}{||{{{ss{{|{{{vw{{{{{{{yz{{}{{}{{{{}~{z|{y{{{{~{{{z̡zدzȭzzzrzzzz{{{{{{{{{||~H}t9~I~w>~y?~G~z6O~U~M~E~;~N~s}N~J~ɾ[~W~d}g}F~^}ɶY};~H~{;N~>~A~y}Źg}~;~9~?~J~Bj}M~k}f}g}:~ɺP~Y~K~R~I~e~Q~øW~=W~W~J~|}R~A~F~h~I~F~T}||p}a~h~M~j~i~Z~S~F~Q~t~X~O~K~x}yD~M~M~y}Q~}}h}G~b~X~G~~}f}]}k}L~J~H~G~M~D~~}x}}E~K~G~|}E~d}u}I~k~DCk}zzzzzzzzz{{{{z{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{z{{z{{~{~{{{{{{~{{{{{{{{{{{{{{{`|{±{i|u|{||||{|h}P}b}Ǒ||||||||||||||{{{}|||p}q|{{Ǜ{{{{{{{{ֶ{{{{{e|؁|܆|t|{{ϳ{ĩ{޷{{{~b|f|w||q|w|~|ņ|LJ|||ѕ||||`}j}||||{s||ϔ|]}f}s}w}x}|}g}|Ж|b}l|y}{}~}}}}}}ſ}Nj}В}Ȑ}}}}W~Y~~X~}}V~W~X~Z~Z~\~\~[~]~^~_~_~^~b~f~d~e~h~h~l~m~p~r~o~k~g~c~`~[~a~\~k~n~p~r~q~n~p~u~y~{~x~y~v~m~i~i~m~p~v~r~n~n~n~l~l~h~d~e~`~b~a~_~`~X~}ő}}}}}Œ}}|}w}}l~r~ҕ}p}||g}k}o}n}q}r}l}l}l}p}}}{}ӂ}ͽx}Ķv}µt}ɻs}Ķr}y}y}q}n}r}m}g}g}k}f}h}l}p}j}i}h}f}d}f}f}b}e}a}֬|ÿ|||||Š||ǣ|Ӧ|ϥ|ġ||||||[}Ҟ|{|{Ŀ{ҿ{{˼{ƿ{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{U}V}Y}C}]}N}O}R}l|E}c|<}A}F}}||{{[|b|||u}҄|Q}q}V|}|{|a}||ޕ|c|v|J}>}M|:|;|z{@|zL{]{zzyyzzytwzyzztv{qr{zzzhi{~{{{l|{{{{ؽ{u|||{~|mo|{{|{{m{tzzjzozz߮yzyz}|{zvzyȞyyyۗyſyyrzɊzl{m{lzyz{zѰzf}{5L~C]~W~};W~Ż[}M~`~9q~|7y4T~7F~8FBl~Z~|Ax1V~^~B~P~ɿS~P~t~b~5]~Bn]QJAJ7|8LZ~SEb~e~Jm~O~A~C~`~_~P~~=~<~K}:}j}f}?~l}r}=}m{z8}R~b~?~F}S~G~t}|}b}_}`}H}J}C}]|1}R|B|g{X|?}A}l|>}_||i|E|h|C|d{p|d|A}[}\}f}G~x}F~D~k}o}]}9}4~4~5~1~R}m}E~J~O~P~L~E~|>~B~A~E~G~N~O~L~·W~Q~N~R~\~_~b~b~[~\~`~^~_~e~h~c~]~d~d~`~d~g~b~[~ȻT~[~Z~_~ȻY~\~X~ɻW~ǺU~S~P~õS~R~S~U~ķS~ǺU~ȼV~ǺT~ƹU~ʻT~οT~W~U~ϾQ~S~T~Q~Q~S~ϿS~˼P~S~S~S~V~W~S~˼P~ɻR~ɻT~;R~ǸP~ƸP~W~ƹR~˽R~U~µN~µN~ǹP~õN~M~N~N~µN~µN~ƺO~ķP~̿S~@~˂|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{þ{{¾{{{þ{ê{é{¿{{{{{{{{{{{{{{{{Ϫ{Ջ|b|X}zϲ{z{{{nn{zzmm{kl{zzznm{qq{rr{ss{rr{zu|zz{χ|o|z{yy{{{{zzzzzzzz~{{{{~{{{{{{{{|}{~{{|}{{{vv{{wx{zxy{~{|}{{{{{{{{{|}{yz{zzuu{st{vw{vw{{xy{{{{{{{{z|}{{{{{z{{{{{|{{{zzƬzzȰzŭzxz{{{||}||{{{xV|yM|y|ͻ^}ռ_}t7~D~G~H~W~x}a}P}h}B~q}G~L~B~T}]}s}k}}7~=~|4~C~e}F~<~y7~l|;}Z}O~F~T}R}||~;}ϺN}Z}X}t5~P~M~Q~d~b~}:{:{~|G~`~q~f~H_Uc~EJEQ~Q~b~Jl~Dm~{~j~W~\~n~b~c~f~}^~R~R~o}r}E~D~|E~ɀ}G~|X}V}i}n}b}z<~O~G~M~M~~F~N~p}[}ח||g}o}F~D~{@~R~K~Ļc~g}j||v|zzzzzz{{{{{{{{{{{z{{{{{{{{{{{{{{{{{{{{{{zzz||{{{{{z{{{{{{z{~}{{{{{{~|{{{{{{{{{{{g|g|o|{{{{Ǵ{{|{{{~f|L}U}]}|m|{{{{||||||{}|z}|{{|||{|{{{{س{{{{{{{ȵ{k|||ޓ|`|p|s||{{{{{Ь{۳{{{}a|s|o|l|k|u|І|R}|_}}S}ʏ|W}z}}}j}|c}|z|w|||||i}x}y}]}||Ž|ď|g|s}u}}}z}}}|}}}}}}}}}}Z~Y~X~}W~X~V~W~X~[~^~\~^~[~Z~]~\~b~^~]~^~c~f~c~c~e~d~f~b~a~f~f~h~h~h~i~h~f~i~h~h~h~o~q~s~p~p~n~k~m~p~r~o~p~l~j~g~d~`~[~Y~X~V~}S~}}Q~U~W~~R~}|}|}}}}}}}|}x}}}x~]~}|||ؾ|||||a}c}b}h}u}j}g}l}j}h}j}h}c}g}l}n}q}d}b}b}||||ޱ|ڱ|ҭ|Ħ|ǩ||¾||˨|ʣ|Ǡ|||ѥ|ϣ|||||||ȟ||á||[}Z}X}|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{d|i|H}I}>}H}I}K}U}I}S}C}x||K}x|T|R|n|b||V}]}\}v}L}a|ނ||k}ۊ|`}V}M}x|q|z{~{q|e|V|f{u{M{͔zשzyyyyyijzz{{|{z}{kl{zzzzz{{||||||||{{vy|{{|{{{ězyhzٱyzzzzzyԓzhzbzzqzpzdzyWzuzzȅzhzyyڰyz|5}R~9}7_~^~e~b~G~H~h~=g~:W~[~b~T~9>?L<85B~J~H~Z~Ž[~{7l~J~_~8?Nrj~c~z2d~ODIKDTXDMRPjPE{:?g~M~U~i}B}V}U}f}P}S}W|N|D}:~F~I}A~L~;~n}y}\}]}Z}I}Y}?}1}R|X{8|f{E|W|k|C}q|h{f|I}M}>}}=|]|G|E|:}@}c|E||{=~C~S}8}h|G}I}3~1~.~F}K}9~G~L~D~q}q}}>~s}n}B~O~O~Z~V~øW~U~M~Q~`~`~_~i~f~f~f~j~e~e~k~g~l~b~e~b~]~˿V~^~Y~̿V~L~U~M~O~ǷQ~M~Y~̿V~ƹU~O~P~Q~N~S~N~ķS~V~P~õQ~ɺT~S~S~V~S~S~ͼP~S~T~T~ɹN~ƸN~O~̽R~U~U~ɺR~ǸR~ǸR~ŷQ~ƷR~ʽT~ƹR~ĵP~ķP~ɻR~ƸN~M~ŷN~K~K~ĶN~ŷN~E~l}?~G~=~J~C~C~|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ҟ{¾{{{{{{{{{{{{{{{{{{{{{{{ɨ{ݼ{ք|ʾk}`|a}l|{߯{{{{vv{{zzzzzzzzzzzz{c|z{t|z{{{{{{zzzzzzzyy{{{{{{{{z{{xy{zz{{|}{}}{{||{~{{}{{{{~{{{{{{{{{{zzzzzwx{{{|{yz{xz{|}{vv{xy{{{{{{zz{{{{{{{{z{{zz{zzzs{{{{{}~||||{{{{z{z~w{ȑ{c|x|;~E~źi}w}}}}|}q}@~:~=~F~?~[}q}?~@~δI~F~N~^~g~ǼY~AG~C~N~żX~>F~o~f}U}?~M~?~G~<S~S~}=i~k~Aw~O~Ǿ^~b~`QKCv~Hv~`~l~h~t~n~~v~p~f~Y~Y~ɿd~X~ļd~Ƽb~g~V~}}J~}G~}D~T~R~X~S~Ӂ}||N}L}K}³[}O}\}s}a}Žr}d}d}{}~R}^}|b}]}e}I~p}h}n}|B~w}d}x>~G~G}~M}j|{zzzz{{{{{{{{{{zz{{{{{{{{{{{{{}}{{{{{{{{{{z{z{z{zz{{{{{{{{{{~|{~{{{{{{{{{{¼{{{{{{{{{{ǭ{x|~||\}U}P}̛|؟|ګ|||{||{{~||I}{|H}|{{{|{{{{{|||||{}|{{{}둀͠|{{Љ|i|d|h|{{{{{{{δ{{f|{j|p|{i|y|Ĕ|֝||a}f}j}z}f}||ٞ|`}|w|z|}|||R}b}v}x}b}Ԓ||||f|}~}»~}Ľ}Ɔ}ъ}ޑ}ӌ}ٍ}׍}ʈ}{}ˉ}ݑ}}}ۑ}ߐ}ݐ}ޑ}ߒ}}}}}}}}}}}}U~Z~U~}S~X~\~^~\~Y~X~}]~Y~\~Y~Z~[~[~\~^~`~`~b~f~d~f~e~`~^~`~^~`~_~\~Y~Z~[~W~Z~Z~Y~T~T~T~R~~O~P~S~V~[~^~^~~S~Đ}}|}y}x}q}v}{}}o}l}x}_~ʊ~t~͖}|ɵ|߾||ͤ||ݭ|||b}{}j}h}f}i}e}}_}||ڵ|׷||b}Ӷ||î||||||ũ|ӯ||||ѥ|ɞ||||{||||ť|Ȣ||О|Ӣ|Х||}U}|S}~T}͗|u|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[|i|L}N}E}H}@}>}J}S}\}O}N}Ϳ[}||n|c|j|P|{|V}W}c}t}U}ր|X}J}Y}|m}L}h|N|\|G{v|t{K}{}{zzxyyyz{zopzyyzzzmn{z|{{{{{{{{e|{{{{{{{{{|~{|~{|{̙|v|{{{zzzz{zr{zǦyҹyӉzpzĐzܩyyyuyzҎy\zuzҕzzy_zjyy:~`~:d~9X~W~|8k~O~a~Y~b~c~]~Y~7^~A~7:F9Cy4öN~P~D~Fb~g~V~L~~:<x3935FEI[T>HBIUJSOLh]MXbFE~~[~s}_}`}9~g}t}]}]}U~i}8~]~B~U}]}g}8~>~h}\}Z}\|J|O|~9|;|a{s|Ž_}I}}T~_}|Y|N|C|o|_|T|U{;}<}9}\{V{M}n}I}8}G}R}O}]}c}:~¿M}M}2~?~q}E~x}p}B~ĺZ~Y~U~Z~`~ǽW~U~ɿ[~Z~T~_~^~`~b~i~c~]~a~b~d~^~f~^~g~a~\~Y~W~Q~Y~Z~W~M~ǻR~S~M~M~οS~õO~T~ʼR~ŶP~´O~M~O~N~M~L~´O~˻P~S~νO~ʺN~ĵL~ɹN~˺N~˹N~ȸN~̼P~õN~ʼR~˾T~ƺR~ȻR~´P~N~M~K~M~L~õO~ŶP~ǹP~µN~M~K~N~K~M~\~K~J~F~t}ƅ|ֺ{|L}~O}|R}ӌ||l|{Ͳ{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{b|{͖{{{{{{{{{{{¬{{{̗{Ɛ{ے{{{{{{{{Ե{X}t}Ѐ|X}o|Ԏ|~|{{{{{xx{zzzzt|ҏ|zzzzz{t|zzg|z{zz{{ut{uu{zzts{~{z{zz{{{{{{{{{{{{{{z{{{{{|}{}~{z{{{{{{{{~{{{zzzzzz{ʳ{{z|{xy{yy{{{|{{{{{{{{{{{{{{{~{z~{zzzz{|||||||||{{{\|w{{ܯ{ħ{|O}׈|l}B~~A~f}|H~P~O~{}xO}J}[}<~ӿO~?~n}T}y:~:~ʶO~BD_AC~_l~Z~S~?~H{8DV~|@~R~M~J~K~Ž`~g~t~p~h~N~BAy~[~W~z~k~s~}?e~`~r~u~p~ûh~k~_~y~_~i~¹e~L~M~c~k~R~x~Żd~M~P~u}{}Ǿ_~I~S~P~I~Ҁ}c}l}d}t}C~K~}x}k}o}ڀ}^}]}r}H~}B~n}g}q}G~w}`}B~?~g}r}l}J~M~y}l}q}u}{}|}a}^}l}X}~B~f|{{{{{{{z{{{{{z{{{}{{{{zz{z|}{{{{{{{zzxx{zz{{z{{{yy{{{{Ƚ{ĺ{{ݯ{}\||]|{{{{{{{{{{{b|k|c|{ځ|ȑ||||҈|||qr|gg|gh|hi|||vw|||s|~|D}׹{o|}j|||||ë|ٯ|ͪ|{|}||||||||||h||~j|q|{|||z|{{{{к{i|p|{n|o|o|||ӡ|~V}X}a}[}Z}|S}Y}`}||||͕|V}_}U}l}|}yB~~}x}T}~Q}~|k|~}}}܂}}܃}փ}ކ}܇}}ż}}}R~P~zJ~}}zJ~zJ~N~M~O~}K~}L~}K~M~|M~}}}}}O~Q~R~R~S~R~U~V~W~X~X~[~[~Z~Y~Z~X~[~]~]~^~`~a~b~b~d~c~f~a~`~_~_~_~]~Z~W~U~U~V~W~~S~W~T~Q~R~U~W~R~V~W~X~Z~]~]~U~S~\~T~nj}}}r}j}m}~~}}v}Ɉ}Z~x~r~ۥ}f}ԯ||͠|ß|٨|||l}e}a}f}c}۷|ܶ|д|||۰|ì||||߷|ϴ|Ͳ|Ī|Ҩ|ҧ|ը|||||||||á|ǧ||ܦ|ܥ|֡|ס||–||||ڞ|ޡ||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{w|I}P}b}Z}{|O}a}Q}E}D}V}H}f|Q}р|Z|i|Y{|^}9~M~`~L}]}p|r|X}b}O}u|`|_|D|t{b|{n{zz{ywvzxazyyyyynmzyyzzrs{{{{{{{{~|{||{α{{zzzztt{{{||Ɣ||{{ع{Š{zzz\|zygz}z_{r{jzyysyyiz`zhzuzbzyyhyyA}f~9O~=\~ʿT~Y~^~Z~_~H~\~^~R~[~|4<~[~=D;`~U~}:[~d~ŻU~V~k~\~ùS~GT<9U]~|2K¹UULr~CNJq~H<@<alWSGH~>MP?Ti~S~^}i~_~h~^~h}W~Z~g~@~P}L}@}u|?}W}\}e}[}?}:|H{n{~{O|L~u~X~CEO~|c{̂zZ{F{jzoz{]zd{g|W|e}x|ˁ|K}?}5}A}>}s|Y}H}T}M}Z}I}A}D}¾W}o}O~W~T~D~ĺY~\~_~Z~\~Z~ǾY~ɿ[~_~\~`~[~c~]~[~]~_~Y~V~c~`~b~`~`~[~Y~X~\~^~\~ƻT~P~ŸR~V~H~˻O~ɹM~M~U~ȺM~K~L~ĵL~ȸL~ƵJ~óL~³L~L~ȹN~ʺM~ϾO~R~˽O~µN~K~L~N~P~ĸR~ĸR~ʽT~˾T~ŸR~N~M~J~J~M~J~J~J~J~J~K~I~I~I~K~C~\}Ľ|{{{{{۾{{{{޾{е{¬{{{{{{{{{{{{{{{{{{{{{{{{{{zz|{~{{{{{{{{{{{ƅ{Ԙ{E|f{Ɉ{{{{{{{{{{{{{{y{i{o{{{{n|{Z|ץ{۾{s|W}|q}ݸ{Ӛ{s{{q|e|ǡ{{{{{z|]}j}\~^~^|zzn|{آ|{{޷{ǰ{{{{{nm{zzzxw{z{{}|{{{{{{{{{{{{{{zz|{{{{z~{{{{{{{{Þ{{zzz{zzzz{{tu{ˬ{{vv{{{z{yz{xx{{{{{{{{{{{{{{{zzzzz{tu|||||||||||ru|{Ҍ|Ŷd}ҟ|}x}߯|}Q~J~F~wA~J~ùo}Q~e~Y~I~K~<>~X~C~?~ȹZ}c}u6~E~\~n~HN@ºY~XKĹ[~Z~R~º^~Ee~W~P~P~m~q~Ո}p~b~r~i~\~n~q~n~ĸ]~d~}P~l~X~c~n~ým~h~k~f~Žf~Z~n~p~S~n~i~ڌ}}p~GLl~Y~T~o~R~Z~~}W~N~~J~}a}P~w}}_}P}X}B~Z}żj}_}c}A~W}j}K~øX~·Q~K~h}k}n}j}y9~ȹ_}`}i}v}l}V~P~V~X~Q~}m}w}e}i}G~[~Z~h}x}v}_}n}g|T}`|{{{r{zzzzzzzzzzzzzzzzzzzz|{{{{{||{{z{{{y{~{{zzz|{{{{{{{ֽ{չ{{{z{{{{{{{{{{{{{{{~|{{q{{{||zz|{||{}p|v|g|s|F}s|i}|||¶|ɶ||{{{{|Ҋ}x}s}||{{{{{||||||||||{{||y{|||||͔|ߝ|||~|Ȋ||k}||̒|S}K}T}ƣ|˻|||w}j}n}s}B~N~P~O~Ȍ}}G~_}z|M~O~Q~P~W~O~O~V~N~M~T~U~P~N~P~Q~R~R~U~T~T~T~S~S~U~T~Q~R~R~S~U~T~Y~Z~W~W~V~V~S~T~U~T~V~W~X~U~U~W~X~W~X~]~^~a~d~e~d~d~d~c~e~a~`~[~[~]~Y~[~_~\~`~~R~_~]~U~[~]~X~}}~O~P~Q~T~}}r}t}ȋ}ȇ}}p}e}g}}~d~[~Ɗ}x}q}x}׏}]~d~_~s}߮|~X}h}d}b}k}p}e}||||ߴ||||_}_}||Ԫ||ʫ||||ק||ʠ|Τ|У||ʠ||ڦ|ަ||Ř|ę||Ė|Л|Җ||ܘ||T}|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{¶{ñ{g|U}]}b}^}]}O}C}[}@}|}|a||l|f|P|H|B}_}O}T}w}||P}T}F}{|s|Z|w|b|d{n{NJ{{ʚzzo{zzzzyyyyzzzz{{{{{{z{q|}};ё|W}b}g}\|T}|{{ƚ||||d|P~Ω|x||{{{Q{p{y|ݡywzG|N{M}zy̒yuẏypz~zkzczyyyԜyfyfy{4~i~c~U~[~X~A~F~V~f~z8~?~Q~P~i}G~H~O~2l~`~]~>;9_~|7A~ƽR~L~X~CWkHD`~W~>>U~5\8CNDAm~~8n~EgGQBLa~ZHAB?t}M~Fl~WIu~`~8:h~J~T}L}6}^|d|N}E}Z}^}K|Z|I}P{O|Hf~d~s~==}>Fh~m~l~»X~B}pzԲyyjyG{b~ԋ|k|w|y|{;}w|A}P}E}[}X}r|G|T|K|R|I}k|B~M~J~źY~ŻX~]~R~S~ùX~Y~S~¸V~U~V~T~¸T~S~X~`~_~^~\~`~b~h~^~`~\~^~^~\~[~\~W~N~ĸR~ǹR~ķP~M~S~L~P~ȻR~O~I~K~ȷL~˺M~ϿM~ǷN~óM~N~U~Z~S~Y~[~ŹT~O~O~P~K~N~M~P~¶O~O~M~N~K~I~J~H~H~G~G~G~F~H~K~F~܉|{{{{{{}{ts{vv{{{{{{{{{{{{{{{{{{{{{{{{{{z{{{yz|{zz{{{{{{{{{{s{T{r{a{\{j{k{{~{{{{{{{o|X}|}{{i{ԓzzzr{U|{{a}}@}d|{||}}9R~y|j}k~=|7;Sʛ|է|î|Ƕ|h}k}ڌ}i}v{{{t||h|{t|ߧ||Ϻ{{zpo{zxx{~~{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{yy{zzzǤ{d|Ƣ{{zzz{z{{{}~{zթ{{z{}}{xx{{{{{{{{{{{{{w{zzzzzzz{|}||||||||||||wx|{{m}h~>K~J~b~]~p}`~V~}W~V~y}f~DS:?ʻT~µN~I=~n}@~l~>=z5m~\~t~[~<j~Bj~R~l~e~]~e~Ⱦa~l~J~p~f~o~`~{H~|}ºa~^~IP~\~d~S~P~O~X~}~q~i~|K~^~V~g~Fy~M~_~s~l~[~~;v~Z~Y~Q~J~R~S~U~U~V~U~S~}ց}s}}A~^}{?~{}ûk}i}M~a}V}~>~X}^}~B~ͽV~T~c}S}j}j}y=~y<~m}|\}~|t|r|xH}́|yA~zH~^}~A~x}~}z}q}Ži~S~{}}}\}]}o}|}z}c}}}Z}Y}||{~}{zzzzzz{{zzz{{{{{{~{{{{{zzzzzz{{{`_||{{wu|Dz|zz{{{{{{{{{{{||||||{|{{{{{|l|Ի{{{{|K}Z}|{|rr||}S~u}|||{{{yy|{{|]}Y}|{{{{|{u|\}i}|||{yy||˥||||||||||}c}|c}||Ү||s|||R}|||ײ||||o}||r}~~v~n}ߣ|\}i}j}||C~I~K~P~R~R~L~}}N~N~N~M~~M~S~W~}ݎ}W~W~X~Y~a~O~O~~M~}½~}v}x}|}Ʌ}҉}ߒ}}}}}T~R~P~U~X~[~Z~W~W~W~X~X~V~\~^~`~c~e~g~f~e~a~`~Z~U~W~X~R~T~S~U~~R~}}Ǐ}}w}w}t}u}o}z}s}u}x}t}k}m}h}e}|}{}v}x}w}d~i~ؕ}U~U~}y}|j}k}i}r}ݚ}g~h~ї}k}t}d}m}d}n}j}a}ѱ|||ܫ|_}|d}||߭||ԧ||]}||ާ||˛|Ț||آ|Õ||ԟ|Ӟ|ӝ|||||{Q}|я|R}|w|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{h|K}H}]}M}H}{|s|~|m|E}k|o|m|d|g|Q}d|O}U}[}J}|j}t}P}L}P}|j||»l||\{v{g{|z~{{|{{{zzԗzn{}{{{{{{{{ʶ{^}x|q|ڏ|N}[}L~V~E~b}K}՘{j|l}}d|{||T}i}ûv|x@}|s~}|{zr|zzm|W|Z}azM|R}h}C|qznzчzʔyezuzΎzzzƓyzyyƕysyxJ}żZ~;W~z4ɾT~M~Z~N~=~M~=~F~:~;~{9~M~\~\~M~Y~9T~>z7:`~c}K~j~a~5HC;A~3x19@A~c~J|4e~d~;RUD>A@IVQXFS?SRHR~7ANE~h~e~==>[~C~Z}U}E}T}c|M}¿U}9}7}S|U|R}{y|PCx~|9Ap~}7<C_~f~JET}zygzwzD\}w||{|{{T|>}\||=}Y||Z|d}I|i{{Z|]|c}M~O~T~ɾX~[~ɿX~X~Q~V~S~S~ƺW~øW~Q~ĹU~U~Y~[~[~ɿV~[~a~[~^~^~^~[~[~]~Y~ùS~Y~Y~ĸR~¶P~̿T~S~ͿS~V~M~K~µP~N~K~M~ǹR~W~ǹP~M~Q~P~X~]~ʿX~S~T~R~P~K~L~M~M~M~P~N~N~N~÷Q~P~N~K~J~H~M~L~ƹP~e~G~G~X}{{{{{{{~{yx{xv{xw{{{{{{{{{{{{{{{{{{{{{{{{{z{о{{{{z|{zz{{{{{{{{|{zъzzzzU{xT{p{{{q{l{l{g{^|M}g}H~|I|zU{̇z|z{zzz`{<}e|@~<}{{q|l|x|G~K~M~e~j~}3Y~ɽS~v~ۀ}}||ؽ|||ş||p|{Ϩ{||{{l}t|~e|~j|{yz{xz{rs{ss{{{{{{{{{ž{{{{{{{{{{{{{{{{z}~{yy{zzz{{d|c|{z{{{{{{{{{{{{{{}~{{{{{{{{{{{{zzzzzuu{zz{||||||||}}||||||}}||d}kC~;Z}e}|m}I~j~Ev}K~?OTL;z8e~~6E`~˾T~C@Cp~q~M~j~·[~c~i~R~Z~W~R~\~ǿx}d~n~r}}@^~i~`~l~U~Q~b~m~g~_~x~e~y~`~\~u~j~x~|~w~a~l~o~i~j~y~p~Ǿc~f~P~`~V~O~X~Y~T~[~ȃ}ߎ}~}N~}^~H~g}z}Q~}y}}L}{}J~{}v}}O~D~a}R~F~y}}l}w}o}U}Ւ|ۏ|U}Y}a}y}Z}f}g}}}G~~}T}t}l~¾Y~{F~m}}R~߄}g}E~u}}}E~d}S}Q}Ƽm}|}^}R}X}z}h}b~R~k|¹zz{{z{{{{{z{|t|{{{{z~{{y{{ء{{{{|z}{~~|||ɶ{{{{{{||||||œ|Ф||yx||pp|gf|bb|cc|{{{{{qo||}l}|ҕ|і|ق}|{{{{||||||{{Ͽ{{{{o}k}||z|}||||[}a}}||~~|ٛ}z}|||Y}`}||{|{|||ѓ}b}||ͬ|Ը||l}h}ʛ|Х|ȣ|߮|ϣ||۳|կ|Ԯ|ū|||ͮ|}u}g}|a}d}||t}s}y}|}}H~}v~W~}~}w}y}x}h}w}|{}Ƀ}}ʆ}׊}ݗ}ي}ʂ}~}}}|`}|e}e}c}e}b}c}e}n}o}p}n}s}w}|}{}y}x}v}z}r}r}y}}}}}}Ŋ}}}{}x}v}v}o}n}k}n}s}s}t}n}n}o}l}m}l}p}m}j}c}i}m}t}q}j}`}|]}y}،}x}}}z}x}|}y}w}t}~}o}|~g}||}a}n}{}}j~}¼}f}j}c}c}e}`}a}|\}||^}W}M}|ӓ|ު|||Y}Z}|Z}||ڠ||ԝ|||||||ؔ|ۘ||Ê|ޖ|ü|{{{{{{ú{ż{{{{{{|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ʱ{L}S}Q}?}H}V|J|I|X|m|_|]|o|j|q|]}=}R|:}H}N}|ݗ|]}|K}L}c|}{|d|܉{q{z}{{zzh{~{q{zyǻz{{˴{У{c|ߦ|ߟ|}}~R}b}}|e}r}|Q}c}a|Y|p|a|V|^}{{Ў{zz{k|{׀|P}r|{{|{{`}K}M}g|^}{:~U}a{vz}{zޮyrzэzzqzymyry}ypyyԫy|z}8<w2^~G~;O~=}W}K~X}|8~|8~>~K~M~e~ŻT~F~T~}9T~@}8d~b~7~?~N~:b~6KɼMBD><k~`~@Y~ĻT~ŸN~j~Hľ[}<=@K~Y~DZPF]~b~^~ZQQ~h~~Έ|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zz{{{{{{{zk{zzzz{zpzzΖzz|O{q{;~G~p}^}_}ԗzvz̓zuzz|z[|O}_}A}D}z6}Ý{՛{ަ{{f|ր||Ę|͘||||n|h|p|||{vx||ª||v|٫|m}|ǒ|||p||{{{{oo{{{{{{{{{{{{|{{{{{{{{{{{{{{{{{~{zzzzzz{`|i|k|{{{{{h|{{{i|ʯ{{{{{{{{{{zz{{~~{zzzzz{z{{{||||}||||W~{~Fl~Y~X~~l~o}i~gQ~z~IHݟ}j~V~K~f~l~H~F~k~p~G<Hy=~d~@;g~^~öS~W~o~¶W~l~>t~T~x~c~q~d~l~c~^~J~S~X~M~Ľv}}?G}Bv~^~j~^~P~K~Y~e~k~\~ҍ}f~|~Hz~p~q~q~j~^~s~~q~v~f~`~d~W~}Y~c~`~Z~S~^~X~g~»e~f~a~n~l~U~P~N~Y~ڂ}~}N~v}c}}G~R~_~W~Y~^~\~T~R~ʁ}K~}}q}|v}o}q}}g}r}l}}c}u}b}ߜ||Z}s}|P}`}b}{{~|B~n}M~S~W}Q~a}|x}k}w}U~S~Ċ|k}a~P~b}{zzzzzzzzz~{||z{~{{zzv{{u{p{{{įz{{Ԍ{^|j||||mm|hh|nm|qp|cb|[[|||~|Ϳ{}||jj|{{|}rq|||{{{{{|{||o}Ѱ||||{{{|{{{|||||||yz|st|ss||}}ȶ|З|T}\}a}X}{{{pp|n}֘}}v}j}P}ś|{{{|{x||}}||||||c~~e~_~Ȩ|t}o}Xݠ|×|š|||}|ܡ|}}}ص|||X}{|j}d}g}e}|~\}e}ϱ|ͱ|p}t}Ľ}ǿ~}ƿz}||||i}o}s}t}t}r}|٦|||||߬|||f}t}t}s}w}m}m}n}n}u}x}v}t}v}{}{}{}w}v}x}{}}}{}w}t}q}q}m}k}o}u}v}v}x}|}}}|}s}t}w}q}o}m}n}k}¸r}w}}Ё}z}x}p}m}p}u}}Ι}}t}r}o}||p}z}}k}l}j}g}Ҹ|||||a}}`~e~؝}h}^}a}d}a}f}g}`}|||s|o|́|Ԟ|֥|ՠ||њ|ɗ||||||֠|ݢ||Ô|ʓ|ޚ|ߚ||w|||p|{{{{{{{s|z|ߙ|Ŏ|x|r|s|q|n|q|{{{{{{{{{{{||{ÿ{{{{{{{{{{{{s|u|{{{{{{{{{{{{{{{{{{A}]}^}J}E}M}V|{u{{ς{I|V|Q|Q|b|e|p|[|W|b|q|h|l||E}x|A}T|ۖ{M|܌{_|zj{u{w{zzzěz{̵zzzz{{z|}R~\~|>[~Ľt}ڃ}u}}Y}_}|R}a}|~A~}W}N}z6}e}|r|i|||{{n|}F}t{{{u|g|^}~<~h}Q}z<~M~a}d|ێ||L}c|ךyvz^zpznz͏yٝyЖyyyğyyhzp|P~z26y3´L~\~>~ɻP~õN~·S}E~l}z9~I~L~T~K~{?~j~h~Y~b~}9j~X~E~7~z3=d~IKKPKKCE~]~@6{5Y~U~K~>~U~Z~QV~IGV]=AP?QSZ~X=:~ƼX~BȾX~d~_Q~n~9ǺW~z9<\~;[~ŷW~M~`~Ƽ^~a~y;V~j~M~}`~t~M~J~K~}x~[~o~o~Q~_~~~M~y}O~R~e~DLz>l~a~f~X~g~~n~H~Y~k~e~{Bd~a~d~d~}}M~d~T~\~`~v~U~a~_~L~V~ûg~b~ûg~r~`~a~^~U~}b~h~p~n~Z~Y~[~V~X~W~a~P~l}S~{}|}~}|W}}S}T}t|֏|z;~P~̗|{O}|{|{o||[}y}y}X}|D~o}t}~||y}w~p~q~i~p~\~O~}S~g~p~ûh~t~ý|֕|t|װ{||j|{zzŜ{}{˦{˦{{{{{{zzzzzU{{۱{|щ|ω|r}k|h|g|~|dd|{{}}||st||}|{{{{z{t}~{{{{ҽ{{{{~|{{{{ѭ{w|}|{{{մ{{{{{z|{|||{{{{{r}}X}||p|o|F}U}λ{|{{||||||||||{s|u|}e~Xڄ׍~|~}}|||l~r~[t~}q}k~}|{ں{{|i}a}||^}Ԝ|{|k}~Y}ќ|Ǔ|ٞ||Ԩ|ܳ||||d}h}^}ҕ|~|||ݡ|j}o}r}s}c}Û||||||ݫ||ޡ|ޥ|c}p}q}o}k}h}e}m}q}t}m}g}j}h}i}g}|m}q}r}t}z}z}z}~}}}x}g}a}f}j}r}v}p}u}v}w}n}j}u}q}؅}x}o}v}|}y}t}u}o}u}y}t}t}n}g}k}|ݸ|ٻ|||||t}x}j}b}||ܴ||б|||ģ|||m}{}}}}ÿ}^}U}g}W}|՜|џ||v|s|s||||Ǒ|Қ|ڜ|ݟ||ۣ||ܠ|ӝ|||֗|ƕ|ƾ||||{{{{{{{{¿{ǿ|Ⱦh}S}Z}|W}|ޞ||z|{{{{{{{{{{_}\}||}|{{}d|{~||{{{{|a}ؙ|{{{{{ɿ{{{{{{{{{{{{{s|C}d|B}@}<}K|K|t{̈́{m{{{W|{Ԅ{{]|O|{x{W|{Z|h|b|l|q|w|Ƃ|}S|b{zn{r{{zzzzz}{{{{{{ʾ{t}\~|<K~h~þj~}^~}Y~|||}t}S}I~}|}j|֔|V~H~x||К{o|{{Ա{{|l|˿l|v{Ǔz|N|||b}C}T}Ո{]}j}{m|v|S}T}N}{}zÿzͅzhzc|̗ykzlzʕyyyyV|S}F~6e~x38W~X~z0´J~ƹN~e}p}I~K~J~]~e|µQ~O~y6|9l~[~¹Q~f~@H~`~=ECC?\EP~:~|1?FF9Z~B[~\~@x~n~j~q~~~LIOKNüWÿdg~V\[IPAT~v+x/:f}W}X}[}N}H}_|M}F~n}ƿZ}F~K~n~h~<F{~g~}9v~[~[~Dy3}8z=~?X~j{C|d~x0a~}„|q|{||||z{|{xx{zzi|i|||l{x|^|zm|ƾY~Y~Y~[~ǽV~R~Q~Q~I~G~P~R~O~L~Q~L~C~Q~R~J~R~Q~ǽ[~[~^~^~c~`~^~]~Y~Z~U~P~ƺR~S~·O~T~N~P~K~F~K~H~M~ȺP~Q~M~G~M~P~N~K~K~N~M~O~T~S~P~R~S~S~F~D~E~E~G~G~D~D~D~B~D~E~I~:~}|ٲ{{{{{{{{{~}{}|{~~{{{{{{{{{{{{{{{{{{{{{{z{{{{{{{{{{{{{{}{zzzz~{zzzӒzcztzuzzztzzZ}S}?}L}G~9~B~P}|w{h{~w|‹|^}U~\}{{f{k{{{{{Ը{{{{{{{{f|n|ā|q|Խ{{||~}|{y||x||v}c}|z|{{Ь|{{{{{{{{{{q|l|߼{{{{{{{{{{{{{{{||{z~}{{{{{{{{{ݐ{[|j|{{ò{t|˱{m|t|z||K}o|{{{{{{{zzz|~{z{{{{{{wz|||||||sv||~|ru|{{{y{||rcz~XqBK^TM~:?EAs~f~d~U~I~F~ǽW~I~Q~?Cg~ɻU~R~BLBf~C~G~b~k~n~`~H~J~{E~T~a~H~]~Kf~k~a~e~o~}~}Z~r~z~}K~T~[~l~Ļe~X~`~ýb~j~g~{}W~_~o~i~k~l~V~Z~N~[~e~΄}ȿe~o~O~d~e~V~d~р}Z~Q~}O~l}L~M~܂}}C~U~ºe~Y~}P~I~X~]~NJ}^~N~|J~}}]}~|z=~O~c}ýt}˾l}x}j}^}á|ٝ|w}C~V}k}m}L~؈||J}k|Ѹ{{n|m|r|ʋ|e}I~~?~~S}ɛ|ު|p}h~BW@O~[~N}|l}n|ߋ|Ǿz|y@~¼u}\}X}ýo}Ǿl}J~|E~S}b}s~I~_|ۻ{{{{{{zy{{{{{{{{{|||u|{{{zz|}ٯ{~{{{{{{{~{{{{{{{{{{{{ȶ{{{||||||{|{{ff|||mm|ff|kk|x{|{{{{DZ{||H}}|{|ڋ|x|y|J}h|{{{~|{||||||||q||̸|ز||||||||||Ф|ơ|||z}|Ѷ|ϵ||||ܞ|̧|[}Cʼn}þ{}u}q}|q|l}X}ŏ||ŏ|ޢ|||}Y}|ѡ|||ۚ||{p||ۦ||i}j}p}||||}||||ș||||o}s}k}m}i}||g}f}c}j}n}k}n}p}r}r}r}n}q}w}r}p}w}s}o}^}~[}[}a}g}o}h}̓}ه}r}s}k}{}v}w}}}|}}{}z}w}g}_}|ߩ|||||||||ڼ|տ|ؽ|}g}q}f}||~_}|ʩ||Ȫ|ԫ||||ǜ|_}c}b}}}ע}x}h}Z}V}ؠ|ȕ|||c}||R}H}S}ї|||Y}R}||T}٘||{~|}|{{|{{{{{{{v|ɽe}n}m}\}||Q}|w|t|{{{{{||U}Q}q|g|ǹ{{{¹{{a|ܧ{{b}o||o|{~|]}E~t|m|{{z|||||ʻ{{{x|{{{{{{{O}F}K}D}q|]|_|_|{~{e{i{x{}{Z{j{W{x{k{m{Ϝz߸zs{~{z{{z{u{b{p{n{{{{{{{{{{{{{{{{{|||̫|F~úR~H~~~R~l~n~y~|}U~a}͖|b}s||^}|w|֑||x}u}{{h|{}|r|u|{l{ߒ{{P|{O}{g}g|\}R}ÿ{ч||K}|{{ǤzzW{`z_}zR|ԈzŚy`ztygyj{w|B~\~~829;=81z.=~C~D~E~P~ʿZ}J~b~j}G~E`~I~`~I~_~z1X~@QGBƿUFM5z-<LTļRDJN}7>;@i~N~\~T~m~f~d~a~VL};COqKTYLW~B8:]~A~S}b}M}K}C}m|c~~}R}r};~T~Z<~6p~@HCb~b~826m~U}|_{|H{N}z67J~|p|{|||ȝ|||uu{zz{ɇ|||{~Q}zב{d|L~R~W~W~ȿV~L~O~P~Q~J~K~ȿX~O~M~P~źV~Q~ȼT~ĸS~ȽV~ȿX~¸V~Z~V~\~\~ǽT~W~U~W~P~ĸQ~O~M~K~N~I~K~K~P~N~N~H~{;~O~ŸP~S~M~J~I~G~D~E~H~G~M~J~O~M~L~J~F~E~H~D~C~F~C~C~F~D~D~D~C~@~m}r|{{{{{{{{}}{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zz~{z{zzzz{zzܚzzvzz_zƉzhzeznz{R}A}K}Q}_}Z}p|{g|{{Ζ{t|ɂ||{̨{{{{{{ֽ{֩{{{{п{ی|o|}{{{ޗ|X}s|{~|ג|¾|zƢz|{|{{|½{zͿ{{{{}~{{{{zz{{{{{{{{{{{{{{{{{{{{{{{{z{{{ï{{{{{{e|v|{z|m||{n|Ҁ||P}H}]|~{q{{ͨ{{zzzz|{{{{mo|il|{{vy||¿}Ҍ}r}||~|||{|tw|z{|wz|||}~||^IP\``^]@^`~̿V~z:e~\~H~l~R~{<~a}E~s}P~5b~{>~f~`~h~MȾa~Z~zC~Z~<T~~}zE~ĺt}U~Q~U~K~{D~¹]~[~b~g~X~I~ނ}I~X~U~{=h~\~j~ɾ_~T~}g~ľg~EGLKY~u}q}ʾQ~M~c~M~||w}}}ǿd~R~k~j~e~]~`~X~}S~}H~Q~P~g~M~P~Y~L~}T~V~M~}}}K~΁}L~[~}zD~|}úr}m}u}X}n}r}_}ǽs}e}q}}}O~J~@~_}}x@~U~w}}݀}X}[}b}Ã|T}S}{{{ÿ{{||{¸|}l~t~U~ù^~]~]}Y}z}f}`}V}|||f}x}H~w}\}|z}M~w}U}ѓ|||`}T~v}ڣ|ܱ||{Λ||`}]}͘|S}{{|m}||z|t|{{ż{{q|{ſ{{{{{{{}||z|||{{g|w:};}F}W}O}||¤|Й||kl|||U}Y}ě|||{{{{{ϕ||ɥ{q|n|В|ؓ|ف||C}w||||||||||||||||||~|~|p|||©|ѵ|ܵ|ި|||{y}|||||v}ݴ|||Ȳ|y}|}E~z}c}F}c}S}|||||b}d}d}|{}h|q|k|{l||||޶|||۳|||~|z|z||ʚ|ŕ|ա|Ӧ|c}f}h}q}h}c}Щ||k}||m}r}p}o}l}j}m}o}||j}e}`}a}[}a}W}~X}`}a}g}v}n}|}z}o}ŏ}}}}}}|}}}r}|||||~c}ܰ|||i}|||||޵|ۺ|Զ|޻|s}}e}ͬ|||||ʥ||̧|||Ü|||[}Ν|b}v}`}}Ԗ}^~}x}p}י|ˋ|o}|ٌ|߁|{|G}Ȏ|ߔ|J}x|q|||ԇ||V}ʓ||}|z|{{X}{u|p|{{{{y|b}b}˿e}Z}W}|W}U}|y||ޛ|}U}Ď|ɓ|Z}S}r|{{Ī{{Ź{|]}ٛ||Ȕ|Ε|X}g|}|r|ܓ|l}m}o}f}ƽp}f}v}D}J}U}||S}T}|l|{{κ{q|~L}@}L}7}<}F}F}Q|D|Y|[|h{h{\{_{^{}U{p{|zuzzzzzzzzzp{{{p{l{z{{{||{||||||||||||]}h~d~_~FO~Eu~l~H~z}q}ӫ|l}܏|{`|{\}|Y}q}y|t|L}Ӑ|{[|{\|l|{{}X|{W||{l|j|q}j|d}P|b|̇||{{޽zZ|zG|Y}>}хzǿc|ybzkyjzm{`|L}^~8O~^~9ôH>J<ɻM~U~>~=~F~R}J~·R~L}B~T~9B|5g}G~J~@~SƽUGUSKǾSSEBIOMLBCOLDTM@~[~E~J~W~q~k~R~r~EBS?lOk~}57<Cc~g~G~N~C~k}a}D}w|_}|?~P~x6~νV{k}z1B8:D|=a~~6?|9<CK~|~{N{¸Z{A~T~E~ٔ|r|{|||ܣ|||zzk|x|{{{{{Ʋ{lj|B~O~ĻT~S~ŻT~P~Q~M~M~N~M~N~P~̿V~W~R~S~ùT~W~źS~ĹR~R~ŻU~M~¸R~Q~øT~ɽT~ǻR~O~ĹR~ƺR~Q~¸Q~P~N~O~M~ùQ~L~M~K~J~t}K~νM~R~˽R~M~E~E~A~D~F~D~H~F~F~K~Q~J~L~C~C~A~B~B~D~F~D~E~A~_}zG}a|{{{{{~{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzq{z~{j{zywzxzkzfzz^zyxczu{j{|T|{S|V|{{{t{{{{{{~|{t{z{ٰ{{{ŷ{{{º{{{ʝ{Y|`{{ڧ{y||ޗ|{u|l{o|c|_||P}]}{{V}{|{z{{{{{{z{{|{{{{{{{{{{{{{{{{{{{{{{{{s|{{{{{J}h|Ѷ{{ԇ|A~E}u|J}K}\}M}b|z{{{{zvu{{{{lo|oq|~|su|{|}||}W~üc~x~J~||vx|{|Ҭ|}̕}|||{@~O~R~T~}~m~]~R~M~o~P~t~{=DHžYļYOKE<L~t}d~^~u}w}l}|||i}i}k}}c~`~}M~}P~|}~K~ݐ}P~Ľb~O~ކ}|}N~k~}s}ƿq}F~|}J~ûW~]}v}F~l}j}q}c}g}i}|D~I~ϖ|\}w49v}y|m|||c}ޝ||\}{}w}a}l}RLĽm}|{{{ر{]|{~T|~W|d|g~9|8Q~}[}R}W}]}J}\}V}V}p}[}]}f}ަ|ʙ|t}t}b}͎|o|݌|Q~|}P~Ar}|˟|ʦ||V}Żk}l}ό|f}O~v}ȥ|ǫ|ܛ|Z}ܘ|T}e}N~`}|{{n|z||y|x|{{{{|{~|rt||~~||||{[|_|v|N}d}|yz||x|{{f|_|zB}|B}A}J}j|j|n|z|~v|||̐|©{u|l||ʕ|Ő|||||||||||||z|{{||||||||||||ƛ|ګ||||||||Ӷ|έ|j}ơ|â|m}f}T}T}]}|m|ֆ|||||V}i}}F~}}ʒ|p|q|t|ܿ{Ѻ{{|^}g}_}f}b}d}a}פ||||Ʀ|˯|||d}c}i}g}h}c}|||q}|թ|`}h}j}n}k}|||||b}[}|d}y}~E~m}|Ӊ}b~}k}|{}}}}}}{}|||Ю|֯|ݳ||b}||||||||||||||Ȱ|ҳ||a}_}|ӭ|س|ح|\}]}a}|^}و}ҝ|||||_}ܝ|i}y}l}ֈ}}b~}}f}||||[}ʄ|}||k|{{~|Ι|^}~V}ƍ||||ٔ|]}|̎|‹|{{{{q|ƹ[}b}^}R}]}W}S}W}n}^~z}t}v}|\}||ƞ|͝|||Ε|W}ږ|}Z}|ؖ|~O}l|ǽ}|{||e}[}c}a}|t|M}ف|B}b|l|d|K|b|`|f|l|ѱ{{{|k}W}F}F}<}V}F}\|E|[|g|q{S{Æzxzzӧzz܁zyyyyzzzzz{{{{{{{{||||~|||ß||||{|~|t|S}e}?~S~Dh~n~^~|@d~a}r|{ʴ{k|r|G~X~p}~||{{՗|{H}h|b|Z|y{{{{|ș{m|{l|O}}s}O}[}r|b}e|a|{l|]|ûvz||g}T}[|y{O{yӁz]zyu{G|B}T~L~DIy//O@v-y.K~A~`}^}D~k}~8~ͽL~W~^~CJ~e}`~]~H~<ȿQUļTFO[ZPPNAJe~O~VZIYaT_LC~=K~Z~u}M~V~~?g~l~~]~f~iXb~X~fUa~~Dg~V~B~i}a}F~M}X}:~<~O|h|M~U~x4c~D>e~AA>X~NEU~|{¶_{¶I{N{T}}A}{{|j|n|f|ԭ{{zzz{~~|{{{{{{{L~Z~J~R~Q~ǽV~K~L~N~P~Q~N~¹R~L~Q~ĺU~R~W~S~M~ƻT~Q~O~Q~R~ĹT~M~ɾT~K~E~N~J~L~N~M~M~·R~ƺR~øQ~ʿS~·P~·P~o}L}ǹN~U~X~ʼP~J~G~C~B~D~@~C~B~B~D~K~G~C~B~B~C~A~A~D~D~@~~9~|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzz{{zzzi{׏zzzznz|\znzpz}fzƢyΥyuzyzٙz}{ɡ{i{t{l{{{ǥ{{ݲzzh{p{{{~{zz{ѧ{{{{{{{{{d|k|i{}{Ԩ{v|m||{o|zO|W|{Ԇ{w}s|ĩ{l}½|{zzz{{{k|{{{{{{{{~{xx{{{{{{{{{{{{{{{{{{{{{{{{{g|C~f|\|{ܟ{|@}\}D}»`}R}ŽW}M}k|ùzr{ʰ{~||||y{||||||||||}|V~¿}}|xz|st|P~ƿk~_~n~o~I~S~Q~d}x~mb~a~<H~y9{9>^~p~j~]~R~Ѓ}R~u}l}N~q~h~Bw~w~C|~}F~n}l}g}N~W~ҿV~P~}<P~D~}}A~^~n}T~w4f~>ƺ[~ĴS~ǹY~}|}}F~[~c~c~X~M~H~NQefAe~`~j~h~p~_~Y~z}ە}|~c~}|||||~||H~zC~c~P~G~O~t}y}H~F~H~R~m}M~f~g~t~@y}Y}B~Z~I~a}|}m~J~t}}K~V}I}Ǿq|l|<~\~Nj|x||||||||||g~v}}Hw~ͭ|||{{{|{̕{{`|v|?~g~A~R}Y}f}v}}|d}a}Ɍ|ϑ||Q}ô]}{K}S}݌|P}ǂ|j}@~ۯ{ž|~@~`}v}a}Ÿ|m}}A~P~N~[}v}S~S~}}}}Z~L~|\}|i}v}l}}}_}ю|͕|g|||]\|^^|gf|__|\\|||{{|n|a|X|Q|p|b}Ġ{{{{{{V|`|e|e|j|s|m|v||||ş|đ|ʓ|||r||”|˜|||op|ee|ii|sq||{{{{||||||||||||||||||||||||||||Ȝ||T}|Ж|כ|֑|o|a|||}|}||¾|ޙ|Y}Ȇ}}||||Ő}{Ҹ{{r|r}n}`}k}x}m}Ѭ|||¿|ƭ|ŭ|ʫ|ٔ||Y}X}\}||e}|||i}f}d}i}h}f}o}l}|||_}c}f}||~^}l}c}|p}|}z}z}џ}}Җ}W~}Ȁ}f}|ܼ|ִ||ϰ|ر|Ա|ǧ|Ѧ||||թ|]}f}|||׾||h}||||ů|޵|ޮ|||a}i}||}p}e}}G~[~U~`}e}q}`}f||O~Z}y}h}߄}]}d}f}q}ˉ}}t}҉}ׁ}}s|ߍ|S}Չ|o|t|{u|֘|Z}ё|b}Y}u}u}\}|Z}`}|||R}|V}f}U}M}S}P}Q}d}]}R~q}X}|||l}|}y}}}}Ї}m}_}s}}J~g}j}|~Q}|Ҟ|i}o}|c}V}^}i}o}Q};}ɾ[|{I|M|R|V}q}|P}͐|}||g}^}K}B}>}Q}B}8}J|C|P|K|Y{tzzizrzyymzyzzzzzzzzz{{{{{|||z{|{||||||||u||lj|D}K}e|Z|m|N~u~}~W~v~d}Բ{}{j|݀}x}N~~z~ۊ}e{z{{{{{|{~{{{w{z}{z{{v|^|w||L~^|>~Y}|D}q}W}Q}s}k}b|p|B~8~g}W}C}M|y}J|yXziyL{_|G~z.:O22M=:4S~y7~`};~ɼT}N~7~T~67;Y~e}m}B~P~U~RʿNVPX[bc;LJJc~COHýW>IUDJAFg~g}F}B]uD_Ld~Y~ZBU~|/NMK_RP}NGcD_~O~J~z1~I~ùY}c}_~y.L~z6ENIMSǿ`~f~H@R~Ԇ|O{C{C{N{Q|{t{{Ѡ{xS|m{]{l{zzz{{{{{{{{{?~Q~J~O~O~I~H~N~B~E~D~N~N~R~O~ƼR~V~Z~¸S~R~ɾV~L~N~S~R~N~N~Q~K~G~H~I~J~O~K~L~P~N~L~P~ƼR~P~K~yD}[}S~Y~[~J~J~G~B~C~B~@~@~A~A~D~B~B~C~C~@~A~C~<~i}}I}e|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzz{~~{zzzٔzkzrzpzgznzczĨyyyyz{zu{{{s{m{{zt{{{yl{{{o{۪zz~b{{}{zzr{zx{t{r{{ͪ{p|{{{{{L}S}Ȝ{ϥ{ט|{Ԓ|q|^|o{\{z]|e}{d}z{}h|k|zz~{n|m|{o|{{{{{{{{{}y{{{{{{{{{zzzzv{zz{{{{z{{Ʒzޚ{{Q~ľp|||ѓ{ȁ{\|K}f|o|ǜ{٭{`|||||x{||||||x{|y||{|tw|tv|{}|||||}}|}||V~b~~P~}nw~BI~I~Q~Y~^}p}l~s~o~zBw~x~u~»n~Nx~~JJſt~q~~~a~ԕ}b~n~G~Ǻ\~Ǻ\~O~~B~ѿX~?~B~G~I~ɹS~P~K~R~P~{B~S~=~ŴQ~c~U~}S~l}b}E~w}I~I~_}}a~[~M~l}R~\~BKMXJx~k~q~V~y~`~c~e~p~Ɣ}||||||{|Ϥ||}U~K~`~U~X~j~ý}}|k}H~HƼVPM=@b~Y}Ļg}V}k}W}Q}}}L~k~Z~m~a~g~g~{~j~ʋ}w}h}|x}|}ϙ}}ߨ}s}ܠ}o~v~U~}o}}|||||||a}ۧ|R~D~>~s}Ⱦg}l}D~s}c}r}}I~X~u}S~h}\}]}|g}l}l}e}x}a}ۭ|a~żf~ދ}N~||~W}}n~F~R~Z}O~t}҄}}l~O~T~}N~ۊ}s}p}`}w}Ҡ|ݴ||||ӎ|y||·|}|}{{z{|{{{{Е{t{s{K|U|q|{{ʪ{{{{Р{j|o|\|{d|{|||||||||}|i|z||͜||||nm|hh|on||mm|oo||||||ج|է|£||||||||||||||Ģ|||||||||X}Ԛ|֩||ҝ|||φ|g|{x|v|s|v||ؗ|||ߪ||||}љ}}|{|||V}]}Ӛ||||||||ϫ|—|nj|w|Z}Z}]}|a}{}}||y}}}f~b~c~ה}v}|e}|ڥ|ߩ|k}|~d}׳|޹|ӯ|ܺ|||||}٭}ũ}sn|||ϯ|||ܷ|||׳|̧|ר|||ܪ||a}ޭ|ƪ|Ժ|޻|յ||||||Ӧ|̩||ʧ||~}_}h}ӷ|ʂ}}^~W~O~y}\~}G~}O}}}|?~F~t}}{}_}[}|U}Ϛ||l}Â}y}u}}d}||||͔|ӑ||Ս|\}͉|T}|E~|Z}}S}{O}Q}W}h}ºe}f}?~h}U}f}g}j}|~[}d}g}||͢|e}u}R~Ո}N~}}}}}U~_~N~[}ʚ|j|||֥|_}z}I~M~}r}}}h}r|ƽl|p{]{e{zy{p|m|g|a|Ŋ|[}r}C~R}V}Q}C}a|J|n{J|X{zzzzzzzzzzz{{{{{{|{{|||||||||~~|~|{{|w|{||Ė|ݕ|]}}|g|҅{D~½p}}ك}||{{|O}l|~CK~}|h{~zʳyv{{{x{{{}{u{p{{zz{^||g||}{J~e}j}L}D~u}H~}}`}V}h}k}:~j}=}x|ޕzz{b{i{yyD|L}2y0E4ZMEFR~C~`}~7~:~|7~]~<~ƼR~X~CIͿS~8~ķN~=ǻO~T~TYRZ>}5YĽYLGAI{99TBLBGOY~a~\~N~a~\}d~i~Jw~R~LRD~<~;e~g~Q~JGDPb~:LcRTt~LPNw~p~T}B~P=9~G}T[]XN~y5e~Z~z~|zzL{mzԆy߄z`{zڥzۺz{w{z{|e{x{zz{{{{z{z{¼{L~E~=~G~H~J~D~D~J~H~L~P~I~F~H~I~P~H~F~H~G~J~H~ȾT~N~K~B~K~I~J~I~J~L~L~M~L~M~L~H~M~M~øP~o}{|Z~Y~Y~D~E~B~B~H~E~?~A~@~B~?~?~@~A~C~?~|<~\}}|ϭ{{{~~{}|{~}{{~~{{~~{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{~{zzz{}}{{{zz˛zzfzszxUzyӥyyطzzz{{{{{{{zץz}{ţzs{{k{wq{{zv\{zԫzj{z{z}{z{{{{՚{b||o}{{y{܉||[}[}V}|{}|{Q|ƺm|e{~R|U}c|||״{s}p||{{{ľ|Η|~z{{{{{{{{{{{z{{{ʯ{Ͷ{z{{zzzzæzϕzzz{{{ִ{}|{|{{{j|^~y|~|{{{b|٠{{{ȯ{t||||||||||||||{{{|||}s}Lj}Q~Ɋ}]~f~M~BĹ_~ƻa~M~q}Q~Ͽ]~j}_}B~J~M~F~r~`~r~ه~u~އ~ކ~܀~~Jk~e~On~|}z}Ԑ}W~W~k~d~c~>g~ķZ~U~H~^}x}P~n}P~K~l}\~n~o~T~`~T~A~P~}`}~>~X~ù]~|D~zB~a}K~a}R~}L~R~r~u~IDk~Je~U~܎}z~~q~~Žo~ޙ}֘}|||||||||n}K~M~Z~BCJ?N~٘|g~A:j~t~l~H~~F~I~q}|L~|}}}[}}}X~R~L~փ}c}g~Žn~Y~[~`~^~ْ}ӗ}d~Y~˖}}}m~i~~e~}x~k~|~y~ÿq~g~}}}t}֗}p~}}s}\}Z}K~D~[~}O~K~i~Y~S~}}a}Z}Ǽd}T}u}j}}t}n}N~M~S~_~ӄ}}z}b}V}|r}ƽt}]~ƿn~W~T~[~X~`~Y~V~}O~f~]~Ҁ}Å}}~L~ב}Y~߁}}z}ߎ}h}n}~W|zz{ {{{{{{r{Ɣ{֛{ӝ{{~j|}h|{{||r|j|[|{zd|{{{{|||||{լ{w|w|y|ͫ{{|||||{z|vt||T}ߜ||ڢ|ڢ|Õ||||||||̧|||||||k|||||{|{p||̚|̪|}|̑|||{{{|y|u|v||||||ǡ|p}y}~n}o}|y|||ҕ|ԕ|V}ɍ|ƙ|Ƞ||{{Ǜ|\}\}ӣ|X}|U}Ԋ|Q}|_}d}|ѯ|c}~g}x}}̘}}}t}|||ϭ|֭||Һ|||ܿ|̭||||؟}|||||||||֯|Ϊ|֯|||~_}a}ߴ|է|զ|^}_}d}|||||||֮|||Ԩ|Ϟ|٤|خ|||߭|ǥ||p}n}}N~u}t}}w}E~P~M~]}¸U~A~B~n~M~W~M~C~d}[}V}|Y}~R}[}~W}g}m}x}x}~}r}b}|Ԓ|b}d}k}Q}f}n}_}j}e}|k||N}\}i}^}d}e}W}Y}b}a}ޕ|u||Ǽu|܈||||i}l}_}^}^}H~O~X~\~~M~Q~ς}ߐ}w}è||_}f}`}d}p}q}~\}Z}a}]}S}|t|d|{{{t{s{q{{̍||t}t}f}f}D}B}j|T|t{Y{g{zصzzzzzzzz{{|{~||||||||||||||{{{|||}|||||¾|n|m|i|z|Q}ɿ`}Q}b~l~u~}}n}{^|}M|h{P}n|Y}ʾo||{{܊{Ғ{ݮ{ӯ{{zzz{z{zչ{b|Ӆ|p||½r}l|E~C~n}L}L~`}f}=~X}J}]}L}m}P}i|p|{w{k|Y}ԜyӐy´Y{ʺS|=`~EDξJDE]~9@~h}<~A~x3~˻O~G~V~T~<5J~I~9{5S~U~AcMKC^}5UOKA|1˽J~B~CHIY<|;>z7i~Y~@~~k~w~ƿc~w~A~;k~B@;;qWSh~J~pQ_~HB^~J~CMH~p~P~D~E~Q~[~U}@>d~j~Gu~{4?W~p}|zp{|z̡y|xy^yؕyʉzz}h{{zխzȷzzz{{{{{jk|{z}~{wx{{A~@~r}|=~D~H~J~G~E~L~L~N~H~L~N~K~J~I~H~D~D~G~C~E~H~F~C~H~I~J~M~L~M~M~O~M~N~K~K~H~H~H~;~K}K~d~~1I~?~?~A~C~F~E~G~E~D~@~C~B~?~?~g}ƽ~|{{{{{{{{{{{{{{{{{{{{{{{{{{~}{{{{{{{{{{{{{{{{{{{{{z~{zzz{zz||{z{{{z{~l{zmz{\zgzyyyyġzzzu{zt{{zzx}{zzzЩzzzzzzϕzw\{q{{k{g{zz‡{H|Q|b|u|_}u}h}S}^|{j|X}Z}\}}r}|v|o|؆|{O}^|a|S}l|b||b}]~Ŏ|{Ȩ{{{n||{{{{{{|{{{|{{{{{Ȝ||{{{{ܚzzzw{{{{d|{{{|}|{r|m|N~I}[||{{{{{{}~|||q|||||||||||uw|||||~||}|}O~߉}}}x}k~CY~M\~}l~>t~D_~L~߅}d~Fe~A~s~w~Bºk~S~GPI}CLMr~~w~}ő}V~ǿSl@}E~J~ɼ\~e~y?~E~c~M~U~d~n}|}ƿe~U~`~ýc~_~`~i~V~}I~~H~D~}F~I~}ƿt}h~S~|L~ۅ}||j~k~9m~h~V~Q~v~}~[~Q~n~}~W~s}y}||||||`}n}M~[~RN{:U~U~P~c~P~_~T~s}\}}{B~M~}{I~K~{F~}}t}G~Ľp}d}U~h~}L~}}}}}~}X~üi~d~`~Ȉ}}}}}T~~^~l~v~׀~Z~}[~f~h~V~r~_~}}V~¿}I~b}{@~}M~r~~Ձ~d~}y}NJ}}a~L~M~a~g}||l|Ƥ|}H~K~zC~d}h}|\}B~}H~O~Q~Z~y~l~}½}`~k}ƣ||]}u}t}J~T~e~Y~~R~R~ُ}ɀ}}{~U~S~t~H~ͤ{ʪ{{{{׹{{{m|{w|}g|z|}|z|||qq|ff|mn|su|{{{{{{op||||||{{r||{|z|~n|{|{||||Σ|ћ|y|޸{|||~|{|s|{||â||߰||ק|ä|||||||||||{w|||| |}||x|{е{{p|q|xw|po||~||ʗ||Ð||||ۓ||Q}|Ŏ||||x||{{|d}U}|ُ|U}X}`}Y}ӟ|||ܺ|ɧ|ۥ|||l}o}m}||޾|||Ѽ|t}||}|||̡||ۣ||t}||~}}|s}}}}ܰ|ڷ|֮|ٮ|˧|Ѩ||a}||Ԡ|f}`}q}|Ϩ|_}i}|f}||||||||||a}ϡ||ܺ|χ}t}}X~i}K~W~y}x}u}]}y}`~X~S~@~L~]~|=~c}X}`}_}|R}zN}|r}||є||[}źo}W}ӎ||h}m}||f}V}e}zP}}|e|q|ʄ|d}o}I}F}J}x|V|{|||a}b}٣||˶|m}{}׆}ڈ}ƾz}}{}t}}N~~L~V~~O~}O~}}z}ҋ}R~}þ}̊}Q~}}y}H~p}u}v}u}m}ۉ}`}|y|~|||||||]}~L}v|L}q|l|\|T|m{n{{x{{{{պ{q|{||||||||||||||||||||||||||}|y|~|zW|{W|o|N}R}V~ɻU}o}T~B~l}g|[|W|[|܅{r{{X|ķh|zB}W}Z|F|K{ܖzک{zz¿yyzz{zm||r|h|N}}N}L~F~D~V}9~7~X}S}O}F}C~f}B}=}H}=}n||u|W}f{ؙyV{B|z06GʼJNX<W~^~<~K}6~ŸM~?~]~̿Q~Q~]~5z0c~W~8U~A~N~ZKB>GɿQ5dgMe~I~e~~58N[F~~i~v~E@@k~]~żQ~^~k~J~W~NA<M<ohYS;>RlBv~FOSM;JVKu~[~{6~B~`~II~Q~|9?i~g~<i~W~S~b}|{zyڻxcyiyywy~zzɌzΪzz˫zzǥzzҵzz{{{{{{{q}B~?~D~F~D~D~F~A~G~G~I~D~J~D~L~N~I~N~O~G~O~L~K~H~P~H~M~O~M~M~ƼT~N~L~M~J~H~G~B~C~G~D~[}e}{>~[~^~S~E~B~?~@~@~B~A~B~C~C~C~@~ɾd}ؒ|{{{|{yy{}}{{{{{{{{{{{}}{}}{}|{}|{|}{{~~{{{{{{{{{{{{{{{{{{{{{{{}|{z}}{||{}|{{{{{{{{{{{{{~~{Ѫz`zyyizywy|yƬytzzz{{vn{rk{zw{ȶzyzz~v{{}{u{zz؜z{xf{zzzh{T|O}A}>}_}Z}o}K}=}E}~|L}|\}Ć||̓}Z}{Z}i}|e|Ϧ{Ķc}Ӑ|T}m}b}Y}{m|Ȯ{{{֚|~g|{{{{|{{||{{{{{{_}~|Ü|{{{̡zŏzיzz{{{{Ӽ{{}]|Ĵ{{}|{h|{l|||{{|{{{~r|{{{{־{{|{{|{{{z|||||{|ԉ}n}|}K~}Ɗ}ԋ}}L~j~>{>m~s~]~p~j~R~||}UUSI~@B~}Eh~ľ}}P~h~EdS{~y~Ju~]~R=9Ri~k}i}ɿk}Q~O~j}}Z~J~{F~a}}Ⱦ^~[~P~s}ڔ|}g~Bc~P~}?~ӌ|\}c}|}R~g~¸g}L~L~N~n}x}ֈ}Y~@LDMQu~i~Y~ûf~T~üg~`~Y~܌}e~}`~R~ٌ}|}_~}m}n}s}|}ż`~p~N~P~T~K~L~S~O~R~K~Ľ{}M~}x}܇}Ѐ}T~|G~m}܄}J~p}|}ø\~X~{F~{H~}L~}}}݅}z}e}}N~T~}t}i}l}l}}N~\~Ǐ}Z~n~V~}s~~~~IS~|Y~|}g~N~}|B~}ļd~P~}T~m~V~[~V~}c~U~ۓ}a}zE~S~V~|||w|ܩ|ÿ|v}w}`}l}n}W}d}I~h}g}x}K~G~d~f~P~x}|`}d}|q}Ľp}X}f}m}}P~M~q}|f}Z~l~f~JP~]}|ϊ|W}ƿg}||||Ŝ|g}u}}ɽ|oo|||||mm||nn|{{{{zz|vv||{|}||||{{÷{{{{||hh|||{{{x||c|j|{Ҵ{h|j|v|v|{x||||آ|Ԥ||˝||||{{{y|z|z||{|||||n}p|{٪{{j|||~}|yx|Ż|}ã||||q|x||Y}M}M}А|ِ||֦||||||^}\}]}X}X}۝|`}o}m}|d}d}|߽|ɬ|||e}q}x}p}p}|ٷ|d}||ݷ||||̵|Ȧ|ک|||||ɧ||κ|||i}i}}}|}g}|||a}^}b}|||c}||ߩ|Ψ||c}i}~c}f}k}f}\}e}j}|||a}r}s}h}r}`}w}j~w}Q~y}n}x:~J~g}ŸS~F~<~g}c}L~A~n}b}S}T}P}|h}b}w}b}^}e}l}r|w|}V}Ќ|nj|{O}n}p}T}n}l}C~^}~Q}~|Ƽ}|k|`}z?~B~Q}}>}x|zQ|^|t|ј||ȓ|]}||h}l}]}||d}n}x}w}ă}[~Y~y}o}}}r}s}z}e~`~V~|}}ъ}}}ن}{}w}}ƿ}}܋}N~|}}o}o}u}}܉}t}}}Q~^}|]}_}Y}č|V}f}o}_}T}Б||֟|ը|~d}f}i}[}b}}v}z}݋}~}l}}m}ڎ}T~}z}ņ}Ə}~}i}||||d}n}|Ϯ|Y}v|f|e}|u}|}B~D~R~L~^}@~}1z/7~c}:}T{zĽ|{{{ø~{ŷi|e{wzzƣytz{zzzyyʡxzz}{{{v||Y|g}y}p}M~G~g}[}b}b}I}C}?}F}m}I}m|_|A}p|W|_|j|Y|]{g{Y|F|x0~8FE=F\~b~H~A~H}ȽX}[~C~8]~úU~]~8źO~R~<?V~{2B^Y]~ED;G}3_?D~/F~L~~6~5NQ~[~|>u~p~a~j~n~?]~U~y~;VcSC77@Gb1O^Nf~k~H~a~|AC~>XI]S`~S~D~H~L~`~Qh~]~z46)Z~j~^~l}X}Z|zzxy߱ze{ўzzȔ{}K|X{wzްykzyzΦz¬zo{p{{~x{{{{{]}x;~D~E~C~E~F~@~r}B~D~L~F~H~I~J~L~H~G~F~D~K~N~P~K~L~N~O~O~Q~M~N~I~G~J~G~H~B~B~C~E~D~y9~S}=~D~U~T~J~A~D~A~B~A~B~B~A~H~{<~M}ͳ{{{{~{}}{{{{{{{{{{||{}~{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{z{|{zzfzשyyݢyyy~y{zzzz׻zzɧzsm{tl{ϬzԯzzzzٿzzĪzݷz֩zԞz׹z{zzzzzE|@}6}5}<}H}>~;~`}=}F}]}\}W}V}ܦ{v|^}‚}|ǿm}`}g|{s|x}|̛|c}b}{||{{ޥ|r|{{{{||||{{{{|b}||{{{{{{{{{{º{{{zz{{zz{{}s|{{{{{{{͵{ھ{{{{|~|vy|{{{ñ{|||xy|{||||†}W~l~і}s}X~o~p~O~l~k~w~m~f~c~Q~}U~}ۂ~~]LWJD~C|~EH\~}N~}IJt~r~J~n~Ŀq~}EMFW~ԏ}|H~K~_~}H~w}c}N~N~R~N~F~T~n~|B~X}||t}ȻR~øW~C~l}X}|?~@57j~?~t}|j~q~a~·P~G~o~u~n9R~ƹU~h~l~ǽh~b~_~j~ш}܎}}}ӏ}d~`~[~}Έ}}K~Z~V~N~W~~K~N~Z~M~}}K~}M~Z~\~S~h}}]~}K~E~C~~}}}}G~J~O~I~}O~}ۇ}Ї}s}m}I~Ā}k}Ɓ}R~؆}|K~L~Q~ۊ}p}ʼn}}u}q}^~Ŀo~~~~u~Y~s}|[}`}`}|Z}e}J~~}ùk}n}}}n}o}¼u}[}c}_}k}u}d~F~F~H~}|}Ѫ||}E~}H~N~v}a}[}n}Q~}\~}}O~}؅}Z~L~ف}q}}y}}݇}i}k}ƽs}j}k}c}~G~z}J~u}g}j}F~ƕ|U}||}}|||m}}|||{~|{{{|å||zr|||h}h|||{{{{rs|pq|||X}M}Ӂ|j|{ٮ{{x|{{{{z||||ɳ|Ҫ||ś||||||d}|{{{q|o|u||||||}v}|{{ޫ{~_|{{{|~|v|~t|{{{{{}|Ú|ȝ|^}||||||||||||T}V}[}e}i}d}|e}r}c}||߼|j}|r}ڂ}܇}}}i}||ת||Ŧ||۹|«|||ճ|޽|̴|||}z}|||ڬ|n}y}f}l}e}|e}]}}k}|W}Z}|V}zV}ڧ|ɝ|||ݮ|c}m}s}p}g}[}_}l}n}Ƌ|||c}L~|}v}k}d}}Q~x}_~z:~E~B~B~?~>~y3~:~g}s}h}f}r}]}Z}V}Q}N}P}y}½h}k}ϒ|Ԑ|||ܓ|||X}|S}U}o}n}}=~F~κh}]}V}V}||B~{}l}}f}]}d}}m}қ|֣||ݝ||Җ|_}^}|||h}i}g}·}o}}z}b}r}À}}z}ʁ}}}׃}ݎ}Z~y}j}i}ʃ}½}z}|}M~|N~ʇ}S~s}ا|ϋ}P~T~W~S~k~Z~V~ސ}֍}Ƃ}}}Ԃ}w}G~}q}|w}E~K~P~}L~U~e~_~K~N~X~\~d~}T~]~U~Z~p~a~}]~`~V~X~W~b~}ݓ}׈}y}x}}M~a}n}~}F~F~Ǽg}ɽb}g}]}÷P~~9~ȼL~7?ȿP~b~_~_|p|h}p}Ϳg}K}[|o|Շ{zytzrzzƷzxzxyzz{λz]|c|O}]}a}^}A~<~J}F};}:}e}>}m|l|H}=}:}M|\|[|T|P|k|u|W|k|P|{v|8M8>V~Q~c~P~r}=}N}R~W~@}6[~ŸP~<\~ȺM~`~8;?HLH9KAH7dVDd~3I~N~:@DB<~?~Fx~`~m~o~[~DP~:p~JJgQ6Z~;D]AM^j~k~f~~~~j~Q~e~OY]ct~i~G~U~K~FKABI7;~.D~O}ƽs|ļw|{zyzl{{`|’{m{zرzn{zzkzycyyyz}e{{{{þzxx{{h}m}@~B~C~C~B~B~B~E~=~E~G~D~C~C~H~I~F~H~L~H~J~P~K~J~J~P~N~L~O~J~J~G~G~C~@~B~@~I~N~D~ƺ^}B~H~F~U~\~?~D~B~A~@~>~>~C~~=~b}v|{{{{{{{||{yy{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zztzΨyyyy{yxzذzx{ƞzzzy{{{wv{zzpzxzzƹzzzzwX{zzzzzzzzH|0}4}8|0}:}:}N}k}]}O}R}`}J}Q}ؒ{t|w|a}̇}n}V}{{s|ٔ|W}ǜ|n}z}{q|c}{q|Ԣ|{{{ż{}|~||b}ͺ{{{}|{{{{z{{{{{{{{{{{ɛ{zywy{{{z{{{{|й{ҽ{{{{Ǯ{{{{{u{z{wv{{{||||||||}}^~k~Y~}L~d~_~S~g~~~|~~}Ev~i~r~EKRQUUUY|CEv~s~Iv~g~}~n~]~|Z~r~P`~[~~ɓ}|ӌ}l~e~TА}j~i~X~W~m~K~^~=d~]~d~d~|}d}q}r}Ⱦa}{7~D~I~L~>~H~~4=:`~}<~F~U~I~r~b~L~_~X~Y~\d~K~}M~Y~R~m~üh~[~R~`~}]~΋}T~»f~_~ߐ}Ї}k}|D~L~o}O~`~R~U~ς}^~}[~}P~]~ؓ}l}R~f~[~¹b~{}I~T~}c}K~I~}»s}x}}~}o}s}p}}L~}r}Q~m}o}N~a~T~_~[~}~}†}ܕ}Z~K~J~`~i~z~N~}}t}j}H~n}Ѐ}m}|a}ϰ||½|[}J~C~R~}g}p}Э|u}Y}Ơ|z}ي}||S~b~L~}J~}}}ӆ}t}|G~o}ā}l}W~P~|~}~z~W~a~Z~c~S~څ}J~n}K~O~u}|ɘ|}}v}M~߄}o}~}^}Ԯ||||||ۇ}X~}m}|z||{}|c}`}f}ˉ|x|||||{{{{۽{{{op|{{{{|||Ǧ|ˏ||||E}m|b|q||||||Ğ|||||k}e}ݯ||t|x|~|z|p|{Ƹ{{|||||||||ˮ|||||c|y|||{{|{{{{{{t|||||||||||||||||||֞||^} |Ľ|Ѵ|Ծ||ؿ||ٿ|k}̌}M~N~W~އ~|~}|ݶ|۱|ǥ|۱|||Э||ͳ|δ||l}||n}ڮ||Ƣ||ٱ|g}}ե}|}p}[}g}|}f}\}b}X}}T}ė|ޤ|^}|ڭ|`}d}d}c}\}X}|U}Z}^}Z}e}]}Q~~>~u}~C~~>~L~E~};~N~{;~H~a}{7~_}[}F}U}źV}P}Q}W}V}Z}Z}i}w}W}že}y}c}N}||ؒ|ю|T}V}{M}|O}|V}Ⱦf}j}x<~w<~w}}O~P~c~}z}ڝ|̔|}T}|ɑ|ߢ|W}|||˥|i}݉}}f}`}{V}~`}e}b}b}z}s}ȿx}}ֈ}}K~ʀ}{}f}d}r}r}]}|k}|r}x}ؓ}}}K~~}r}ʄ}W~˅}х}ȃ}}}|}}}}V~e~}w}܊}Ԇ}}އ}N~}I~R~[~»`~Z~W~ш}X~\~P~}a}q}~}y}݀~m~}[~k~U~~k~i~ό}|ܷ|_}O~W~}}x}S~~M~V~ڊ}ن}}N~݂}J~Q~N~o~ŷS~m}r}G~h}:~C}I~T}]|w;}y|Ǻ_}s}g}H}h{o{\{zkz{zzzxryyz̤z֬zz[|o|y|R}A}:}Y}9~T}R}V}N}a}E}[|s|J}N}A}{{V|O|e|q|g|{X|^|P|g|I~ƻT~6W~̼O}E~J~c}}=~S~|}c}H};Bo~S~g~\~3X~g~[~@GLAJL5C6\TG5{57C@KWļW{5HH=J?j~Kk_V]JE}~[~9?~`~25:enE|6]~f~¹S~Z~j~ľ_~H~`cJ]~I~B~D~ME>@GK:`~O~E~]}N|J|{Y{\{ÿ{؛{f|p|Ĕ{{y{۠zj{zzzzݻxSzcyyةyzzвzz{t|u|G}A~C~A~B~>~?~w}A~C~<~C~E~C~C~C~A~C~?~A~D~D~F~J~L~G~M~N~M~N~H~E~E~C~@~C~B~@~@~G~I~D~V}B~G~N~S~K~o}E~N~C~=~?~}:~[}z|{{{{}~{|}{|}{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{z|zyyyyyyڥzxl{z{zz{v{|{ηzzwztzzzzzz͙zzzzzzzz`{U|6}7}c{h{9|L|E|0}G|;}w}B~y}l}q{f|]|Я{Ӳ|ߗ|i|}L}{q|@}ܛ|||}{{|b}}|p}{{{ţ{̻{x|[}^}~|{{{||{{{z{{{{{{{{{{z~~{zz{{{w{{{{q|{{{{z{{zzzz{zzuw|||||||xy||||w}Q~U~O~`~d~[~Q~\~T~a~\~o~JKFNNUURRU^]R~~r~G~h~DDZ~¾}i}^~|~SD]~z}}Dk~Lx~j~YKC|:^~k~:X}9E~C~P~Q~J~r}|}B~?~c~]~^}k}J~K~h~^~r}_~b~q}X}R}E~D~Q~G~C~ɾX~n}Y}~R}H~|B~y}O~U~_~}Z~a~k~O~Q~ł}{J~T~c~N~M~؊}R~k}p}f}G~}X~s}{}^~y}}N~V~F~|}ӄ}H~ڌ}a~e~}M~Q~}L~u}S~}}|I~\~b}|e}c}I~D~D~C~q}p}A~{}O~ƼZ~o}|j}}E~}Z~Y~U~C~K~O~w}H~S~g~|ʪ|غ||H~}m}}H~h~]~ù_~x}څ}f}x}Ⱦt}~Z}|}W}ـ}k}݂}I~|U~ȼ_~O~y}ŧ|e}w}s}|}z}h}{}m}ȉ}ͅ}ϋ}p}́}}R~\~U~O~}~N~Q~\~}o}b}_}|l}|k}c}ģ|||{~|||||F~}}a}g}||U}p}u||_}||i}|||l}}m}m}|ڽ|Ͳ||xy|}{|{||ޝ|~|v|~|u||I}||||ž|||T}ڜ|||||Щ|˥||z|{{{||{{|||||||Ӯ|||||||ȴ|ǯ|l||||||sr|{{{{{{||]}||||||||||||{|w|‰||ޜ|d}c}o}y}}r}r}u}x}׍}R~z}}H~X~h~}i}|ѱ|Ы|||||֭|ȯ|԰|g}||||||ʚ|ל|~m}}}|}}}}n}c}g}e}c}l}c}h}[}̙||[}||_}`}Y}||[}]}Y}|||\}m}~}q}h}}}l}E~N~{8~D~j}^}ƺR}X}R}I}v|y|}<}|~|~A}C}E}G}[}j}M}]}g}o}X}T}Z}T}̿l}f}^}Y}b}e}F~G~w}{?~[}Y}ƴe}~}N~[~}ޑ|o|||ߓ|}a}q}]}U}Z}f}i}o}k}y||yX}U}x}k}q}b}d}n}v}|W}_}‚}p}Ɂ}|||ѩ||j}х}}X~Z~]~Y~Ћ}ԓ}{}}}}}ג}t}s}~X~w~k~}d~b~ÿy~ߙ}ˇ}W~þ|}}W~P~W~Y~\~U~O~^~l~|K~O~W~Մ}}˄}O~t~}~s~x~}~c~Z~t~d~_~f}k}y}e~a~T~]~}r}}k~}}Ԇ}n}r}^}L~}N~|}S~s}a}ü|p|~||t}M~{|m}U}j}f}L}t|m{{t{˻z{{{{{{{o{ɡzŝzo{m|Ļw|׃|E~<~2~>}_}E}[}T}g|G}<}J|T|D}i|e|l{y{S|Y|j|k|{p{u{r{y{ى{?}Q~?:I~C~F~O}H~b~F~J~?~ǽV~Fq~6z3U~X~Y~b~:86=z2KIĹM<[^S`d=@:z2?S?C;~=j~;h~{~QIV9K\~X~M?_~8~<~Y~5Im~:37<A:¹T~H~fU<;R}G}l}o}I~OI=87W~M~Q~|C~|R|N|u{w{{ڢ{ʝ{]|{{{zz{zŵzzzܤxQzUyXywybzňzC}`}ɿ^}I}]}e}?~?~f}[}g}V}s}j}~;~y;~?~?~A~E~?~E~B~J~H~G~F~D~K~K~K~B~E~F~A~?~A~E~D~B~>~D~?~D~G~@~V}@~J~H~M~H~g}F~N~H~r}T}v|{zzz{~{{{{{}}{{{}~{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{tv|ru|{{{{{{{z|mzîyĬyyyyïyؼz}{{ܽzzud{ԫzzzzyyz~zzy}pzázzܛzzzyz{{|{e{U{V{X{D{zE{_{I{K{z9|;}F}}{{|q}_}Ѐ}d}|{w||[}{|d||{|ȇ|j}e|{R}\}`}k}{z{{Y}|a}{{|{{{p|{{{{{{{{{{{{{{{{{{{{{{{ƴ{ȴ{x|{{{{{y{ڼzzzzz{y|||||||||{{{|۞|b}j}A~ʼY~A~L~e~}K~ۑ}x}q}}}v~FG~XOVQPWĿd_Xr~}}I~F~G~c~Gw~[~BW~@}:|=]~R~e~I~TDON]úSd~g}A~Q~j}g~V~c}x}T~j}P~v}Ք||~}T~{>~A~s}y}C~`~M~m}r~m~Q~[}Š|?~M~j}K~K~C~@~s}ɼh}\}|X}}N~Y~Q~c~j~ƾh~b~[~Q~Ŀ}h}K~}}L~O~ٌ}V~f}N~R~v}d}e}|Z}D~N~Q~Z~|}}U~ǀ}V~g~X~g~}}N~q}|^~{I~M~I~K~}~}F~c~A~}z}u}q}g}L~Q~M~M~{G~O~J~Q~R~|D~ހ}P~}|}u}c~}}}||p}}y}K~e~d~S~P~Q~}I~ۇ}P~z}ļr}x}؀}o}b}d}m}݇}}n}V~]~}E~f}l}|}}I~|Ⱦ{}I~S~e}g}|x}R~zH~q}́}h}}}w}h}}X~}o}y}R~M~u}|ε|||}~||||n}t}|f}}[}|t||b}^}_}԰|||s}ֶ|||ƥ|~}}U~\~S~½}΄}S~ÿ|}ܰ|dz|b}Җ|{{i|m|q|{|Ń|{|z|z|є||Œ|˒||w|y|~|||||{{{|||{y|{z|vu|{|||||||||ǹ|ؽ|f}o|c}ѡ}n}|{{{{{{{u|||{|||ļ||||||||||ϛ|Џ|{||W}V}a}Ƽr}k}g}o}e}m}_}v}݂}Ձ}x}~}~}|}ƿw}s}g}`}e}i}c}b}_}||ϳ|Ϭ|۪|ߢ|~|x||||||||}w}v}}_}m}B~g}Ǽg}h}b}W}ļj}{U}Q}Ž||^}_}Y}|||Ԡ|||X}|v|h}||R}W}Y}z:~?~H~;~}9~_}ǹW}L}`}N}};}i|»f|b|n|f|¹f|j|o|||M}U}P}܀|M}Y}źj}X}V}[}U}c}}\}b}e}y}zF~Ƚk}L}|́||T}ʸi}}}D~|ӎ|֐|V}^}c}ƴi}~N}S}[}~T}a}e}•|wP}o}m}i}u}{}a}j}}j}{F~|c}r}r}†}j}s}x}}n}Ć}Q~}}¿}c~f~d~S~ˁ}n}t}ˑ}Ɋ}¾}ۓ}{}}ܝ}~V~U~Q~S~S~T~R~P~P~]~~L~}}L~؁}d~e~s~h~}}_~}}X~}S~u~j~~X~||\}R~t~k~o~}T~z~ژ}^~}~X~~P~{}X~}}|̧|Ƀ}R~Ԃ}L~G~}}}r}¼x}|y}|U}_}K~H~_}L}X}L}E}^|ϒ{{{Ş{u|o|m|d|r|q|v|{{{{{{|x}^~A~=~J}@}]|Q}}6}c|h|[|X|P||d|h|N|h|i|J|T|]|t{S{u{zU{݄{y{O}k~@;>/´Q}ƻT~K~j}@~U~]~^d~9|3[~O~P~{2Ca~67S~V~@ĹMW^iɿQ\d@ŻP=ADMD;g~}@z~k~h~~|=??R=A~V~QIþY=m}z3;UU½`sKdXS}c<R_g~F~Ӌ|p||d}G~_ĽXŽW;>T~X~Q~d}O}q|]|`|{Y|ա{{զ{{zzzzzzzz}zhzwz^yډxzxiygzZ|d|a|N}W}H}T}l}X}K}E}Q}P}V}w}>~=~?~@~>~C~F~D~F~F~G~G~D~<~G~@~B~>~B~C~G~F~G~B~J~D~A~?~C~J~>~g}M~M~C~W~=T~=~@~g}`|{{{{{~{{ww{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{|||~||{{{{{{{zzyyyyzww{zzɞzzzzyyyyھyvzz|Z{zzzzyzt{zx{zܐzzz~VzezmzzK|5|H|T{W|9~H|Q{Z|zϜz{|S~ԏ}}~}{|f{пQ|{х|{{ȳ{{՝|t}R}{{q|o|l||p||Ʒ{{{{|{{{Ҹ{{{{{{zz{{{{{{{{{{{{{{{ټ{{{{{zzyz{|||~|{{{||R~T~||{ƍ|ĸf}j}~}J~K~L~b~e~P~E~Z~`~P~Z~~Ё~w~E^IGG~plQ{?a~C~Y~R~b~{;]~=C_~T~P~L~H~@Ⱦb~t~O~~=Y~O~Ik~q~P~c~Z~k}ۂ}Y~FS~e}{}́}g~U~I~J~}}ޜ|b~d~ƽ]~J~m~a~O~ži~Z~H~k}o}m}u}L~T~ȻW~K~L~ǶS~a}_}q}||m}L~p}~}[~]~P~}O~}S~[~O~Ӏ}K~Q~k}Q~}P~P~L~}l}q}u}M~_~c~^~\~V~`~T~}}҆}w}}}|K~|F~m}|}U~{}y}ɾ`~K~E~N~L~W~|}S~}}G~K~}y}}t}e~Z~Q~R~u~[~}}a}_}I~T~w}t}||||ь}ߍ}Ӈ}׈}O~K~G~g}¾||k}^~{}}x}f}|||k}m}}n}}}E~z}xC~O~E~c}ض|f}l}I~΀}}|I~Źu}R~S~}j}т}т}υ}r}π}m}ǯ|«||{|t}~H~|||||||||||c}i}x}j}||z||||С|{|g}c}ݭ|a}n}}ƿv}Ŀ}}|}x}}_~}Ȑ}n}|d}ƽn}h|d|g|o|y|k|o|d|f|n|w|v|߽{Ȯ{{{x|{||{|{׹{Գ{y|||{{{{||||||||||}|{Y]k}|||}|||ݧ}w~}}u|||||||||֩|||ђ|̕|Œ|Ԓ||d}_}ɽg}m}b}c}g}i}t}t}}Q~K~ʾq}ƺn}}|}s}t}{}v}k}ºv}u}k}a}||ۻ|ݼ|ְ||||ò|||s}x}q}ÿ}s}d}j}}m}v}v}_}f}n}·g}ɿk}l}~W}Y}|Z}[}Y}\}]}|ȕ|̚|ۢ||||||R}|ߐ|Ն|Y}_}п[}h}>~}7~|9~ʼW}˿[}T}P}>}d|^|O|O|P|O|T|W|T|b|t|f|k|b|Q}^}L}V}}~}j}d}m}c}Ļh}q}e}n}]}u|^|Y|p|Ć|ӌ|Y}i}Z}[}t}c}wD}Q}˷g}j}q}a}W}ޘ|{||}\}|ۢ|~Z}|||||Ȟ|||l}}]}Ә||||||ō}_~[~{~o~m~}ǐ}ӿ|s}}~R~}͉}ތ}{}̊}؍}Ɔ}W~\~a~c~}щ}Y~g~]~T~W~P~n~]~^~}}}~M~؊}R~}w}X~Y~}N~R~c^~k}`~g~j~a~p~i~\~{}}W~[~U~}d~m~}|}|}Q~}}ӂ}x}l}܄}ýx}v}j}k}x}N~u}j}y}@y}O}^}s}Że}{N}T}^}e|{{q||_}t|u|t|r|m|p|{|L}l}P~h~1~S}Q}a|7}`|L|M|g{C|U|w{\|t|c|{`|[|X|Y|O|s{T{b{U{i{ԉ{j{B~=~CG~}5L~>~A~@~m}`}D|A~DH;<U~D~Z~5::`~7P~P~^~<JJDCVPC<ROƼRS=G|~W~~<~L~v~u~}6}8Lk~@~ǿV~CGN`lCg~ǼM~h~TPM]~p~M_j?FQo~p}P}||R}`}H}Z~\~cC`~C~e}h}e}V}j|r|i|j|ۅ|l|e|e|{z{zzzzzzƧzzszoygxixxB{czgz\yo|||y|I}X}w|W}||v|N}ǽ]}>~=~@~|<~<~}:~?~~<~?~@~A~D~~=~C~E~>~ƽZ}R}q}?~G~G~G~|8~<~a}{8~k}=~<~t}˿]}g}K~D~@~F~y}[}ك|k|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{|}|{{x{|{km|{{mo|{{{oq{zzzz}~{zy{zݬzz{zzyyۺy}zȅzwS{ߠzzzцzzٖzzݨzzԣzЖzztznzkzzN{ׅzE|.~@}1}3}~8~5~E}d}l|Ӌ|}V}zηz{{{{zzyzzw|{zz{z}V}Y}z{Ŀ|t|||{ڝ|u||||ƫ{۾{m|}|{{wy{}{{{z{{{{zz{{{{{{{{{{{{{{z{vw|||}|}~|su|{{uv|{|͋}d~~s~_~є}S~N~b~b}}H~B?ĻTd~P~o~{6]~[~k}|m}}k~JXLWtIRAz~q~Dl~~~G~C}M~]~q~|~d~v~}?_~l~Fp~p~|~Y~o~üi~k~O~Q~m~s~l~_~X~L~W~\~l}߆}d~j~Bf~[~~J~j}i}~@~Ż[~O~N~M~c~݀}l~V~߄}x}W~C~^}w8~C~P~F~B~`~s}u;~{B~x}v}q}͠|x||a}P~P~ʽ[~Z~R~R~»}}T~z}}W~P~܄}}G~}R~O~̄}p}~}z}O~M~n}l}y}z}C~x}z}}݋}}a~Q~}}I~Ձ}~}~J~߉}x}R~h~a~M~J~܉}y}~L~O~K~K~c~}T~Q~~J~}}}}S~V~c~W~Ս}z}S~N~}}}}|úo}_}|Ⱦr}E~O~}M~y~N~}|}}}w}}}||||˽|||i}}}k~M~Z~~}y}s}|{}P~`~}|}V~[~H~}i}}b}ղ|||߫|||xy||y{||||||||||||||}}g}r}֪||~|}Q}|ڟ|c}w}ѻ||{}|f}ф}|}}v}|W}i}|½y}^}n}}k}o}҅}}R}^}}J~̊||y|֕|ݓ|q|{]|{{{Ȫ{{ȶ{ڿ{߻{{p|p|{ܺ{|||{{{{|||||{||~||{|t||{˲{{{{ɢ{{~||{{{Զ{{{{{|||{|Ō|ȋ|||ח||Ӕ||ě|̚|Г|d}`}f}]}b}l}m}q}{}q}u}N~R~yF~`}ŷj}w}{}z}}t}q}Ļu}w}c}n}m}[}|׿||گ|||||o}r}}r}c}s}]}j}p}ļn}m}v}p}a}y:~?~A~B~>~Y}X}V}^}\}V}||Ӛ|ʔ||Г||||r|M}|}O}W}i}n}z;~I~H~n}ѿb}J}U}J}A}i|W|T|{{ÿ{þ~{م{І{~{Q|Y|[|\|i|_|||L}e}P}|w|Ք||{L}|||ԇ|d|Δ{`|x|h|~a|x||S}p}b}ӄ}}|~l|r|p|w|~||||ի|||y}i}_}|Z}]}|||||Ƹ|i}}½||ֶ|t}|t}}}}}ء}g~i~w}|}lj}͌}z}v}}}}}[~Œ}Ą}N~}}}z}}O~c~|~W~g~\~m~g~R~L~d~܏}}}s~b~W~T~Y~i~}Г}|}}O~S~}f~U~}W~k~~~V~[~b~Z~݄~~ڌ}|}ԛ}r}~J~݀}S~}|J~t}s}ſ}}i}q}L~i}c}y}}}Z~d~Y~ڌ}~J~|}}}x}y}I~՟|қ|a}|f}?~p}g}d}қ|d}s}x}i}F~D~V~T~>~X}X}[|>}V|X|A|F|w{V|Q|f|z<}s|{g|\|y9}}@|{W{|{X{U{z\{m{Z|b}Ip~J~5~Z~:~>~^}_}b}H~C~k~B~^~¹R~X~53I~y69@I~d~_~^~;:6y2EF;KcO?BƽU~<K`~S~þd~r~Q~_~FLIH~P~iFh?DucVCDFT~Ca~C|;\~<S~k}||[}|W}v}X}d|?~Rq~g}f}|c}k}c}O}ބ|o|p||s|e|{{{{z~{zzzz¨zyvyzx}^xlxxDz[zxԆyjzO|a}e}|u||E}z|Z}ǻ[}i}o}{=~D~~A~?~};~?~@~>~g};~n}?~@~F}r|M|D|[|h|V}K}A}`|y|l|J|f|P}p}o}U}t}{:~o}»f}|s{{{{{{{{{{~{|}{{{{{{{{{{||{{zz{{|{{{{yz{zxz{{yz{uv{su{vx{}~{pq{zzz|{{{{{{{{{{{{{{{{{{{{{{z{wx{{{{{{{{{{sv|{{{{z{{zyz{{{{{zzzzǥzzzz~zyzrzzՅzz~zlz|zhzϐzz̟zzٟzņz}zkz{czmzgzozi{S{Q|6};}a|`}d}7~I~I};}zzzzzzzzzpzza|`}|ĭ{z{|_}h|z{ut{l}||Ϫ{{zl|y|‘||s|z~{~{z}{zz{~{zz{{{{z{{{{{{{{{n{zw{{{||||{{{{{||P~b~N~u}a~Z~Z~L~U~r~g}}}Z~`~G~K~~}R~P~}Q~t}|ſx}i}Q~c~h~\PDFNJAq~u~{~`M}Bh~ٍ}V~~p~k~E~~Cžg~J}~~p~JGs~؏}N~^~}}}[~o~q~Q~e~Bv~b~d~»g~FQ~~O~T~S~]~~J~d}D~A~_}@~Q~u}f}}U}u7~d}a}^}ؾH~=~C~?~H~I~y?~N~{D~}L~K~m}l}ՠ|P}|B~~C~U~]~Y~r}Ӈ}}v}T~}P~w}n}żt}q}C~}Z}f}t}s}_~l}}Ѐ}|ºv}o}x}K~{C~|D~zE~d}X}h}U~g}T~q}{}W~O~}v}z}P~W~n}|}M~H~L~Q~F~|F~~I~{I~}}|K~ڇ}}P~y}o}}s~a~f~a~T~Z~π}c}Žp}|^}D~\~{~u~Gx~Z~`~}}ǜ}}|}|y}|}T~}||}r~J~l}b}m}Ļ||ֆ}i~ƽe~v~a~M~Q~}}J~ي}y}|}w}|||||||{xz||||||||||Ʊ|g}w}Ŕ|Ĕ||\}ƾt}}Z~~X~ϓ}||~}¾}t}|e}f}||~K~[~N~x}||ć}}~}ùo}ʿo}ǿ|h}d}ƾy}[}e}s|{{Ϸ{˭{{{һ{ݸ{{h|n|o|{{{{ڼ{ʶ{ɿ{{|||||||||||dޤ~z|ū{{{{Ę{ޘ||{{{NJ~b~}||~|}|~|~|ז||ȑ|||S}U}U}¾|đ|Ɏ|[}a}Z}P}Q}X}_}[}b}ǹc}b}g}o}g}Z}d}̽m}}D~N~zB~|}x}w}p}j}|^}e}i}o}s}j}r}u}t}s}y}p}p}źo}w}}v}s}D~{B~H~}A~p}u}E~F~R~D~~=~úf}y}n}j}a}S}}P}[}ޚ||ٍ|ˆ|w||Ϛ||yA~G~a~^~G~H~G~|<~E~h}ʺ^}F}I}r|e|Y|Տ{Ԓ{{x{{f{k{h{s{{}{{{X|O|V|g|v|Q}a|c|{~d|j|LJ|z|ʾ|p|u|ީ{ö{{d|f|ݸ{{t|||Ξ|c}̜||õ|ϛ||||||ӹ|u}k}՞|t||||||||i}u}j}|Ҳ||w}x}v}~C~y}ȋ}ǎ}}}}̐}׈}_}u}u}}~}x}}\~X~Y~Y~{~f~}Z~c~X~ͅ}|b}c}}}}݇}U~Ā}X~J~x}z}v}^~ݐ}U~Ї}}o}n}}}˄}X~}ߒ}`~R~Ҏ}a~U~z}}Y~v~\~|ŏ}}_~b~}U~}}Ò}}n}{}|p}}j}`}r}Y~Q~f~P~v}̃}ÿ}܅}}}م}څ}و}I~n}}g}l}y}}p}u}l}k}}}D~V~H~9~A~D~<~}/~e}8}F}2}U|d|b|R|K|Q|p|?}>}N|k|A}=}z4}D|وzx{w{k{f{zz\{{L~g~F~F~<~Z~ķK}X~ǻQ~A~k}ĺS~y5Ʒ@~E>I~`~=:z4Z~6F~ƺK~d~6:A;x2`~Z~;KBEo~k~f~K>>j~d~F~E{7k~|~Ee~Z~IODJ?KYN~ʷ@T@2M~W~A@i~u~V}W}|z}a}e}R}]}g}|T|i|;~O~G}ˆ||u}}}W}t|c|k|^|h|g|Ϲ{{{{{zztvzyzyzgxxzaxwmx܈xPyxxUyxӓzĶS}B}X|p|@}A}s|=~E~x;~l}l};~8~:~f}>~5~6~[}M}L}|8}^{`{z^{]{]|m{u{zX{uzχzɅzb{e{R|i}v}ۍ||ː|{x{z{{{vw{zzzzzz|{}{ww{znn{no{zzzzqo{nn{zzyx{oo{{|}{yz{yz{{{{{{{zz{}~{|}{{{{{{{{{{{{{{{{{{{{{{{|{{z{{xy{{{{{{{{{{{{{{mp|{{{np{y|{zzzֺzzƦzҷzzzszbzpzҖzz]zzezynzrzyzozzzzzzazz_zz[zhz_zozM{]{zn{W|f{Y|L}k}7~O|z{f{zzzzzy{zzz{{{zzz{|ҧ{zww{{ľ|\}S}{z{y|z{{z~~{|{{zzz}{zzzzzz{{{{{{z{u{~j{w{u{zԷzzz{{~|||||{{{{{{||½|}q~j~?]~G~P~}j}h}ԛ|f}p}j}p}}^~_~ۑ}ߎ}B~f}|S~R~<@OHFz~}~EM8HULx~b~~M~Y~AFúd~r~m~~TEBo~a~q~Q~J~m}u}x}W~^~X~P~Z~v~i~o~w~c~z~Y~p~w}t~P~Y~Hb~^~g~R~i~r}|e}X~Y}||>~~;~<~>~x7~x;~r}n}f}@~?~y@~N~d}y}z}t}}}s}y@~}{}N~Z~ȿd~}ȉ}N~½}e}}H~|g}z?~v}H~A~F~o}m}L~Ľt}x}|}v}}}b}b}÷R~Z~{}U~j}j}v}s}C~h}{C~y}\~S~v}X~l}~}G~N~v}m}żr}z}y}ф}T~σ}Q~M~M~}~}q}q}r}t}|l~g~̇}P~K~o~\~M~L~w}_~}d~}B~g~d~h~}v~~څ~d~}}|||v}}\~}}o}}C~e}j}v}||Ϳ|i}P~M~X~l~N~}J~z~l}p}Ӑ}r}z}x}|||||tu|}~|||||||ɸ|||||z|{{{{|Ƙ|ȁ}}f~Y~v~h~f~s}|κ|v}||||i}X~}z}ؘ}|s}|x|V}S}u||`}c}l}e}a}b}|͕||z||u|v||||||||||~}|||{||||||||||||Ӝ~Ҏ|{{{{{{{{{{}Ң}||{|{{{|||U}]}p}b}`}֗||Y}m}]}O}z|~||~K}xE}P}M}K}X}Y}]}\}c}l}t}yC~~}r}}}s}l}`}`}q}ľ|}v}ý{}€}Ȅ}t}x}r}s}t}~}}}H~F~G~L~G~J~E~A~F~ĵO~A~G~F~C~>~B~q}a}W}W}źb}f}U}e}d}v}p}U~|I~ƿf~o~M~i~c~I~~<~A~m}i}m}S}||ľt|W|{Ƒ{{{p{m{a{ިzw{l{|{{̊{v{x{Ր{R|R|`|{ȅ{{{Ա{i|{x|Ɔ|e|{{{Ρ{ú{Ũ{í{Ľ{{{~r|{{|~}|{x|{{|||ź|c}o}k}|z}x}|m}u}m}{}r}w}s}r}v}‚}}p}i}l}á|۳|f}m}ω}|K~y}}}}}Έ}t}ľ}Y~Մ}؏}}O~^~M~}Z}H~|~~S~}||||v}}N~q~܄~s~b~j~g~z}ԉ}h}Q~m~R~k~z}l}x}v}t}ֆ}ƿm~w~u}X~ߡ}Ǖ}ٓ}}\~}ڔ}e~P~j~g~}Q~z}}\~Nj}Ƃ}|}}P~ӄ}s}ܓ}}\~W~i~Ć}Q~Ώ}P~}َ}m}Á}܇}}Z~¼o~L~¼r}ýx}|||ùk}I~J~K~M~ǿ^~<~4~4~A}P}O}^}E}?}E}?}R|j|U|\|X|i|>}F}g|k|]}E}h|j|\{m{j{Y{œzzzz{A~J~G~A~O~D~=~Y~D~ƺO~h}P~:?H>G~j}F=w.6Y~a~X~[~5ED{7Z~f~K~l~>>}9l~BEm~J\~m~r~b}n~q~k~KWAQ\ACmGSFzrd?b=ʻC219@z~|U}f}H}ĸR}q|]}G~}ڊ|t|H}O}X}Q}V|q|\}L~o}Ή|Է{{{ξ{f|i|ò{{{{{op{yxy}zy}}xwwzgxxyxx̴xxřxxg{\{{o|d|h|@}P}~:~f}~|c}z5~y5~z5~>~f}=~e}e}z4~M}t{M{Yz[z`zpzpzȈzzǒzzmzyizmzhzzp{g|{{{{{{wx{{{{{zzzzzzzzzzzzzzzz{{vx{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{z{zss{{yy{{{{{xy|{{{{{{{{{{{{{{٦zzx|zzy^{zޯzؤzئz̡zxz{zwz}zwezٱyyϤyyzjz۶yDzyȷyy~azyyyypzsz[{g{|V{}T{=|`{zU|E};}B|Վzܕzzzߝzťzzzrp{zzzzzii{zzpq{pp{{zzzzzz{}~{{z~{~{zzzzzzz{zzzzz{{{{{{{zֳzʲzڿzzzyz{{|||||{~|{{{{}d~ɂ}J~R~ɾT~O~M~ۅ}ֆ}v}E~R~~}J~Ĺh}żj}y;~g}u}H~g}E~F~w}|O~Kb~o~}>l~p~E@bo>\A~=Dƾd~g~z?{~[TPU~\~q~}~}~{@v~V~ь}Fp~˃}e}d}}Y~]~ۅ}[~t~Z~o~a~c~ԇ}e~Z~}~p~u~x~ڑ}i~݅}}}u}m}{}Q~K~]~y}||J}:~׾F~=~|8~W}{zE}}?~=~ӿO~F~M~r}b~ºw}h}H~c}D~J~]}z}}O~f}D~|C~J~J~ƾt}E~z}»h}A~G~}_}|D~k}}}D~V~J~`}h}}}ҙ|P~ͿT~M~~G~}H~x}d}r}H~߄}Q~P~x}փ}u}}[~ƻZ~Q~|C~G~P~̃}q}}܌}u}||~K~X~S~X~΃}u}zE~o}w}r}X~c~P~i~}x}[~Y~Y~|E~{A~Ǽ^~y~_~Ky~V~}}}f~z~u~e~~i~С}}}x}|u}P~T~R~v}|j}p}v}u}f}u}n}||e}K~}E~¹c~]~Ȏ}|}~}ۚ}||||||{||wx|{{{{||||||||Ĭ|||ݡ|{{{x|ľ}O~P~…}U~p~W~~N~R~b~~N~T~҈}O~y}~}к|||{{|^}|Y}k}}a~K~Ļm}|}|J}y}żp}l}X~Ԃ}}b}թ|ſ|[}c}Ҩ||||w|x|{{{||{|y|~u|||||||||ٖ{{{{{{{{{{{{{{{{{{{h|}|X}|ƹf}m}r}Ȼf}\}Z}a}X}{R}W}M}i|n|e|f|d|ȿp|ĺn|r|Ր|~U}|{Z}{Y}^}`}}}}}{A~s}n}Ղ}v}n}t}o}p}Ľt}}}}ʾi}ɽk}Ǽn}o}q}x=~{B~J~K~L~L~C~}}B~E~E~D~F~@~~>~D~J~<~>~y}p}`}`}q}y}އ}}S~M~O~k}Ɍ|U}|\}x}G~t;~r}f}O}[}S}zK}ùs|܊|p|В{{|{{o{f{i{b{Ƣzzf{l{{{d{t{{{{ˆ{Ŋ{r{{{{Ա{Ϻ{{ƨ{Ͱ{zɿz{{}{{{{{{{{||||}g}||ٸ|l}|{}Ň}j}q}ʼn}}z}x}}}}}}s}}ݘ}}ʋ}݌}m}x}΅}H~z}}y}}ۂ}}X}i}\}`}a}|j}}Ԉ}~}}{H~r}Ղ}\}Q~s~h~}|`}w}Ч|o}Ս}z}O~V~þu~^~}T~|O~}}t}}ߗ}`~ړ}V~؅}t}V~R~N~n~ý}ľ}V~}}҅}Y~}ԍ}Y~~X~٘}ϓ}r}n}v}Ɗ}Ä}r}|م}v}Ʌ}h}y}}}ň}}}}ʁ}a}l}܉}r}i}]~Ā}I~N~J~T~G~`}Č|}}g}ސ|s|X}y}C~K~<~K~;~K}Q}5}6}C}6}[|Y|=}Y|]{@||?|\{K|W|G}P}\|d|>}H}H}Q|t{B|{i{zz|\{p{8~E~c~S~:ǼO~U~l~ɿU~K~F~ӄ}H~m~@6:O~>Bw2]~9~7~4{3HD@U~{3b~`~}~t~A:v~r~{~n~s~;k~:K~v~S~c~EVHdVPLrRAnF?A^[OJ73-?X~X}z}:~c}B~d}Z}:~M~]~J~|Q{|{{~{{{[|{{{{{pr|{{{{z{zzx}xyzyyxwwʼw̾wxxyyxwyT{yyzzz~o{–{O}P}r|ĸV}Q}_}Y}y4~y4~@~]}z5~9~;~B|ozfzizTz\zlzxzrzozxzzܭyz{zyzzzzzzz{{{{{{{~{|~{}t{v{z{zu{{{{{{~{{{{{{{{{{{{{{{{{{{{z{{{{{{{{{{{{{{{{{{{{{{{zz{{{{{qu|{{{{{{{{{{{{zzɽz×zđz޼zp{a{^{zzˑzNjzzڏzЖzyzgzyŤyyԥyyyyyyȥyy۠yܞytzńzzzzR{zkzԃzzz`{U|X{z|zvzƀzǍzzޘzzz~j{zzzzzkl{zzzzzzzzzzzy{~{{vw{v{{Իzzzzzzzzzzzzz~{{||{{{{{zzĦzzzzzz{{{{{|||~|{pq||}P~ڑ}~MCS~j}|F~}P~Q~K~E~a}\}|T}X}Ź\}g}k}m}E~L~Q~[~`~IV~R~?82CIPoa}5R>RF}~r~GRUFx~U~i~b~y~c~x~~@Z~U~~~~X~d~Z~ޓ}r}M~M~y}r}m}m}~G~y}R~a~R~T~}V~W~z}|||q}F~P~;~n}B~M~t}]}>~F~F~n}{@~v}}²w|=~K~q}y}}U~L~ڇ}Q~v}x}}I~|}G~yE~J~w}a}C~x}r}F~|}V}n}J~E~g}]}b}}\}K~g~g}π}N~{}U~e}C~M~}}p}}t}y}}D~F~Ľn}_}s}G~L~Ƽ[~V~^~J~V~}{}V~Y~e~[~}U~Lj}P~ź^~L~M~|}G~X~t}||U~k~h~^~M~ړ}~}zJ~n}|Ą}¹`~V~g~m~d~S~{}|}a~Z~}]~~h~úh~M~ͅ}|}ό}P~_~}q}y}|~i}{O~Z~T~Q~}|||||}p~\~}}}|O~}||||||||{||rs|{{{||||||z||||||{||{||m}|þ|z}}}T~f~}}I~}s}a}|||{{{|||}S~Q~|I~}Օ|j}}y}k}ٍ}|||}|||o}}}|s|s|||{{{{{{ʫ{{|{{ź{ȼ{{{Џ{{{ǻ{{}y||||{|{{{{{{u|{{r|ļ|\}[}e}b}X}Z}`}g}U}U}S}Έ|Y|{{Y|{{{{{_|z|Ԣ||¾||؛|W}g}v}}}m}{}o}g}m}{}||Z}q}j}[}S}d}^}c}J~|C~T~I~D~{?~o}o}t}{}}@~>~y=~r}j}s}x}s}k}F~E~w}{H~|H~S~Z~}k}[}|ʍ|ļ|ǿ|b}R~~D~i}ι]}T}O}я|ۖ||||P}ȃ|a|s{zzzu{t{l{zȯzzzi{k{zx{m{{ܐ{ݖ{{{{{{{{{{{γ{y{Ļzz{z{z{{{{yy|}}||||x}ʼn}}k}x}t}a}x}ʿ|ܼ|q}ſ}ɋ}v}s}}}}ć}}}Ր}}u}½}ݑ}}ă}t}Ӈ}~}žz}o}׊}q}u}}NJ}ċ}~}}}}q}k}p}}΄}]~j~W~Վ}R~v}h}|}úp}|g}ľ}Z~t}}M~x}||O~P~ȍ}n}{}}}_~{Dv~ߗ}}X~}W~ºz}s}u~ٖ}Z~ýt~}Q~Z~i~{~d~}}v}}k}Ɖ}z}o}|Ʊ|n}`}ދ}Y~}~}я}S~ƅ}ƈ}}}|L~W~~}܊}t}}K~R~}̂}X~}}}e}e}X}|}}t|s|y|p|S}~>~G~I~6~C}C}_|f|`|Y|6}6}M|l{V{[{=}žg{|{m{^|;}Z|R|7}÷M}B}Y|b|r{r{u|Q|dzzܑz{c}C~x0~¶K~k}J~`}n}|||||||˜|}}Y~]~9[~]~|4}ɽJ~=TL}<L~b~6^~i~ſe~q~o~:p~d~b~a~d~W~j~g~m~A=x~I?x8CLgF[d\:GW6F>=/{,5X~|9~T~U~H~<~g}H}@~|<~M~|{zxz{{{{{z{{{{{{{{zzyxxyyyyxxxwxxyyyyyyyyzz{z{{zp{zs{^|u|e|e|q|b|ɖ{{՜{L{Ъy͢y_zyhzyzzޞzozydzxz}zy~k{{zzzzzzЉyuz{{{zܗz٣zzzwzezpzzŠz}zyzz{{{{{{{{{{{{{¹{{{{{{{{{{͞zz{{{{{{{{{{{{{{{{{{{{{{{~~{{su{{{{{{{{{{{{{{{{|{{{zxzzzXzuzzzyH{Y{T{߅z}zzҐzzݹyԫymzyyyy}yyyyyyʡyYzqzzzيzM{zzzzmzyzqzwz؜zܦzzzzgzgz{zzʎzzzzЎzzȓzzzzzzzzzzzzzzz{z}}{zdzzyvzyzzzzyzzzzzz{{{{zµzzzyըy{z~vz{||xx|}}||{{{{{{n}j}DŽ}r}Y~`~JWF~G~e}k}}O~g}_}||Y}_}}B~N~_~L~Z~:;M~X~E~K~i~:2<:RbĽVYENǾWPEt~{~Mv~|~w~}~x~_~b~Hp~z~^~}e~~ſl~[~ݘ}E{}ݒ}v}w}||ֶ||l}>~ĹR~j}t}v}{}[~Q~Ĺ^~}o}}C~y-}4~>~f}d}A~|=~k}øW~\~M~}Z}o}|}ƽm}Y}A~C~u}e}Ǚ|Z}>~s}x}b}z<~b}Ⱦj}f}N~I~I~]}||m}N~F~Żm}Żm}p}Y~Q~R~}ƒ}|I~m}S}Q}A}P}>e~v}ڃ}o}|c}v}K~}|C~H~C~~}ŷf}x}b}U~ún}݉}Q~R~}|}U~M~P~̊}Q~x~Y~X~¹a~`~X~̰|^~s~R~R~t}}}T~}c~N~|||||l}|A~}n}S~V~Y~}V~U~}҂}J~Њ}^~źh~g~P~O~N~O~\~ǽg~}}a~e~~x~P~ڑ}z}r}r}ּ|ܮ|}L~e~f~Y~d~c~{}|||||||||||{{ʻ{׾{}||||||||||||||||{v|p|NJ|W}g}|׽|n}d}z}ܘ|أ||||||x|v|u||n}M~\}{m||m}}a~~}̾|ĵ|||{}|h}c~½}|g}¾|{{{{{{{{{{||{{Ő{{{{{~||˷||||||||||||x||]}T}G}N}x|}|~|J}|l|w|`|Ő{ȕ{ԣ{У{{{{{Ң{x|ř|||v|x|||X}\}p}x}}}{}Ձ}݇}}n}˜|||ژ||ſ||˒|W}h}U~I~xB~t}l}^}^}^}a}q}p}i}ȸc}a}ƺk}b}i}ƻd}`}p}C~E~I~|G~|}w}f}b}}H~H~d}ݏ||O}Ҁ}y}h}T}ׁ|ļ|x|p||ý||{|b|{z{{zzįzzzzzn{{o{zؾzz{{{{{{{{{{{{{{{{{{{{{{{{{||||||||}}}V~~N~ʆ}{}o}w}Њ}Ʌ}}͕}}w}}}}}ۈ}}|V~Ӝ}}o~}}}ǿy}z}v}r}u}{}}z}ی}v}ˋ}f}Ň}\~N~΃}؈}}ž}}V~m~}}Z~z}žz}J~n}|d}j}||w}}}x}{~S~}|~n}z}w}s~Yw}}֟}R~c~N~U~}ϒ}c~a~ɇ}}}}|p}S~y}٬|}Y~}¾}Ӓ}ؕ}\~o~}|}_~s}Ʌ}Љ}X~|}ň}T~}O~݉}K~ц}}Q~܇}}T~}}m}e}|Z}m}`}Z}f}v|P}A~O~C~8~J}7}5}M|=|=|C}]|7}N|;{V{?}}?|L{i{C|g|z4}~;}D}E}a|Z|K|W{{^{ߐzvz،zX{z{u|o|{{{}|su||{{{{{{{{{{|ݍ}R~@~`~Z}9~7½P~ŻR~T}ĹK~z4K;S~t~AĽ_~l}g~ľ\~f~Z~n~v~s~:;>I7LLz~P~_~dGT_KP}42|*_~<~?ɺ=16ȸ=L~U~}3T~@~N~}|¾k|V}I}zzzqryxyózzzzzz{{{{{{zyyxyz}zyy|zzxx}~xxxxxxxyyyxsryyzzz{zzz~r{ſ{zz{{{߆|ȿy|i|e|ą{nzzyyyzzzz{ozy|rzxzzzrr{zʺzzyyyxyz{zzz|zwzzІzyQzyvyӁy˄yyy|izzz{{{{{{{{{{{{{{{{{{{{zz­z{z{{{{{{{{|{{{zzo{{{{{{{{{z{{{{{{{{{{{j{{{Y{zzzzrz`zaznzuz؂zzzzzՄzލzxzgz{zuz|fzzzuzyxy{xyyyyyyykzzztzvz^z\zbzmzy}fz‚zӎzz{zizdzyxzlz|zzzmzsz|zqztvzzzzzzzzzzzzzzzzzzzyzzzzyyzzzz{{{zzzzzzmzyyyz{{|||||mo||~|{{|||˲|q}ϊ}e~o~ZCr~ބ~Y~V~}|}g}zG}|n}d~Yv0H~b~G~e~a~ĺ[~b~`~}7<_~^~n~QVANWKIĻVw~ûb~b~|~Z~IEIe~h~x~q~o~p~`~d~v}a~e~|@T~k~}h}x}k}|Ϊ|ÿ|b}^}h}u}G~X}f}P~Y~r}=~P~S~]~L~D~X}ǻ}|^}a}A~˻T~x=~h~}Q~y}h}t}ˈ|=~y}a}ؒ|R}t|M}t:~`}g}t}}Z}^}k|M}j}{P}|R}P~D~ʾX~n~i}zD~?~E~>R~r}}[~X~q}܂}P~F~^}Y}t}X~}}|\~u}̇}|x}F~I~~@~v}Q~}}ľ}||ϙ|^}m}w}|E~N~P~^}r}?~p~n~^~\~]~^~€}ؖ}f~e~[~^~\~w}}I~L~M~N~ی}q}||}ӂ}E~}~}|}L~U~}x}||պ||[~}j~f~[~źc~ƻc~g~R~y}|Ǽ|}s}u~f~}}l}M~N~~g}ڏ}p}ގ}e~o~X~b~\~ʘ}|}|||||||||||x{|{{{ϻ{ֽ{ټ{{|ö||||j}˿}}}}t}||~|{{|||||}||r|v|\}ь|r|˖|Д|Д|||ߚ|ء|k}|||||||}͚}Ӡ}[~u}w}Ԕ}U~}}m~ߡ}ĵ||}|V}|p|r|{۲{e|z|x|||||||ٜ{o||y|{{|ޙ|q}߲|||||{{|~|u|̓|}|Źn|m|c|^|X|{T|f|r|{{{{{˚{˕{ѝ{{y{{{k||o|i|j||֗||Ԙ|S}Y}j}s}b}i}n}^}k}Č|x||||||i}i}}y}g}W}P}N}}T}|X}`}n}d}`}j}_}\}V}`}a}Y}Źj}p}b}d}a}_}Z}|||}g}a}h}W}q}p}]}߅|j|t|{Ф{{{{ݹ{{{ʧ{{{{{{{{{{}{v{{{z{{{{{{{{{{{{{{{{{~||||||xx||}~|||||||¹|߿|i}|i}h}ڏ}u}v}Ɍ}}}{}}}}Ɗ}|}{P~}}X~Ȏ}|O~|O~]~}΋}ƾ}}W~ƒ}h}k}~O~̓}i}j}ʇ}ێ}}Ԅ}X~h~T~V~S~k}||}u}S~Y~}}N~֔}}}{}q~Ŗ}џ}^~d~q}}̉}|K~S~r~Y~Q~Z~]~\~]~V~e~}j}|s}~X~؏}c~|[~U~Z~k}}Y~}|}ה}x}}}؏}ÿ}z}\~}ڌ}m}x}z}}[~Y~Y~}L~}}o}ݎ}}̄}n}e}n}d}s}h}T}ǿp}h}g}r}{;~|;~<~;~O}:}?}2}\|Z|`|a|M}|2}E|V|Q|Y|N|~>|L|m{V|;}f|A}b|]|`{^{M|ըyrz~zŇzĎzzz{{{{{{{{{{{{z{znm{{||xy||~|`}C~g}[}2~d}g}t}O~W~A}3n~|>Ov~f~A~H~l~j}7_~u~B=~;NBeXc~l~?UW[d~?ARAH0K~F~C{$B:Hu(J~5W~k~e~}S}R}{zzzyyDZyyuzyzzzzzzzzzzywyyyyzzyxxxyy~y}|yxxxyyxxxyzzzнz^||s|\|||M}W}j}h}t}i}m|Q|}{e|{zzzzzyzz|uzyz{zyƳy½yyyy}nyy|zzzqzxhzyyozsYzyyzyǗyy{Yzyƍyyqzz{{{{{{{z{{{{{{~u{{l{{{{zzz{|}{{|~{{Ѹz}|{}~{{k{n{zzɥzdžzzz{d{z|_{|_{z{{z{z{{{{{{{{{h{Ϟzzzȇzvzwzzvzyozhzmzrzgzpzaz{zyzezΟyyfz~bzyz`z|gzyxxyvyyzyyyy—yez`z~zcznzczyvzxzکyx_zpz|ztzrzozyyvzyzz~zzzszozyyy}zzzzzzzzzzzzzzzzzzzz|}z{|zyyyzzzzzzzzzzyzrzyyz|||||||||oq|uw|||||{o}x~\sqnO]Gb~?}Rq~G~x:~C;n~÷M~N}w}G~¸V~[~:T~{1V~E~J~÷U~l~_~|;>LAAEIz~r~T~R~ø]~_~V~~Ct~j~l~~o~a~^~Q~g~o~Ft~}g~a~]~I~r}|V}F~h}ɐ||Ј|i}=~@~;~L}V}ʷI~>~:~D~;|}|q}|@~K~g}}Žy}l}b}@~̿i}U~}w}zE~u}x}|||Í|U}b}p}K~F~w9~|Q}}^}yJ}z>~J~L~]~q}؍}]~B~B~U~G~_~R~Ep~Ǔ}|}K~[~~^}q}}}S~N~|}|j}m}|@~üo}W~}z}T~ܮ|q}|I~j}||z}w}|D~n}B~A~o}|w}}S~{}B~_~S~[~L~݇}Z~S~a~O~}f~~?~D~Z}h}|||||||ا|y}φ}p}|G~b~x}|»|||||}e~d~^~Q~J~U~C\~]~K~x~O~m}}}v}z}}Ӊ}ƿ}}}^~ƅ}R~|O~}Y~½}T~w}|||||||||||{~|uw|{{{{{{|||ƽ||ߌ}}N~d}m}{}|{{u|{{{|||||||||{t|{|||{z||}޷|v}k}w}ڕ}}Q~}ڌ}ת|}}k}u}}}}U~b~Z~}S~a~\~[~}T~̈́}ʎ|w|ʌ|؜|ב|͌|{|ȇ|Ӑ||ԕ{|v|{|t||m||G}[}ڨ|Ğ|||||||úz|q|ʁ|{{{{{}{u{~{Փ{Џ{ɐ{{ޱ{{ߴ{ȗ{Θ{{{{ܨ{ښ{j|l|f|c|{t||ƌ||||Ɛ||v|k||[}a}||||ę|ө||Z}i}W}^}S}|ߋ|̃|Ό|Η||_}|b}W}`}Y}T}_}]}[}]}[}`}W}V}Y}e}|a}X}V}\}Y}c}k}l}c}R}g|j|~b|{{{{{{{{{{{{{{{{{{{{{{{{{{||||{{{{{{xx|wx|{|||y{|||||||{{|||||||˽||ʶ||w}p}w}r}h}r}q}}}ܓ}n}y}Ό}ؑ}}}}}ϕ}{}}}Ō}ˍ}Ր}}ڑ}Q~ڔ}s}d}\~|N~N~R~}g~R~\~U~Ӌ}t}}}؏}V~V~|M~}ڑ}}\~ǎ}}W~Ϙ}}c~~K~|}Z~|~ºg~W~}{}^~}f~ٓ}֍}}V~]~}}ǃ}_~Y~ϒ}[~W~]~܎}i~m}}u}}|r}͒}r}}W~}}q}z}}w}{}}Ȇ}Z~g~i~N]~_~]~\~q}u}}r}͆}w}t}^}`}}E~}}~}y}}e}B~C~K~z<~s}T}U}N}:}6}E}c|Y|6}@|e{zh{W{~:|?|I|ǾX{l{=|_|\|y{ܝzA|Y{yynzbzuzzz{{{{{{{{||||{{|||||{|{{||||||K~|<:d~~9[~`~I~I~C~C~]~k~S~ABFBEGiR[~o~FKPK<BA4÷@»KS;I0H:ʴ5/t%ǷE}/~]}c~a~I~u}z}Y|yzzzyy¯yyyzzzzzzzzyyyyyyyz{zyyyx{zyy~yxrsyyy}yyyxyyyyzU||P|o|ǒ{zB}ο_}T}Ǽ^}d}S}[}^}O}w|y{{wu{vv{zhh{zzwztzyyΜy~jzzϻyyqvzyyxgyߥyyͰyԨyƝyţyÖyѣyyy}yfzgzyטyjzjzjzdz|dzz{{{{{{ɩzzͫzz}z{z{zzzzzzzzzzz~zzzzƫzz۬z~zܦzˠz֪zfzezmz}zzтzҕzz{}{zzz{{{{{z{{zzŚz٠zЀzrzuzuzz}z{Xzozdz[z]zTzgz_ztztzyyyw^zީyvTzךyɥyyyxxz}yyyyyԛyx[zczfz~lzbzyݦyjzsz{gzyyy}zozyzxzszz}{z}zyszwztzoztz~zzzzzzzyyzyzzzzzzzzz}~zzyyyzzzyzzzzzzz~zy}zyӻyz{||||||||||~}r}ɪ||{v}I~R~z~SEVbCK[~h~x}f~b~Q~\~~5ŹR~úT~R~{8~J~O~ǻW~ɼR~?Q~J~K~A~ͽM~öL~~A~c~?KI@}:CGm~c~Z~w5Q~F~}`~J~a~e~GDg~~o~M~i~Ĺd~Q~Y~V~t~V~W~w}i~L~Q~K~|}|p|~|Ƚv|a}M~N~ͿT~ŷN~z:~b}g}R~ʿZ~N~K~V~Ľx}|i}Y~h}||j}x}|N}̸V}N~x<~Z}[~{}yM}||W};~[~U~~F~}G~S}U}P}ɽT~o}U~z>e~}~K~O~l~w}L~}J~_~}\~|}c}a~v~S~_~m~}L~y}}k}D~u}|}n}ć}~}W~ώ}||||||=~z}|}K~V~?~X}z}}H~K~}x@~r}?~y@~ƽ_~̀}_~T~M~}|s~x~?e~Q~||j}||||}|X}h}r}z>~x=~[~F~ݻ|ɯ|||~\}L~L~Ƹk}{C~I~Ʒ`~n~Ǽg~}O~X~X~W~Ά}P~}X~}~}}}~R~¼}ʌ}݉}T~}^~p}|||||||||||||||||||||{{|||||ŷ|Υ|L~Y~~E~|||{|||||{}||||||||||||Q~I~W~q~c~K~p}ۄ}u}e}|Т||n}}Q~_~|N~L~^~g~~e~a~~{~S~͂}_}l}_}|Y}|u|ƺ{o|{Կ{k|f|Ĺo||A}ܖ|||}|x|~|||||މ|{{{{{{{{Ȩ{{{ĝ{{Ȣ{Ǥ{۩{ɝ{v||||{|{{{{{a|t|q|t||r|v|{|s|n|s|y|Μ|—|Ϧ|||Ƣ|ȧ|||љ|[}|ڔ||z|¾||o|o||Β|ԙ|ƿ||͖|U}|S}Z}R}Q}a}R}W}|F}^}p}n}p}q}нj}Y}^}ҿg}Y}X}||{{{{y{{{{{{{Ƹ{{{{{|{{̺{||{{{{{{~~|{{||||{|||||||||~~||~|{{||||||||٭|կ|||||||l}}շ|f}|}|u}~a}j}޿|}}y}w}y}}~Q~}z}}֏}֏}ޓ}S~}Q~t}u}Ս}b~}}Ć}|}T~T~G~q}Ί}}s}¼}}}}`~}X~ې}}}˃}y}Ǎ}S~݀}}t}~}j}V~ԁ}ƾt}}Ŀ}k}}X~Z~Y~V~ߙ}ň}\~{}T~d~}}Y~}S~T~T~߹|Ί}ֵ|m}}ҍ}͍}؍}}M~}}}ʁ}֖}}y}΄}X~ûe~I~}Nx~k~O~_~r}k}{}U~P~||n}u}z@~S~R~c}K~t}F~A~i}>~?~d}G~\}οO}6}4}/}a|F|S|c{n{H{zU{R{T{F{L{E{I|O|w{Ɋzw{zy޸yƈzkzvzzz{{{{}|{{~|{{{|||||||||{{{vw||~||||||||p}}~}ýo}Q~\~{7X}Q}M~s}D~7V~@=?QI\~p~d~n~EBľ`Uw~TV~<5?ʽAH˻=<6&5w#=4}P|D}ʼH~P~\~d}zzzzznnzyyyyy׾yyyyzzzzrtzzzzzzzzzyzyyzyyyyyɻyyxyyuzzzzyyZ{h{z{k{º{j{i|J}U}k}ƽb}N}N}i|k{z|dzzzzzzzzzyŻyyyymyyyyzyyԙx×x֮yyyyĦyyx{yzyyyyyxy\zYzbzdzWzvzz{{{}{zzzzzʿzzʼz­zzzzz{rr{zzzzwzzzƦzzzzpzhz\zgzyizkznzvzzzw{t{zzyzz{{{{z{i{zҌzzwzxzdzdz~izxzČzƁzrzwzhz{Wz]zdzbzyTzyyxpzy؏yϐyyyyyy~yxyyԎyyסyyizfzkzqzyfzyyfzpz{bzyyez|aznzrzzzzzzxzfzrzzzzzzzzzzzzz||zzzzzzzzzzpqzpqzzzzzzzyzzzzzzuvzyyyz{|{||{||||||||||||΁}`}L~=X~a@~9CKA8V~};Bg~Z~[}{?~t}Q~żW~C~{=~»j}}P~]~E~ôN~[~F~G~D~>~M~D~c~An~9=Bm~W~U~d~ŸV~L~µW~S~U~N~·[~V~u~g~|~~E~w~O~Y~~D~O~^~f~ým~^~d}R}x}]~X~_~_}|e};~@~n}l}K~~>~D~a~m}~@~H~L~ب|F~a~Y~ûj~}g}}}e}||ќ||õ]}I~|?~K~Q~K~v}e}`}`}:~C~ǿo}z}y}}R}x|v|r}A~õS~a~K~}Z~Ɍ}s~J~r}P~[~|l}B~]}x}t~_~h~~e~b~K~~}n}Τ|O~S~m}s}M~˂}x}~}|C~e}||[}J~H~F~F~A~[}D~I~Ĺc}|||S}|}u}F~ʼY~T}l}k}c}k}h~u~l~b~f~Q~{}q}ـ}ƽr}q}ޭ||v|q|}|zL}x=~v}X~^~g}Ȥ|ӥ|զ|t}j~^~R~N~u~Bސ}øa~˿e~X~\~}͊}ً}\~^~S~ސ}{}w}}~}}Ɔ}Ґ}s}|||||||||||||||||||||||uw|{|||||||g}}`}f}||||||||||{||||||||||d}i}ς}}؏}։}}ýz}n}L~m}w}|g}i~~_~[~k~¹g~r~úd~Z~W~T~`~ün~e~ϑ}|}ǿm~[~p}ݢ|͔{{{ѩ{±s|{||B}ԋ|ļ|w|{޿{u|||ϐ|҆|{{{w{Ϋzѧzv{{{޻{؜{Ù{Ц{ϭ{Ѫ{Ա{֬{{ڰ{ɴ{{{{{{٭{٪{{d|f|[|j|||||ٯ|c}||ɟ||~||γ|ݼ||\}b}\}|ѭ||¿||y|z|x|n||||||||ˎ||S}R}͈|S}W}Q}U}xK}_}i}i}U}|֋|r|||y|ۆ|˽z|Z|{ǝ{{԰{{{{{{{{{{³{{{||||{|{{{{{{{{{||||||||||||{||||||{\}e}k}k}p}l}n}ϯ|||||||i}x}v}x}}^~ņ}}}k}NJ}R~^~ٌ}}}~}}x}}Ս}ؒ}}y}}m~ц}}}ؒ}}ޗ}ܑ}ؒ}V~}d}|f}m}}ē}o}Ԑ}}Q~~P~}a~ޕ}}}p}؃}\~م}͹|n}]}b}e}c}}ޗ}e~΃}u}ђ}χ}m~z}Ĉ}W~Ђ}ي}^}i}r}Y~}u}؊}ûb~l}g}Ї}Ȅ}S~P~P~}S~Z~\~}|L~d~}S~Ȍ}}c}ľ{}U~Az~]~Y~}~K~~}c}|Ӂ}W~T~G~m}|||_}s}X}|}c}o|`|N}Ľz||y;~R~J~D~W}7}F||/}4}>}Q|v{xzc|C{=|;|s{f{P{O{z=|h|ʃzY{z|zyz{z~{{{{{{Ż{{{{{||~||{||||||||st||||||||||||||||}w}̿|i}}F~n}k~o~f~`~;QEy8\~R~<MQPP7~E=>82ĺ<;Ѿ:=:Ѿ<+Ϸ4?پ6)~`{[{F}X}}V|zzzyz{z}{zyyɶywyxyymzt{zzzuvzzzzzzzzzzzyyzzzyyyyǥyyyyyxyrzvzR{zzzvuzz{c|ڊ|{E}z|ܝ{{zyy|yyyףyzszzzzzzsvzyy°yάyyyyyyϛyqyxxyz}jzpzбyyyyyyy|dy~pyyyazޤyբy~[zyRzVzhz{{{{{zzzsu{z~}z|vzy~{zzzzzzzzzkj{zzözzzpzyz^zyǝyѡy˛yzųz˵zzz{zy}}zyyyzuy{{{b{zzݛzmzlzbzgzbzܬyydz߃zuzzn{lz^zyyz`zyyyyyi{ypyiyxyŜyyyyyyyõyبyyx`zy`zyߥy˪yҾyyȖyɵy|rzyyʴy|gzqzymz{zrzzzz{zazpz|z~z~zzzzzzzyyƽyyyzzzzzzzzzzzzzzzzzzz~zyopzzhh{yyyz{||pr|}||||{~|||||||{{|f}k}y}х}{=GIC@ŸP~J~R~s~V~U}c}u}e}|S~Z~M~C~B~o~K;W~˺K~W~[~ŵJ~G~A~ɻM~?~I~ϿS~_~c~r~p~ċ}c~U~P~Ÿf}K~ͿY~I~Dq~i~S~GP~L~a~l~s~e~_~X~P~K~I~l}[}?~T}>~c}~}Y}T}o}?~E~_~?~x;~}H~]}Ļ`~F~xB~|Q}C~F~h~ƾj~}}S~S~}M~h}f}b}ѭ|n}ی|Ą|̄|P}Q}]}R~F~]~G~M~s}}Λ||p|n|{||Z}Ϳd~G~=~|}Z~~}R~W~}q}:a~i}||b}H~_~[~f~]~n}X~Ѐ}]}p}r~Մ}P~||}||H~÷g}=~X}j}I~M~ȼT~F~@~3~O}C~w2M}ˬ{{{a|e|n|ȼf}A~>~I}|o||e|~C~q~Y~ɿ_~¹a~\~ڈ}M~}L~Q~x>~d}|||||Y}\~_~Ⱥs}`}d}^}{}U~܈}x}d~s~q~x:AM~\~T~t}{}}S~׊}}}ɾe~[~ӎ}}}||||||||||||||||||||||||||{||||||||||r}ʣ||k}}||||||||{|||{|||{{|{||ɰ|||||||s}ϛ}a~|||Ð}o~q~|G~c~j~s~s~Z~|O~`~[~ȉ}}~N~؆}х}W~P~r~_~V~O~y}}J~}n}Q}]}a}|g|w||k|Z||{{{zzg{zzi{x{x{ݓ{{»|ܡ|c}u}e}|}|̟|ǖ||Ѐ}w}}}}}j}c}^}ݞ|||||̣||||۪|||||Ҭ||h}p}|}q}c}Ԭ|||||}||Ɍ||||u|z|Џ|Ƈ|Ɍ|ъ|ؒ||P}X}X}W}|M}Q}L}Ё|[|ݱ{{{֪{}`|{ļ{{ι{{{{{Ǿ{{{{{zy|{xx||~||||{{{{{{{{{yx|~|{|||||||||||||||||||t}h~{}k}l}|j}||{{{{Ż{{|]}|ə|՚|e}||||̄}I~؃}|Π||Ի|~}W~}ܗ}ʑ}z}ԏ}ȉ}v}T~ю}}}~}}~S~}}{E~[}z}Ñ}݌}}ܑ}؋}X~d~`}|}N~ľi~S~h}]~}t}o}O~g}u}d}|D~݅}}r}S~||||ό}h}҄}g}r}h}f}{H~O~l~||}N~v}t}ۏ}Z~Q~ј}ؙ}m~N~V~R~X~Y~o~Ļe~s}u}}}}V~Z~Cn~r}݂}܂}}s}m}^}X~^~}J~S}m}P}ƽm}|}V}|{{r|\|J}g}|:~6~K}2}6|N{A{>|M|6}a{hyI|Y{@{A|R{hzk{Q{fzo{B|rz~T{Ězzzst{{{{{{{{{{{{{{{xy|wy||yz|{|||x{|sv||||||||||||||||}||||||||||o}żW~AKHHs~y~Y~d~Jb_~=}029K?:5/̾><ʷ8Ű3)@~D~Ȱ3~@}B|?z={J{}zi{{zzzkmyxҔy}Kz~ynzzyz“zz|zyyyzzzzzzzzyyyzz}zyyzؕz}zyxyz_{j{zuzyyzwy{zzzz{{{zzzjyvyҝyyymyyyykmzyyyyyyykyyyxxzyxy{zyyy~vyÙyۢyyv`zӞyyy~ywyyyyyyćysyyzۻz{{{tv{pq{ll{qr{uv{z|zy|~zzzyzzzzzqp{{{{{{{{qu|{zyy|{zzzzz{zzzzz~~zyyľyzنzrz|zlzwzez~kzyyizuzuzŃz~zzzpz{z{pzݮyϥyyyszϢyvyM{Q{wyhyu\yyayzyzyyyxzyyyȮyyyhzyczyٹyўyyyyyyӪy٠yϮyygzyyhz{lzmzzqzy]zizmzz|zzzzz}zyyyxyzyy{zzzyyzzzzz{z|{zzzzyyyyyppzzzyyy{|||||||{||y{||||~||y{||||||ͭ|{;~M~x~z~]~]~>~e}]}В|~|L~<~۔|~=~q}J~J~J~^~G~N~̿X~\~{2P~F~=~;^}g}H~@~z9~y6~T~d}f}I~p~Y~h~Ht}c}U~s~O}~t}}~~T~O~}H~T~l~o~X~V~Hr~\~U~o}G~p}B~~B~t}u}m}c}Z}<~E~ĸi}[}~=~|@~\}w}K~`}Q}:~d}˻Z}<~p}b}}h~}¿}c~U~W~V~Å}f}z}k}ё|y|{|W~Na~ɿn}q}U~T~R~݄}T}|~f}}~{}˼[~Y~W~|B~C~t}I~G~S~O~q~H~s}{}y}o}{}||d}|]}z8I~|}n}||||{|T}Q}w}]~^~a~c~H~_}:~=~I~U~E}g||||d|g|Z|G}q|L}A}R}Q}T}z||~|u|m}K~Y~E~}g}~<~B~J~F~H~c}|g}y}i}E~t}}z}Ȩ||ܰ||||a}{<~a~O~_~?\~ň}s}|n}~E~}}L~}}ȼf~{}|||||||||||||||||||||||||||}~||||||||||||||||݄}|̼||||||{{{{{|||u|u||e}|~||||||{}}}~}v}|||x}}}X~۠}}}|||||}||\}u}}b~U~g~UI~x~n~~w~}~v~¼n~r~~~v~z~S~O~}}}S~q}\}V~Q~X~Y~]}{g|||K~׀~~p~~}~{~~u~z~Br~`~~~h~e~}Y~ߔ}}g}i}|||ק|Ü|Ȭ|Ȧ|ζ|Ȳ||ֶ|{}ܐ}҈}t}k}|Ԯ||||Ӑ|p|e|n|j|a|n||z|s|k|s|~Q}R}|X}|Ƿ||g|V|{{Ԯ{p|{{{{{{{ȼ{{{{{{{{{{{{|~||||||}~|{{{{|{||||||~||||||||||վ||~i}f}||X}}[~}žx}r}g}`}}|{~o|ȸ{{{|||{|`}b}m}l}[}թ|||||_}b}|j}s}|}}n}||t}]~Z~¼|}}P~U~~K~{N~}y}|}s}y}{}n}j}}ք}ٌ}Ȉ}}Ն}M~x}{}N~Y~b~c}[}q}k}ϗ}}ي}}ɑ}}}ò|||T~l}j}ޓ}}j}ނ}}j}o~}Ȏ}v}x}_}ą}}}T~U~½}}~L~Q~K~}M~_~o}e}L~h}N~q}υ}n}f~o}W~`~T~}t}R~M~}}~}Q~c}x}N~}q}e}||\}\}|ˁ|s{{|Q}V}/}:|T{iz>{gza{ezlyWyKzuzk{;|`{X{F{izmzyZzhz~ozz}{zz{{{{{{{{{{{{{{{{{{{tv|||||tv||||{{{{{{||||||{{{|y|||||||||p}t~@At~v}f~r~CWi~r~:44}1:>6652y%Ի8~H}}9}B|˶Y{]{~G{fyZ{G{PzzzB{ùczۓy¢yzyyyyxyzyyyߡyڦyyxzszλzz{{~{zyzzzzzwxzyxyyzzzzz{ܕ{D|Ϝzyyyyr{ڷ{z{oo{zz{zzqzyȨyyyxxyyyyyyϺyyyuyyy}yՠxЧx}Zyyy~xzyƤyyxÓyϛyyyїyyy֒yyyyqyqy~yyհyyzzzuw{uw{zznn{mn{zvzzzy{z~zzzzzz{{{w{|or|pt|ux|qt|uw|rs|{{{{{zzzzzy|zzzzyyyxxywzjzӈzxzxzkzyڗyypzȂz܊z~zzώz݄z^|U|݈zyy̡yz|zuzxgzyW|ʃyqVyx~qyxzzyyy|yxȺxyyʯy~lzizʧy۬y׿y{kzѻyyyyyyyyyyyyyyy۾yyygzszz{zkzyzXzxczyyyyyvwzz{z||z}~zyzzzz{{{zzyyyyyyyzzzzzzz|||||||||||||||||ת||y{|{|}_~i~r~N~|~b~|~G~ÿ|d}R}}}}d}`~żq}W}}ƻi}I~v}m~`~;e~H~?~A~C~=~~9~g}P~p}Ǻb}{:~F~L~Q~}^~Y~^~z}x}M~v~GZ~_~}M~Ǽo}P}~|x4~@~N~Y~j~`~f~X~x~[~J~n}x;~E~K~_}e}J~ɾ_~V~c~c~_~d~y}|U}?~?~l}m}k}q}Ÿj}E~B~G~G~I~P~ºv}َ}u}ֹ|]}U}T}x}N~_}e}T}{=~J~c~N~L~Z~Y~_~O~u}\}e}}o~q~P}W~_~=L~ԁ}k~P~}\~_~þ}}||||}||I~g}t}|=~B~j}^}O~b}||x|́|;]}g}s}J~}3P~܋||p}̼P~M~Z};~8~ùX}R}>}H}G}K}O}n|O}T}Y}S}X}e|ߺ{{˦{s|t}q}c}||`}n}l}t}N~F~E~C~n}w}}S~}p}|||y|p|y|Q}q}K~?~˾Y~M~`}ʿr}F~D~F~}u}N~Y~V~Օ}||||||||uw}mn}||||||||||||||||||||||||||||||{Ď|]}t}a~}}ݑ}w}y}||l}x}r}|||||~|v|||||||||||||||||||||||||||{||u|{||||}}}Z~f~~Fz~MPOs~s~k~»l~^~\~x~}||ߗ}ݩ|h}d~o~s~~O~|η|W~l~[~Kc~f~{~p~U}~a~r~a~N~^~~O~~v~~MMr~]~`~n~e~a~Z~}ˉ}}v}k}j}}t}x}p}v}||ʥ|||Ɏ|^|{e|k|m||p|j|^|{m||t|k|”|u|t|i|{ݧ{v|w|r|{Ƽ{{{{{{{{{{{{{{{vw|tt|{{{{{{}||||||||||||||||||||||w}y}|׻|}a~M~ׁ}R~X~ʂ}j}|{}|u|||z||v|ٿ{ȿ{{|||||W}a}Z}ٞ|ȝ|||]}V}|p|p|՗|Ƌ|^}_}||ۏ}}J~m}M~`~T~S~v}n}m}|Ɋ}}~I~|F~}J~x}a}w}\~S~K~ρ}i}Q~J~w~]~ߒ}k}}ٶ|k}t}‡}Ќ}؏}}}}Q~}m}Ɉ}m}þ|}j}|g}ߎ}h~p~_~ߋ}M~w}x}҆}z}[}~}S~X~a~}މ}u}ۇ}}}k~}E~]}j}|R~׏}P~}L~|}W~ľb~N~i}H~G~T~}W~¼x}b}p}W~o~M~}^}|o}X}m|O}z{ō{»{ʛ{}M|H|U{b{P{Lzqy^{w{9|܁y}Vzzlz|z[{N|g{L{I{]zy{zzzzzz{{{{{{{{{{{{׽{{{{{{{{{||vy|or||||{{{{{{{||||||}|||{{|||||||||_~u~a~A`~JG]~M~=S}7B687?G~{0}8|f{y{q{o{j{f{Ƽq{zW{^{}D{οgzzT{W{szfzܜydyѤxrxjyyyy}azizkz{zzpzzz{{{zzjk{zzzrzzyyzyymz~{S|zם{{s{zyyyǼy~{Әzzzjk{{{|{zzzyyyyyyyyyyyyzjzyyyyݳy׵yyyxy}ayyvxzyyyy|~yyʦyyyyyljyדyyvy}wyuy}cyvy̎yy˜yzzzzst{zzzzzyyyyzzz{{{{{|vy|{|||}|||||||qt|{zyzzzzzsuzuuzoqzxxx}ZzzizgzzYzdzy~y|ykz؃zvzقzX{tzzzT|{s{y{yyyydzyj{xʛxz֗ygzN{x|nyy֭yugyĭxxyyyΚyw^znzczqzyʸyyyͣyyyyȡyyyyyyŤyyyyْy˦yyyҰy˪yyБzozyyıyyzy}}zzŚ{zzz~{{|{|}{zzzzyyyyyyyzzyzz{||||||||||||||||||ߡ|E}{{||g~q~|a}[~~m~}|}_~U~w}s}s}E~w}M~K~=~C~o~G~M~R~}J~o~O~\}t}a}~?~E~ȷQ~D~D~?~v}@~ɸK~B~C~k}֊}_~i~^~~Hڊ}[}|k}G~f}p}K~{E~R~`~_~a~O~ɾ`~Ž|}g}}{}~E~S~X~T~V~܉}}Ɇ}L~|}}?~ôO~C~E~Z~y>~_}w>~J~_~Z~o~Z~Ӌ}d~}S~Y~M~h}||̘|`}|Ҡ|~?~V}H~L~8J~|D~G~z}|L~Z~^~~úd~q~Bֵ|j~i~y}W~}l}a~v}a~U~|ҽ||||}|{|q}}A~q}źg}R}?~H~߹{{{|r|Ǻc}E~w9~|G}j|\}A~]~P~q}Y}ʶG~\}@~B~]}h}k}w:~|w|f}K}h|ý|W}@~k}Ո|I}|{p|{|i}G~L~΀}ٻ|d}_}~}ǘ|I~}O~F~f}`}}e}|||||{n||N}ĵL~~;~x3F~h}p}}}e}|}L~V~r}Ȅ}ڑ}}y}||||||||||||||||||||||||||||||||||||||||ł}~^}m}{}}K~}}i~g~Y~l~ù`~w~}|||||||||||{{|~||||||||||||||||||||||ʵ|~b}}}{}h}\~s~a~d~g~w~b~{~~~~~~{~g~[~~~_~T~a~c~n~r~}ԛ}[~|~~~z~Ѐ~Mk~w~t~e~ˈ~ǃ~^~V~P~o~_~X~m~n~d~_~i~d~r~q~~|~~m~s~h~¾o~j~ˍ}y}p}s}r}r}s}z}l}h}~d}Ϊ|||j|i|l|q||l|{{ڴ{{{~c|Ѱ{ײ{ҹ{f|||t|o|{{{{{{{{{{{{{{{{}}||~|||{z{|{{|||||||||{|||||||||l}m}[}|ܷ|a}p}r}۔}x}|b}|X~G~p}||{|א|^}||{||{{z{|{|٢||Z}ݢ||v}q|ߤ|i}||{{|b}ļt}z|||oq}}}ĕ}P~|L~ӆ}y}x}l}s}^}c}žv}}^}i}t}o}Z}ځ}J~ƻ^~n}H~}|}T~t}ۊ}ſ}}݇}l}}}½~}}O~Ȇ}ғ}}Nj}}}ю}n}}f}j}Ȋ}|}t}~I~S~a~L~}{}k}܃}~}}}}E~O~y}փ}Q~U~R~i}b}|}j}||n}}{>u~F~l}s}}D~]}~E~L~v}]}s}_}_~W~}s}Ⱦb~h~\~v}|||u|_|l{ؒ{x{{{}{s{ţzg{zezyd|=|S|ՙy„zsD|a{s{i{l{zzɯzzzzzz{{{z{{ϯ{{{{{{¹{¹{{{;{{{{{{{{{|{{{{{{{{{xx|~|||||~|{||||||qs||||||||||||}T~ÿk~P~K~T~=ZDBDIy*A~W}I|>|G|?|~?|v{m{~D|{k{a{wI{{zyzQ{Q{zzP{|H{TzχyޟxΈyߜy֦yzzy`z~zzyzzu{r{zzzzdg{zzzzzz}zyxЫxnzu{~M{k{L|i{mzyyxszP|zzzzop{z{zyywazyzyuzyɾyyy޽yyy{zmzyuyœyyҴyyyyxݘxyyyȾyyyyyyyyywymyoyy~uyxxxoytyyNJyyyzzrs{zzzzzzsv{{{{z}{{{{{{~|{{pt||||||||qt|jm|{su{zzzzyzxyzzyyyz|zkzfzuQz|VzyyߙyŋyvSzwzxzsztz{zz{o{{{yyyyÚyiz̉zm{ãxkyS{yzy{yxzy~wyydzhzyyezǗyy؞yyazjzնyyУyyyyyyyºyyyyyyؿyzgzyyΡyyyyyѼyϐzizyyy|szyyzzT|zzzzzzyys{fzyyyyyxyzyzxy{||||||||||||||||||t}ƿl~V~L~r}||}||||}̂~b~ǽj}|_~|}U~G~>~|u}F~F~R~Ŀe~Q~L~S~Cu~k~i}µW~=h~?~};~C~O~D~@~K~C~@~A~B~N~v7~m~^}S}n}>P~G~B~l}D~v}}R~K~ƽw}_}Z}l}Ǻh}m}zC~{]}|^}X}?~I~v}I~P~W~}zG~[~߉}b}|F~v}y}i}C~[~Y~}F~V~}k~S~}Q~}ů|||=~?~k}^}{|{ɣ|F~f~|?~Z~ȺU~_~J~O~}t}~W}C~I~@~m~x}X~f~U~}}}E~}t}g~Ľe~}u}|||||||g}h}:~S~]~ۃ|K~b}{|||{{Њ|B~y}:~=~Ղ|V}v}V}||̽c}K~U~B~=~|<~`}@~ƻ`}X}|N}w}J~~I}n}<~|_}l}ޢ||Ñ||||]}E~}@~S~úv}ݫ|ϕ||U}Z}c}Z}o|z:~j}k}^}|||||{K}<~@~{;~{7n~}||g}|ùj}~D~K~}U~ˎ}a~}u}p}W~g~|||||||||||||||||||||j}}}||||â||d}t}o}֏}ύ}k}ҿ|…}}}|r}}X~Ł}̈}ň}w}L~|}`~f}|ø|~g}|}y}o}|n}o}u}}r}|Ϻ|p}̻|||||||||||||||||ܹ|ݰ|Ȥ|||||î|x}}}|ƫ|||ʩ|ܸ|~g}|z}}g~~Vh~V~P~Fe~@v~H~k~j~s~s~؀~~~^~|}d~~}~}~~}~`~~}΄}f}m}P~U~Y~}w}}ȏ}ю}`~d~\~\~T~}p~~~~u~x~}~d~׍}ƃ}}w}a}e}k}ɟ|Ų|||}|{x|{{{{{{{{{{{{y|p||||{~~|{{|}~|{{~||~||~|||||||wx|~||{||~~||~||||||||||}}|~|||||||ͳ|ŧ|أ||׸|~a}ߴ|֫||ܢ|_}s}ӄ}ؕ||z|{{{{|{{{{{~|~|||||||–|[}Y}ӟ||b}ȯ||m}Q~\}խ|e}|{||op}||su}|z}ދ}i}p}y}}zR}|q}‚}J~I~yC~v}N~O~O~F~\}ۤ|ޤ|c}j}u}܉}|Z}Ժ|dž}}}ˍ}Ŀ}~}z}~}}Ԑ}ӓ}ғ}ȅ}ي}ƃ}ڇ}v}[}ކ}r}O~b~}I~}}E~f}}a}Y}W~R~n}q}S~l}l}j}r}O~i}ǻm}}}}H~N~L~w}q}I~żm}z@~_}D~|t}p}q}¸k}e}s}ȼk}v|f}}||c}»l}q}Y}{{{{{{|{{{zzzzb{ԞzzzV|F}|μt|]|{ٞ{գ{{{{{{{{{{{̧{{w|͉|Y}t}{}|U}|S}{{{{{{{{{{{{|~|{rt|st|{|~||{{||||||||||||||||||||||j}|W~W~Ǫ|}[}||||C~T~O~ȻFDɺDI}_|ɾb|P|Q|Y|D|M|M|B|s{s{f{ހ{ɼg{S{O{V{ùj{}J{a{d{}J{zٙzΑzzdzyߖytzlzdz̄zzy͗zc{f{v{zzz{{zwyzy|~zzyyyyxٹx{yzI}S|c|׈zzִyлyx|8|{T|zyyzzzzzoq{vw{yyyӷyݵyձyɴyЬyŮyyy{~zyypy٩y›yyyyxğxxyknzvyzyyyyyyyyyyx߬xxyyyxxѵxxnyڏyĥyzzzpq{z}{{{{x||fi|{{{{{{wy|{{{{{{{{jk|op|}|}|pr|{{zzzzϽzسzzözzzzzmzzzazfzy}^zyzyȐyuUzlzyzezgz}Yz_{f{e{I|e{{}bzyxyѦyyj{O{xtzz]{}M{yͩygzxylzzz{izܺyݣyv[zջyuYzezdzyy٤yyxyyyyyyyy|izӯyyɩyyyyyyyyԶyʄziz_zx[zšyyyyyz{z{z{|zzz~}zyyxwz{yyyyy||yxyzzz|||||||||||{|||xz|}|||}ǻa~]~Z~X~r}||||||߃~q~~~y~Z~b}G~P~K~j}n}d}A~ºq}O~m~g~÷W~K~C~Q~J~;]~Y~\~]~\~H~@~H~h};~D~A~R~Q~~<~\~_~}J~G~h}?~B~W~}c~~~C׃}ɿt}g}|U}U}c}{|k}o}=~y<~>~h}C~m}G~O~|H~}u}u}j}W~[~k~p~W~q~_~g~x}ȥ||¸m}w}z}`}_}Z}r}F~m}W}|{{|}s~i~b~q~z4øS~x?~K~l~[~}q}Y~M~^~ȿo}[}P~w}h}|}}U~r}؈}r}~O~|}|d}Q}}|{ɇ|B~[}ĵO~L~0P~ƹU~ǽo}Җ|S}||||i|T}8O~x}_~v}r}c}d}ڦ|R}i}X}m}D~A~|H}||z}>~g}Z}u}Є}R~׍}G~O~e}|\}x}}\}W}zL}j}E~̿l}|||r|}K}Y}F~j}|||t||b}|||||l|P}E~B~b~G~p}f}Ƽh}\}e}h}Ȼe}W}K~H~g}х}Q~}|u}p~}p}Ե|||||||||||||||||||||||||{~||k}|}ܑ}ǐ}n}һ||||m}|h}ކ}g}t}|U}J~O~~j~»g~Ľi~]~V~}}}}k}o}q}~}Ľ}~M~}}k}·|||||Ӽ||}Х||||||}|_}~O~\~[~ϊ}}y}ƀ}t}x}|m}|p}k}x}s}w}s}~}}}}v~{~x~x~~K]~D~\~]~_~f~p~r~d~q~¼j~~q~i~~p~}}X~]~j~r~m~q~k~}ژ}h~g~Z~Y~}i~j~\~~S~¾u~q~o~p~q~~LMMI{~g~e~Z~S~}ƃ}n}ֺ|||||||{{{{{{{{{{{{{||||||j}`}|n}}}}Z~ґ}|||||||{{}~|{||||||||||||||||{{||||||||||W}’|b}|x|z|{{{|Ǫ||||p||{{{{{~|У|ý|||||ޭ||e}|h}}z}}Z}e}m}`}|||||||}}}`}˜|c}f}l}b}_}z}K~q}F~W~ދ}{S}L~Q~}ɀ}|~||f}q}u}¡|k}}}}Ռ}ؑ}Ë}|}ݓ}Ӈ}}\~a~ύ}P~lj}x}}[}}f}}t}}X~Ct}c}l}~E~}|üp}v}I~v}||ڨ|i}o}J~e}N~}r}y}|v}K~V~I~n}i}[}w}}zJ~}}k}Y}^}C~S}݂}x}k}|||]}p}y}b}j}J~t}{|{z{{{{ϭ{{{zzz~{{{r|U}O}b|}|{|Ɇ||{s|v|U}^}T}u|u|t|P}ڃ|ֆ|P}|9>}{}|{{ϒ|T}x|ƾ{{{{r||~|֫{׵{y|{{|}}|~||||~r||||~|||{||||||w{|{||||\}H~U~Y~n~|}f~M~r}|||||l}o~T~e~O~׍}ɋ|O|Q|L|C|M|Z|y{{~L|l{t{{Ǻg{r{żm{[{Q{zzzG{i{c{ݡzz~Y{Q|ýz{̊z؊z΀ziz|z<|i{|{W|k|m|z{z_|M|yxyyyxyyyz}zyzyyJ}:~J}_{Z{zӏygzy2}Q|q{k{zyyyyyzzyy˻yyyyŰy}yyyzyyyyyƿyyxtxwyyrtzy~yyyyyyy~yyyxkyxϢxxyyxyzuy|sy|yyߵyyy{{zpr{|~{{{{{{{{{{{{z{{y~{zzzzzzzxyzzzzϙztzǎzzzwU{ىzȓz}zuz՝zxzwzazizz֐yҘyyezy|Wznzkz_zy֞yhzm{\{z{N|{T{{zyzizw_zyT|yxI{z}?}ezezyĄzzwdzqzhz|z}\ztzʢyКyyyqz}zR{×z¸yyyڻyyyȟyūyyyyy{uzyhz`zyyyyyyyzxzׁzȜyyyxyyy|O|yyzoozzzuuz}zI|vyxyyyxyzz{|||||||||||||||||y{||{|||||r}{S}|{m|{}[}i~CL~}_}|Ο|Қ|A~`}F~F~[}ء|o}C~|<~H~m}j}_~B~a~M>:_~R~\~ɽV~L~D~};~J~E~˷J~@~_~c~I~||Z}x>~G~m~[~_~w~w~q~p~e~i~[~}T~ā}}||~|_}r}<~o}L~?~g}{C~Q~i}I~s}C~{A~y=~}ߎ}}r}ۊ}S~b}~U}ȿo}o}N~}¿|}݌}Ѱ|\}z;~^}Ƚg}E~M~ƾn}~}Y}T}{}{~S~B~m~X~R~c~j~q~j~L~K~l}zA~u}}L~Z~|||x}w}`}K~ź]~I~Ѐ}V}|~|Ƚ{Ր|?~x=~H~d}A~g}r}u}`}p}_~d}|||g|L~A~p}g}K~I~_~Ԅ}q}}p}U}V}>~f}u}c}U}_}|>~t}|<~u}B~x}u}t}`}o}j}M~}}v}G~l}S}y=~n}||||ɏ||t}K~h}b}{V}|X}ƹl}||s||t|t||<~D~}}||z|a}R}{L}k}F~[}|ȺU~Q}t}}X~F~}׈}}z}m}|||||||||||}|n}z}Ӗ}k}޸||h}n}Ɲ|_}b}|p}¾}|q}|i}g}z}Ľ~}f}|ԁ}ƾz}|}Q~}}Á}p}i}S~i~p~F~~k~ľo~k~d~Q~}}}}~}||м||ٿ|ݐ}U~Ԙ}e~k~c~Ɉ}h}}l}lj}Ʉ}~\}c}Ƣ|s}s~u~~Y~}}Z~S~q}ͨ|r}ƽ|}|J~Y~ۀ}|v}h}]}y}y}ɿ}ь}f~h~p~q~Y~\~X~u~x~o~n~{Fx~x~t~}}Q~Њ}]~[~Z~ſ}s}m}|||x}ԕ}X~}ɍ}\~h~m~¾q~s~f~[~T~Z~^~k~l~d~}}^~o~~~~~~u~q~~}~[~S~p}||||||||||||||}|{{|||||e}|d}s}_~}}ҍ}j}Ɓ}m}|{|||||||{|}~|||||||||||ͺ|||||z||}y}|ݲ||||Ǵ|||k}|v|{|{{{{{|]}ſ|||||_}t}||{|||d}w}^}Z}|i}c}h}b}d}||{|h}a}|ſ|||||b}|k}ʈ}}zJ~ϋ}g}ʾn}܀}d}d}j}l}g}e}y}q}j}~}}}n}|||||{}؈}y}}w}b}{}Ɍ}z}k}H~T~}}}}{}z}x}ֆ}a}t}m}{}q}W~T~p}||z}}}|Z}a}F~s|ݡ|]}b}[}Q}m}I~d}q}|`}x@~s}ǻn}v}O~_}s}s}˽g}ʼY~f}¼_~D~G~g}~S}`}}ɿ_~\~J~S}̿g}{}o}^}g}}|q|z}`}|{{{{{{{{{{{{{v|ɀ|N}ǻz|{Ŏ|Ə|Z}]}|{|_}ɿr}^}}}Z}|Y}`}l|͐|r}A~Q~M~Q~|{|Ʉ|Ǻ_}»p}|{t|V}~|w|n}A~`}ҝ|h}||{|d|||||||||wx|||||d}Ƹ||rs|||||||U~|?}|}Ļ]~~}y}ަ|R~߱|P~]~FLƒ}|||||v|{[|r|v|{||v|{Z{t{U}Ⱦr|q{h{}{t|I}Y||O~q~s}v~k~Ȅ}Ȕzt{]{{C{o1}{9}ŨK}b}y5~W}q7}U|T|Z{yƣxyDzyyưxyyzzxz{yًzL|M~g|f|?}@|ūR|@|F}G|\|S|{||zzwzN}yޭz}}zzyyyzyyy}{yyyyyyxyzqy~uyxwwyyxxxxyyyyyyyxuyxvyxxǣxݷxyyyyxxxxyʱyzzjk{y{{}{zzzz{{{z{{tu{z{{y{zykyyqyxlyiyy[zߩzȆzlzmzlzwzzz|zуzɂzzhzhzђzuzYzazztzċyyyZzxydzzdzyZzczŠyyzg{ޓzM|I|}{K{}QzbzozwzS|w[zy|zf{T|kzizgzzzuzczzmzyyhzizyazzzwfzֱywzY{~rzyrztzyyyyyyyyygzizI{yyyyL{ѧyyzW{sz_zy~yyyyxt{[{yN{Dzyym{uuzh{G|gyxyyyx`{}a{{|y|}ps}||||||||{}|{}||}~||||||uw|||||s}L~j}i}}T~p~{~r~׃}|b}yA~x}E~_}<~<~K~R~I~e}?~S~`}a}z1`~F~h~=I~N~T~}ހ}g~c~R~i~]~z3BJ~w}ܦ|||̘|R}B~z}}}}x~k~~]~e~}`~t~U~}}y}|ȝ|w:~k}V}n}:˽V~N~w}S~v}}ƾr}D~F~ǿu}||~|e}I~{C~H~F~H~L~z}{t|o}ɺa}U}H~R~üj}n}=i~^}P~^~h~]~U~R~Y~r~h~c~|F~ýe~~?o~„}M~R~}Ā}|ռ|||_}[}B~W~T~X~F~|E~v}|||n}J~8~@}|e}h}|_}x6N~<~r}a}R~A~|c|b}m}I~A~Z~[~||}}j~~]}{}H~ý|֐|Ø|||j}l}B~@~u}_}c}g}r}N}\}|M}r|o|ȏ|{R}j}R~u}G~x}{}ی}Ն}h}|m}{}e}l}||p}v}]}|u}||a}|||Š|~|m||V}X}A~L~N~ӝ||ї|M~D}}Ā}߇}U~~M~ف}n}f}ָ|ھ|Ϻ||||||b}w}}ގ}|~`}م}Q~|H~߉}}u}H~x}{}k}[~I~]~U~ُ}k}ă}ǿx}a}Z}}c}N~^~X~|}ƞ|n}|u}\~g~|~{~v~k~z~~T~}t}v}}}w}|||||||~}}t}̎}ݒ}f~o~þr~X~Y~}||}r~}}X~X~|ף|Y}݉}{I~x}}h}y}ԃ}Y~S}|~|΅|[}T~|T}}}Y~}}b~n~O~Ӈ}t}ߎ}}T~Ή}Y~^~m~a~S~Nj}‡}}}y}}r}Ǐ}V~}ϒ}}\~f~e~}}}}y}x}T~~ROx~|~Y~X~Z~Y~[~i~`~o~^~Y~}k}i}|}s}}X~O~}`}}}c}||٤|||||`}`}v}e}c}Y}\~S~}Q~||}|{|ԭ||||||||||||||||{}b}||||||||w}|Ϊ||»}o}~e}Ҳ||||||||ģ|||{{|s}d}W}Ľ||գ|j}o}գ||a}]}G~S~}|{||w|||||{|||||z{||||Ļx}e}e}ڗ|Č||~|ٝ||d}Z}C}w|r|Y|{~|^}Ə||ف}}||¿}d}ϡ|܊}ԉ}|j}Պ}m}~}}}ºz}}}e}Y}Y}|k}|}t}͍}p}T~z}}ݩ|Ί}s}}}M~σ}|}}|}|}~}g}v}~=źV~F~d}u}n}l}c}\}Q}m}T}ɽg}w@~C~b}N~i}v}f}@~z>~G~q}e}U}g}K~e}JA}=Ĺ[~W}s}g|i|ք|[}Z}w}E~U}]}Ѥ|{{{{{||{{{{{{°{f|~||]}h}}E~m}t|ʥ|}}W~[~]}˶g}E~ún}e}g}^}û`~L~P~L~X~I~|Y}ݩ|a}\}||v}A~||{z|A~g};~~|i}j}|w}͗|j}}a}||||||||||||{|{tv|||||}~}Ѹ||ơ|c}b}|ŧ|S}i}Am~v~}}u}z}}~}}`~}|||}w}|w}|||k}d}|X~LDm~|;TRj~w}|u}{}v{z{{w7}=}B~[}@~E~n}B}N|{G|C{־yyszozyyyzzzzy_ynzܕzV|}8~B}O}a}L}r2}@}S}f|ǹU}J}T|Q|a{]{=}t{zyyyyyyyyyywxzyyԯyȳy{yyyxxxxywxxxcxͷxyyyyyyyyxxxΩxŰx׺xϻx}y{yxxoy}nyxuyyzzzqr{zzyz{zz{{{{cf|{z{zs{{}{zzzzzzzz˦zzuzXz]zczYz~SzfzhzЎzɋzzlzрz|zzkzuzz|zݭxyy{zzzzzŖzŸyz`{ьzW|Y|y{b{qzWzazZ{v|wzP{Z{j{֐zy]zyޚyyyyyiz~azizozezjzszzvczʟyyK{y[zۊz֋z}zyyyyyxyy٪yM{S{^{zyyˆzryyzR{w{ˉz~{xzpzy~~y}zxyՈ{yQ|zzỷ{{zzJ|{yyyy}}yyw{||||||||}~|||xz|{{{y{||{||||z{||||w}|A~@~X~x8}~s~v~w~܄~c~q}ˆ}Q~r}±T}J~U}u:~B~t};~f}}d}i}R}b}F~ƼR~[~d~Js~O~?_~>Bn~Bs~û\~j~HK~|}|{|y}v}}E~E~n}T~~}}d~}]~j~}і|ƾr}e}{||E~}>~A~[~òM~X~J~D~{}}C~E~O~l}Z}Ѓ}ۇ}a}Ą|Z}\}\}ĸg}h}x?~m}e}·h}d}m}@~C~}~g~V~e~u~U~û_~Z~R~R~J~G~j~f~O~}X~^~s~\~u}}}n}۾|ح|`}@~r}o}E~Z~_~d~G~P~e}}}U}p}}W~}}}r}|a~u}n}_~O~v}F~l}P~|=~F~|d}|k}]~b~I~u}üw}|}~}R~Q~h}|||n}S}m}u}t}|[}K~>~q}\}|`}ަ|~|{{w|~|Z}Y}]}M~E~~}F~E~N~E~E~~}}d}@~|}}}}Ȳ|ݺ|ڭ|a}e}˥|||Z}|||׏|u}C~O~n}yF~}W~b~y}Z~u}Ό}Ɗ}}Q~ƃ}ڎ}}݌}}~N~u}||||b}r}P~܇}o}}~}a}m}V}|Ѫ|O~}t}x}Q~}s}}}ڱ|n}||}c}{}҃}N~}}}҇}͊}‡}]~T~ņ}]~Y~T~}p~V~Έ}ّ}}}u}}||||||ֿ|||ܷ||˪|Ѹ||}b~]~~~߉~f~}}Š}t}^~}||||~U}Ә|||||c}}j}|}{}u|m}s}i}m}{}P~g~_~ؖ}r}||T~Y~O~\~\~}}r~}r}}u}g}w}`~s~p~V~o~}Q~}ύ}]~U~}}’}}Ŀv~JP}~y~̓~}~{~z~p~b~f~k~\~]~u~g~܎}}]~}ɉ}|Ē}\~d~}}ڊ}}o}Y}ߤ||ў|բ||`}s}z}I~a~žh~W~f~z~żc~K~m}̕|ӛ||i}}k}c}߹|||||||||||||Ȩ|ں|||թ|޴|ϥ|æ||ީ|`}U}|R}`}v}X~̊}t}||Ÿ||||_}a}|]}|Z}Ɲ||֡|`}̔||ޘ|û|z|џ|t}׆}Ӏ}إ|]}\}\}a}||{{|{{|||||||}|k}}S~y}|ɘ||s|r|B}L}R}X}X}~|h|c|m|{İ{{||||Y}|r}{}q}||||u}t}j}Ԯ|w}ф}Y~W}̘|]}ȫ|||¿||o}}̮|h}^}n}d}l}q}N~Z~v}|٨|ԋ}|}t}?Z~g}}:~m}f}e}g}||_}~A~x;~}O~T}o}=~C~~?~F~K~C~t}}d}B~D~@~X~Z~=F~w}K~~}\}]}f}O~{}G~X}~Q}{{{{{{{{{{{{{{{{{{|~|e}|||g}N}p}Y~Ͽc~ŶS~u}U}]}|ſp}i}X}\}ۀ}{D~G~|F~O}|||ûh}s||ÿ|c}@~H~K~H~;~Gv}û|s||W}T}|R}a}|||||||||||||{||||||||˿|p}[~|||||||||||||||||Z~W~`~>^~P~}ؽ|ͭ||w~MY~DYFD]~]}|||{b{Ǽ{ր|ĵX~­P~O~s}l}q|r|j|H|Ȣzzzy{zyyyzyqy^{{J}J}S|C}N}t;}n|n|m|^}Ⱦd}=}L}B}ͼ`|Z{F|k{ydzyƱyyyyyyyyyyyyyyyxxɷwwxyxɛxxyyy~yxxxxxx{{yxxxxxyyyyyyyyyzzzzzzzoq{zz{{}{zzzzzzzzyzzzzyy}czjznz_zbzQz]zyڑyΊyqzϑzzׂzlzczlzsz̓znz^zpzxTz]z{zzمzۅz{zrzňzʮyuzԣzlzS|n|n{x{bzyyh{M}Րzq{~{L|~`zyrQzzZzȗyϜyyybzwzݞzzmz~z͢zؚz͂zڕz|zz{zzz}^{{z|czyy{hz}yyīyyؚyozL{zyÛywzoyW{zzzo{m{]|Tz͋zyyzX{y~{^{G|zi{Y|y_|M|yyykzyy|˻|||||}||||||x{|{{{|{||{}|||xz|||||b}f~˽V~}4W~R~|}}R~·}v}k}ʋ|~D~M}D~F~h}yI}R}V}k}A~?~J~B~k}e}[}{}@~ʿOP~i}y~Cf}Žg~~>u~?j~W~`~e~f~n~}}|{|b}ǽ[~E~j}_}E~O~}E~z}i}ļr}}ļo}p}O~k}|T}|}D~B~u}<~M~O~Z}¹i}E~G~}}?~g}|}T~P~]~m}y|X|V|m|߇|\}Մ|r|{|ļl}g}w}}Nü`N~y}J~`~I~x}_~G~R~[~݄}n}΍}O~b~M~i~j~Ջ}ɐ}Ѿ|v}}}M~K~ź[~}}L~x2U~{?~}l}_}O~}a}||X~|{}S~Y}m}N~}D~J~e~}A~A~s}P~^}Ε|d}}}J~I~T~}׃}L~r}|{}|}W~x}ݎ}i}||{^}Z}h|¾||_}X}j}G~x}j}҄}~^}||||{{v|u}Q~z}h}H~C~F~Źb}{;~B~̿n}n}Z~}K~}g}ƍ|K}i}o}h}|P}||P}ڏ|Y}b}E~y~~~W~}֋}Ƈ}q}v}n}v}c}j}t}v}ڮ||؛|j}}}~Q~|||E~úr}ĺj}͘|K~y}`}ڦ|r|||צ||ɤ|ά|l}Ѯ|Ƭ|i}|||{ڝ|d}s}|K~g}R~x}_~m}v}ݕ}q~`~a~g~h~]~܏}U~n~g~n~|~d~o~ۀ~_~Ζ}y}||||}|v|z|||||||Ʊ|x}ɔ}}f~e~S~;||||||||||||||||ҍ}Ľm~v}`}z}W~|J~ޒ}߽||}\}|ּ|ϯ|w}}}V~}b~T~ƾm~\~d~žo~d~V~T~\~R~}Ƀ}}o}}}x}}}|}\~g~ԁ~~J~~l~؃~օ~l~S~_~{~~|~b~T~u~X~}Q~j~}Ŀ}}x}P~}R~X~U~V~W~K~Q~}L~P~Z~ļg~L~Q~Q~L~`~M~\~Z~U~V~[~N~z}o}t}^}V}y|m}e}b}||||||||||ì||j}|p}Ս}׋}ׅ}c}ſ||ע|}X~\~}Ѓ}`~p}z}|||||¿|Dz|ʣ||ƛ||Z}˞|||͔|ƻn}b}Y}ߜ|||x|{S}j}{}zS}t}|T}~|p||||^}ں|||k}ݵ||b}i}|z}Ŀv}]}|||||^}^}ļa}O}؃|{|X}||||||||||c}|ļk}ޟ||||||n}j}g}ߦ|l}}w}y}o}T~}||a}m}d}l}g}t}|}z}~J~S~}}ýx}x}޲|Դ|ɀ}o}m}F~N~?~d}~G}l}D~@~U}g}̆|Z}a}F~G~M~N}W}C~E~y}C~}}^}I~y}w}]}X}|||u}J~i}x}Y~b~}x}|e}[}А|{{{{{{{{|{{{{{{{{{{{{{{{{ɳ{Ø{{|P}H~M~}؏|V}ʎ||||||l}}B~^}|J~F~{|{|||~|Z~t}N~T~i}|k}UFi~d}v|ō|_}ј|ؙ|`}C~A~|֣||^}N~k}|||||||{||||||||||||||||||||||||||Y}n~KQM||{}Kbn~h~MQ@C~@p}Ÿ|||{zP|vC}H~IJV~z9~b}пd}O}H}E}ǵg|o{{q{{{zszyzO|zP{U|b}ɹ_}i}T|k{U|ё{Ԙ{{e|X}a}d}h};[}z>}I|J|{znyZyxyyzklzyyywzyyyyyy~yxwѭwxyxxyyyyxxkxʍwkww|[xwxxxxxyyyyzyyxyy|zzzzzzrszzzzzzzzǓzzzzzzzzy}yxxӽxѫxyRzRzaz{zюzXzizgzÊz`zԄzՅz}zzmzczmz}zozczlzlzazfzdzozeztzkzvz`z~z~dzߣzxzP|@}}{M|ދzW{izq|I}zY|d{{dzy|\z}dzszyyuzpzzztzzlzwzzՒzzܔzszJ{ڇzzυzߓzՕz˅z߱yҨynzyyyyrzizyjz’yqzТyO{zxnzM|{{i{yyyy|{y|{~{|E|z{c{l{V|T|yyyz˓{||̬|{{}||||||oq|{}|{{{{|vv|{||||||||||Q~j}r}S~G~e}]}H~J~n}d}J}ѼK~;~@~?~Y~H~T}w7~h}ƸN~C~E~Z}T}}K}V~K~zA~H~a~H~?Hp~žg~g~m~[~g~s}ߋ}G~S~`~_~a~e~|||_}`}_}]}ƾp}H~H~t}G~}}}~}L~G~|}p}i}v}y}D~@~]~ŸW~E~Ƽd}B~G~M~B~Ǿl}n}}?~s}Q~T~C~}D~T~}f}~Q}|{{{^}z}n}m~Cc~a}y<~o}P~ø[~N~q}S~a~^~͈}Վ}W~Z~w}M~}z}}e}E~؀}^~v~f~Z~\~s~_~X~v}p}X~Aża~}{G~s}ב}΋}|K~ԁ}{}}J~ժ|I~d~}C~|s}|a~Z~[}Ǖ||v|n|a}G~L~B~ºp}r}|H~V~w}s}||m}»q}}L~}w}Æ}M~y}t|T}]}ڤ|a}o}v}`}l}ް|||~|~|}|{r}T~zB~d}{}F~A~o}A~V}|ߕ|f}t}F~J~B~r}?~D~Z}}|Y}D~c}|^}|Ž|?~|}ùi}w~ۂ}}}{}ͅ}r}v}|i}q}{}|||||{||||}|z|½|W}Ľn}~D~O~H~^}v}I~T~T~}q}}[}~}zG~}||̿|||||}p}||~}}e~c~Y~\~}}Ҏ}~R~T~c~}ý}u}}}ݚ}y}ʔ}T~ýl~z~a~}Q~}|g}k}|||||||||||||ͭ|ë||}|||||||y||||{{|{|ޯ|t}»y}͢||žy}ʂ}Ί}i}||f}l}ʆ}||؜|d}l}t}v}N~R~S~f~c~}P~m}c}y}Ć}u}þ}u}}}X~f~o~_~g~g~؀~P~~~~_~U~t~z~n~a~[~`~}g~}ە}h~l~_~l~}h}ʓ|k~ӎ}g}x}҅}K~}}L~t}|c}^}ԯ|o}}}L~P~[~S~E~g}a}}}N~~}n}|߄}}s}e}l}m}q}}||||||z}Ŀ}}}}n}~`}||͇}}}n}٧|}ƿ}z}|~e}k}c}||̥||||||Ɯ|||x||g|z|z||{Y}ü}ۥ||||Y}}J}c||K}ƹi}Ĺj}͕|_}||||||||n}r}||||Ŧ||m}j~_~u}٭|_}|||||||||ѵ|i}т}t}|ȝ||{|Ű|||˩|м||{}}s}t}x}}q}y}z}|n}m}U~B~ɽX~w}Ð|—|k}\}y}l~n}Ε|l}}f}\~O~W~W~V}f}b}ؔ|{}]}^~P~K}{}E~a}@~ȷb}^}ͼc}|R}=~z<~ǹU~ɾj}À|M~X}X}P}d|Q}o}v}K~}t}F~}L}Њ|l}T}u|{{z|{{{{{{{wy|{{{{|{{|{{vw||||||{{}{{]}p}z=~µ^}Z}Ï|z|||е|ڂ}Q~r}o}j}q}ѧ|ߨ||ͤ|Ŝ|d}_~D~T}]}R~{>~}Q}y|݉|N~9?~<p~l~J~x}X}G~P}ǹk|g}F}N~bћ|u}|{|||wx|||||||||||||||||||||Ƶ|k}|||t}L~}}¼c~DUKQ~c~ؘ}m~ƾ[A{}ͩ|}w~}~¾q~Nԓ}a~{k}ν||v|f|U}v}[~{?~s}m}x>~M}n|[|~M|{zL{΁zj{|N|{J|S|^|ֽf|?~w<~A~|N|v|ȹ{|h|ɿ{ǡ{h|{|S}ο`}W}L}N|}{c{za{ޟyyxu]xͭxyyyyyyyyzyyxxxwwxxxyxrtyxxxwҐw˃wvuwاw}^xԾwxwyyxyyzzzzyyyuxzzzzzil{zzyzxy{zz{zztv{xz{zǽzzzyxxxxxִxyczezVzZz\zWz{zz~z{zpz}zrzmz^z|Wznzyzgzcz[zyyzZzލybzkzbzyYzxTzrzyo{~rzY|M}q{I|v{c{E|A~^{Q{i|z{wz|bz}mzvzizy|ozqz~zszzzkzyzfzkzzuz{z~zՏzzzgzozÆzҐztznz٤yzyztzdz}czbztzyyszʞyZz{xxlzT|b{¿l|q{yyz~|z~{z\|e{V|Ƈ{˄{p{{|<}i{c|yz{|ս|ñ|α|||||||}|||{{y{|}~|vw||m}||||||||||o|k}f}F~=~`}Z}Y}K~@~k}d}H~w9~|6~_~]~P~S}^}ǽb}4~i}N}ч|K}T}@~p}r}>~u}h~Kp~h~L~c~W~Q~ļu}c}z@~9g~e~\~{=S~ː}o}||p|e}x|d}\}w}Ԃ}U}R}y}>~H~Q~x}l}R~g~Cv}j~T}@~¸\~f}x}|ȿi}|}}}z?~o}o}P~<~C~H~l~g}x}a~s}ֈ}ۢ|a|R}^}P~Y~źT~R~X~I~\}T~p~f}q}{}|d}Ŀw}҂}}a~x}b}}|@~e}y}D~O~v~a~}e~Q~l}ۊ}̍}L~K~y:s~T~i~S~U~^~ߎ}|Ձ}Y~J~P~i}S~Ļ^~ײ||þ|̿[~H~¹n}_}^}Y}ƿ|k}J~>~k}A~M}p|j}N~}m}Z}[}}|s}B~c}t}a}d}}y}}|}Q~P~~G~]}f}|}||||X}W}|M~ɽ[~{B~[~v}ƷV~i}r}>~J}{y|||l||H~<~Y}?~D~n}|||w|v|R}{}D~]}W}`}h~L~y}}w}Ɛ}}}r}}}}~}||||||{|||||||{|g}X}t}}a}}X~Q~X~Z~ـ}٠|m}t}w}}}}s}|c}}}¥|y}|||}}[~ük~Q~}f~Z~}Q~i~}~]~ך}}}}w}t}|ٛ}q}u}÷||i}m}_}|||||||||}w}|||}|{{|{|{{{{{xx|{{{{xy|||m}}n}d}c}۶||j}}x}}֒}Y~}ȼp}w}n}|||h}Ʌ}_~d~U~}}}u}{}ʌ}_~m~}ǎ}}̓}}V~W~w~l~ؗ}ܟ}}}}}_~e~r~ɉ}Q~}}|}n}ڌ}V~a~V~ҕ}d}j}}||f}}ˇ}|}|f}ή||ф}}N~̈́}Є}x}p}s}}}l}v}f}~}v}}Ɓ}|||n}v}u}L~؁}h}}K~v}}q}ټ||Ӿ|||||ˤ|ђ}}|||d}w}m}Ą}{~a}|Z}||٥|g}|̠||ݲ||կ|||{|{|ݘ|ː||ۛ|ĉ|~S}i}\}ƅ||||٦|͗|j}e}|{r|~t||r||||||||i}j}p}ܢ|Ы|Ӯ|h}a}r}߇}ż[~r~Ձ}ۭ||^}||||||l}|}}}g}x}g}i}|Ʈ|ĵ|||s}|p}}|L~}}|i}r}ݹ||i}u}G~zB~e}|}M~_}d}l}|||||U}}c}Ļo}¸X~ƼW~@~T}i}Z}W}K}|̻`}P}˼R~L~=~f~T}R}r}z}P}c}|J}q}{;~@~D~c}M~W}{|_}?~ŶV~?~p}W}t}|E~m}m|j}Ň|x||ϗ|{{{{z{|{{{{{|rq|y{|{||||}||||||}~|{{{͵{||̻r|~S|||||t}}׊}}Z~Ӑ}||h}|m}}F~L~`~A~b}Q~_}œ|֗|k}å|{E~p}z}CFn~k~¸U@p~f}}m|{n}Ż_~=@_~L~F~9d~n~ۃ}}D~n}`}||}}||||||||||||||||}m}|||Ό}Z~||||q}܇}{~}Q~X~||||k~}FЀ~~}||||ϗ|}ˆ|b}J~f}v}o}f}B}e|b|}{`|{ƒ{xI|b|X|I}G}b}ܽV}c}<~C~y<~s|x|ɵj|Ҍ{{a|T|P|Њ||c|e|Ӛ{t{۴zæzy}hxxcxxz|z{{zyyyrrytvymnyyy|xyxoxxu^xxxxxxxxxxwϴwwnwwz}xxxxyyyrtyyyyhizwxzuyzyy|zjlzyyzrt{gi{zyz{~{z{{zrt{zzyy­yöxx˾xxuxxxy}z\zwHzyvIzzLz[zaz\zbzcz`zkzozkzpzzozrz}^zyYzwXzyyyʖy`zvz\zyyփywbzǢyC|zv|i}{^|{_{J}s}J{m|z^{zSzzzbz`z]z{YzyZzz}W{}zx`zwWzztzyqznziztzfzuzJ{`z}azjz{zzzyy\z`zvzgzzfzzʨyƐzzF|R}ez~dylyhz{W|M}M|z{zy}zn{z]}И{}|}h|~O|D~N}yz||{||^}b}|˜|||||||uw|{|{wy|{||t}||||||||||ʩ|w}އ}{}x8~^~e~]~|s~|<~˻{ǔ|o}k}N~R~P~Q~g}||Y}x||O}Z}̄|Ʒ]}A~I~U~I~[~T~g~z}}V~}^}`}]~Z~>@}u}K~c~Z~X~~}q}O~_}y|R}k}>~a~>~~<~E~{4@~D~d~WVn}P}g}k~{~U~M~zG~}f}ˌ||s}D~~;~k}z;~x}x?~w}o}U~z}ɒ||i}=~z>~i}o}G~y4f~T~݅}S~P~}||u|k}ߕ|U}[}م}~L~W~}}j~}D~|zH}|>~]~A~O~U~W~L~||Z}t}O~ķY~|9i~d~W~X~`~o}Ƀ}N~}O~P~ɿ]~ǽ`~i~x}|{ږ|z}E~r}{|͋||z=~M~h}́|N}|m}_}ö_}P}ć|}M}|{}W}X}ƽm}c}`}X}}i}\}g}}S~K~ȿt}k}¿|u}¸||{y|z|d}P~;?R~~>~ƺg}j}n}H~r}X}{|j|žx||{{x|Y}X}|<~E~`}||}||^}j}ݶ|e}e}{}q~U~}}[~}|}x}|Ŀ}ڔ}|}||||||||||||{{|e}Ş|ў|g}}E~x}J~G~}v}}Y~P~W~b~c~}ɣ|||g}Ƈ}È}~}p}k}ڿ|U~k~e~}b~}w~߄~n~t~d~ښ}s}}}}u}}}}Ɯ}q}d}}}}i}|||||}}}|}}}|||}||||||{|wx|{uv|{}|}~||~||«||||}|{{{{||l}g}s}֩|u}p}{}S}|]}^}|l}V~U~a~x~[~z}}Ŋ}}ӽ|IJ||ɖ}Ț}q~j~h~j~]~ח}}}}}}ޗ}_~Í}_~d~h~j~}Q~y~]~x}ّ}|ä||a}z}}}z}|||||||||||||||||°|ͬ|`}̧|||m}|̢|ϡ|n}d}h}x}¡|a}z}|ܰ|||}}}֣}}غ|ݸ||||||ڮ|܎}P~Z~u~ڝ|||ϟ|||||W}ɛ|ݧ||Ԧ||{{y|||r|s|||_}||S}|z|y|o|}r||Y}e}ڼ||{}|q|{||||ڦ|۪|t}a}|~h}x}z}x}d}}R}r}GT~|]}|||||||||o}~}n}o}^}~Y}||u}|}o}|ؑ}z}}o}c}|||h}||j}m}}l~{}Ք||B~D~]}}V}{{|}}փ}t}W~z6a~I~|j}w}@~f}c}y<~}:~G~[~?~ʺU~ƷX~|B~}T}ч|}F~}}}U}j}L}v9~>~l|{οe}@~V}C~T}ݾ{{{{j}y?~`~C~b}||{{wy|{{{{{|{{}|lm|{{{vv|{||||||||||{{||{{|||ɾ|m}n}}L~h~{}||}ǁ}r}X~R~g~:3\~>~?~C~O~Q~o}U~s}p}f~89~O~ZHU~[~ʃ}ԉ}r}v}L~P~k~½j~|h}}X~v}|e}Ŀt}~|{||||||t}b}|i}|||||||||||g}d}k}y}F~G~||||r}|||||||||||՝|R}ս_}J~E~|C~H~i}`}`}Ķg}}H}Q}D}z7}{B|w{o|ז{ځ|U}:~<~w<~f}´a};~=~?~ׄ|b|R|P|úd|ڑ{}{ļ{ޫ{ǿ{}{q{z{h{}{{{zyyzzzyyyyyyyxxЬxxxxxƲwxxpryxӥxxwwwwxxz}xxxxyyyyyyyoqzywsvysvz|~zzzzkm{ee{zyz{{{{}{{zyzvxzyxxxxxxx|gz}zOzqyqyzytyy`zyzOzpzyz]z~XzhzyizyΤyvezyy]z|[zʤyyzizfzy|SzdzyyR|z{t{~=~F}y|z{S|V}B~B|y|pzU|{~ZzyVzyyyyz`{hzy{\zozyșyxdzy`zozpzkzO{czyywzazw[znzmz{zszzzlz~zzezzV{M}i|ωyڨye{R|e|ľo|q|U|s{\{j{z}X|׆|m}y|W}V}J~Q~E~b}v}|ť||p}z}_}a}||i}||{|{{|||||x}yz|||||||̔}}@~>~{9~p}o}D~D~}E~X}xE}T}{|}u}Ϊ|~}}||}}N~9~P}K}|K}Ǹ\}?~B~?~E~H~=69y2=3`~M~{B~i~l~?E|8d~H}G~a~}ۉ}q~\~~}}[}h}ƿ]~};\~a}ȿW~>k}BS:~=ſq}L~L~x~Ct~U~}`~T~p}ʋ}|W}s}@~G~>~´b}}L~~}b}yK}{T}P~[~c}b}@~T~[~}|^}d}`}|p|Ӛ|m}d}X}i}B~}x}_}}@~R~Q~X~@~B~C~i}`}Y}w}]}ƭ||`}j~R~öp}g}T~^~}Z~M~|n}}a~Ɂ}\~G~h~o~~D~\}^}s}u}q}j}\}ƅ|܏|>~d}m}֗|]}W}k}>~]}|}q}Y}V}||{|v|T}{|X}ؚ||^}W}Q}n~F~U}|||{u|||{x}T~d~P~|?~w}m}d}Q~t}|}`||J}M}p|{n|{΃|v|W}؈|˾i}M~||Փ|x}L~{J~dž}ǿ}_~}}ޒ}ܑ}}̒}}o}w}q}}p}t}|||||g}Ļk}f}{S}h}x|{{{{{|||{}~B~|[}_}ә|Њ}ڇ}j}ƾx}n}M~b~[~S~}V~}֍}~}}»l~S~Y~ߗ}j~[~W~T~}}ӏ}Ӌ}h~i~ߙ}ϐ}U~T~i~}y}}׫}}^~}S~f~w~q~c~ڣ}p}}]~ڣ}c~Ú}}|||||||||||||zz||{}|ª|l}}d}yz|{||||e}ɠ|||ӥ|k}c}h}|{{s|ő|y}R~}~}ّ}T~l~}{}w}y}g}}}\~o~i~]~V~}̐}T~]~}f~f~a~ʐ}^~|J߈~]~e~V~V~}c~Q~Q~ۖ}}p}o}ٍ}|||||||||||||~|||||||||||||x|||x||p}p}a}}]}b}϶|k}t}~c}a}|||۴|m}q}||||||||j}|[~Y~y}||||i~\~e}»|}[}||y||~||ȗ|~||ї||R}В|ؗ|Z}^}h}i}|x|{{{|~}||||{{tv|~|||||f}X}ɿw}d}N~S~P~ˁ}n}m}b}}}i~r}|{|q||||||||||g}ը||ּ|v}͊}m}p}}}||h}}|s}e}m}w}ג}u}|||}܌}}T~}|~|e}b}c}Y}\}|||ù|r}d~;Q~M~C~}}w}D~D~ĵ^}J}@~}<~ĵQ~N~I~y}C~r}z}g}P}I~i}{A~u}̾j}¹|s|~`|k|р|؆|ڊ|õc}B~_}|P}~|||}L}[~K~O~d}|ܯ|{{{{{pr|mo|np|||wz||y{|vx|wx||~|}|||||||||||||||||||v}j}||}ؔ}Z~ӏ}}Ӑ}}׏}y}Y~Y~K~\~a~K~j~m~S~V~}o~U~ͅ}J~\~n~s~m~X~s~Pl~S~S~}c}|t}|||||||||z}V~I~ΰ||vv||{{{s}V~}g}Ľ||ɭ|||||k}p}||B~ø]~w}L~E~R~l}O}|v}}}v}|}}|||{{|{ų|uG}ѵ^}l1~|9~E~};~e}m}ԑ|~||M}N}|E}]}<~O}s|}|e|z|z6~˾T~P~Żc}[}P}i}~=~{|X|ǿy||P|d{u{r{{w{{{zzzyvszz{{xz|{{{zͽzzyyy}ytuyxx{yyxytyxxxxxxxwwwwwxxxx{~y~yyyyyyyzstzxwxyyyzxzzzzzyzzzzzzzyyyy||yxzyxxxyjz[zyKzywykyxމySzyYzpzɅzezݙyyyyxXz~yʖy}gzizazyۊy}z{zuPzaz~zazczqz~zV|V|zt}O}B}{{h}c}=~Q}}{D}iz{yؚyޗyy{yyN{hzyyyyyՑyڻyyyy}zqz[{ozɬy{ez[{lzyczlzwzzvzmz|_zzszmz{Z|e}V\~mzl{{|f|K}|~{^|r{x{m}d}{}Q}Ԉ|l}j~MI~^~f}I~g}n}}R~G~||ֶ|o}ר|Ʒ||oq|||||||Ɇ}||||||||ɿk}|q}I~W}xD~H~˾b}}|D~{<~k}f}|\}}ø||}ߣ}۟}t}P~f~ļi~p~y?~w}L~ʺ^}H}}|Ј|C~W~D~<a~87J~S~AZQKiL~m~@Dz~܉}h}G~I~y5[~Q~V~{~̿n}Z~X~z}Y~u}L~ĹX~Ef~X~X~}M~e~J~l~E~R~U~}W~V~~P~}p}Л||H~n}U}j}U}d}^}¶b}m}_}Q}a}K~s}P~D~p}}o}_|M};~g}û{|{Ԭ|k}j}k|T}ķe}Źc}p}J}l}D~O~B~B~>~;~_}Z~Y~|J~M~k}y@~F~e}|z^}g}F~f}|B~k~}s}K~|}Љ}S~a~Q~}M~X}E~}\~R~g~a~}G~s}s}ͅ}}{}|p}|f}v}|v}X}a}f}_}s|s|M}r|||{{|s}[}f~>~q}f}[}e}t|r|~S}}A~T}t}@~Z~L~}>~O}ļf}˿d}W}|J}g}c}P}Y}w8~|\}}P}N}Ȓ|f}g}^}b}ۯ||||{{ƽ|t}O}{N}U~ʈ}҉}R~S~S~T~|l}}}k}|||||g}||W}T}Վ||Y}\}ߡ|u|{yx|||v|z|]}S}œ{ơ{{m||Π|Ǜ|||T~}j~}Ć}Ԑ}ӌ}|}}}}}}^~k~}~k~~T~}q~{~؀~I»m~}{}n}|ܚ}ٓ}}}ɛ}|}~|~i~j~k~b~n~z~^~|}}ڝ}|||||||||||tu|z|||||||e}܉}U~u}j}f}u}|Ġ||أ|||||ܤ|פ|}]}{ëz{ٻ{|h}l}}U~ʍ}ږ}ľ}y}{O~i~d~w~}~߂~x~f~d~g~d~o~i~\~[~\~[~s~j~ſv~\~X~~w~X~žo~~|~Dž}O~Q~o}{L~b~}z}||||||||||||||||||||||||||||j}l}k}y}k}k}|}||||Ҷ||u}|||||||||ϴ||||||b~X~X~|Τ|h}~}R~P~||||{|||||{R}U}m|ʩ{o||ݡ|Ô|y|{{{{{{||||||{|||||J~k}~E~b}l}e}b}|a}m}z}{}[}}|{|x|x|{|||||||||ͭ|||o}q}|}K~m}t}€}Ǫ|}Ŋ}m}ſ}U~b~ψ}w}}ڏ}͉}Չ}x}dž}Ҋ}H~c}|m}k}p}H~K~e}a}|~c}X~c~P~g}Z}|S}i}øS~M~i}y7~;~A~H~{;~~>~H}f}S}ɻe}\}Z}R}P}{|=~a}{Q}|{|||J~d}X}U}|؏|I~d}U}}|W}~>~ŽZ~F~{}f}~|{{{wy|no|qs|||||y|||||||||||||||||||||||n}l}֐}Z~d~U~g}l}j}R~zJ~}I~V~Ї}}}c~L~c~}]~Y~?T~L~h~_~»y}B~}6@T~φ}m~@n~P~>MU~b}M}ˀ|\}h}||||||||||}f}||}||{a|s}T~Q~ř|ك}M~ڈ}P~ˇ}|Ȱ|y}}Z~a~{F~W~m~c~}b}Q}V}\~T~T~\~Ӂ}ٞ|{|x||ͭ|S}F}ܻO}>~B~>~w9~j}a}g}a|Ӥ{yS|d|{A}O}h}C~8~v||A}s|Ё|p}ĸP~A~_}~||7}˽Y}}H}c|k|^|z|}Z|ǿ{{ڍ{e|{c|l|{{{{{|~|||||||||{{zyz{yxyyyyyv|T}m}w}C~j}Y}I}j|G{oxxxˡx˼xxyyyyyyyyyqqzywxyyyz{}zzz{}zz|zz}zzzyyxxyzz{{{zwv{oo{zzzyyyyyhyaytyāy{UzZzbz}YzWzazmzyydz}Qz]zazyPzUzVzyvFzZzZzxTzxzyzkzczփ{zڤzi}j{Q}d}{||L~K}a}k}Q{h}lzڀ{ŒyҐyyx_yqyܦy]{zhzyyyyysyyyyzz{~zߗztzyzyszyyyfzyvYzjzZz^zyxYzvzN~s}a}j}p}}zg}M|j|q||Q}[|k|ÿ{؈|m}H~\}?~p}O}@~¿X~X~a~[~D~r}̼m}N~b~J~}̫|c}k}h}|||||}w}||p}|||||||{ڿ{z|N}x+>~t~O~a}?~G~};z<=K~͉}ƈ}a~zK~b~a~T~}p~}_~\~T~Q~||{w|=~5G;W~?J\~y5Z~P~f~AQX~o~Dg~~}}}J~}F~k}w~d~m~c~v}S~N~U~`~O~p}οQ~=n~I~ˎ}ˏ}X~[~~g~T~ߕ}x}f~[~}}n~U~ۂ}L~|}Žs}[}g}s|^}ۍ|m}H~M~O~H~~?~f}Ⱦk}L~[}y|H}>~R}{{{{{v|T}y9~|9~h}=~|^}ӻI~A~P~k}?~x1;~J~]~Z~]~Q~t}S~c}|{}g}x}·q}}D~>~T~p}֋}R~Ľg~n}o}x}|}M}щ|L~z}K~O~^~a~h~U~P~O~R~}V~l}^}܉}|||x|q|К||ޟ|s|ݓ|{{{Ծ{p|{`|}H}M}w7~N~?~|r|zP}ǻf}`}|c}p}O~Z~r}t}F~M}\|~L}Q}{g|{|b||W}k}<~_}S}y<~y=~d}x}|||t|o|l|w||Q}Ĺj}i}ÿ||w||}¹]~M~p}~L~}~}||||{|ӎ|S}|c}ۤ||z|s}z}{{||||Z}c|{|||{|||||˔}~Q~b~^~S~׋}p}g}υ}|P~N~}}o~}P~`~ÿ}i}s}}`~g~}|}~~n~}}|}ә}}|t}љ}}~X~[~j~٘}ݕ}r}||||||||||||||||߾|||||c}|||v||{|”|Е|}U}_}|||ҡ|ɝ{ø{{ÿ|r}J~^~W~[~}~L~}~`~\~c~g~[~{~[~_~h~i~Y~ȑ}}}}}|m}„}|T~ߒ}o~y~{~{~y~]~}h~ݕ}j~b~[~}ɘ}}}v}||||||q}|}x}ɺ|||||||||||||||{||v}|g}m}v}|}m}n}s}m}}}~}j}״||||||IJ|ɫ|s}z}X~}]~]~ԕ}Į||{}~u~X~||||||||֡||n}a}Y}b|o||||||{{{|{y|y|{{{|||||}t}~}d}a}Z}x||||Н|||~Y}|||||{||||||||²||j}͈}}v}T~u}½||}}g}||~}}}}~}n}|m}Ռ}݇}Å}|O~}H~a}|i}||}ǽ\~m}p}j}}J~I~k}b}ùm}\}E~J~_~?~=~G~L~U~I~]}h};b}K}^}]}ȍ|{͈|Ȅ|{<~xK}g|g|||a}w}|g}r}ʽn}ߖ||N}zF~I~Ǹd}R}|Žc}g}Ԅ}܊}L~|»||rt|tw||||||||||||||||||||||||||k}}q}}˔}}}Z~}_~܉}|ӂ}L~ǶU~N~m}ͩ|u}e}s}|||}P~Z~Z~Z~};X~T~G~T~Ex6żl}m}ȾX~f~I~>~H}4A~H~X~O}g}|||||||||||z~D^~||υ|˽q|K~e}~}͐|{˜|Z~e~@}~}t|e|A~:G`~źY~c~P~J~r}|B~^~ǸP~c~d~=~K}{u|U~Y~W}a}ιQ}:~P~F~ĩD~Y}[}H~E~ռ_}^|~|Ҿr|m|÷R}J}<~F~K}i|B}K}f}J~D~|B}v|D}|yJ}yE}P}Žr|h|d|{w||܀|ſ||r|t|{ٸ{{{{||||||vw|uv|{{}~||{}||{{{{z{R}L~[~Y~O~X~|=~r}y}H~´b}u}}>~z>~ň|Շ|L}q|{zz{zzšzzɩzK|Ҵzxxyywyzzzzij{zyyuvzzz˿yxxwzzz~~{{zzzسzzܦzhz|KzOzmyWygynyzyщyۄyyyʒyywQz\zbz~zTzHzځyyyԌyŁyUzuQzbz[zizmzazvz{U{̒{g}]{s|P}܀{^}s}O}^}h|t{v}Ϧyҏz՚yyvy~yzrz|{yst{nq{{{il|ps{zyyf{o{yyN{yzڲyƦyyyyfzx_zyyyyyyڙz|F~;~~|c|u}a}:}G}ل{v|؀|f|Ѝ|i||b}>~A}A}f|V}]~J~6~C~D~n}Y}y<~Z~[~ɺP~p}g}f}m}||y}|ut}|}||k}||||a~v}|{{R}g}Z~ôJ~x4~F~?~ȶN~<~U~|~T~Y~C~a}b~Z~ým~f~T~ý]~y}H~l~{B?~L~^~Q~|H~x}Y}˺U~7v0b~7?X}5b~[~K~C~Ǿ`~>U~EG|~\~i~T~z}q}u}S~|}w}v}}U~}g}l}U~@~P~}7z;s~@p~ļm~܌}}x~s~_~n}ы}t}j~u~_~c~Ŀ}}y}q}c~i~}e~}s}`}L~F~f}_}C~}N}e|c}y}\}j}I~Q}{{Ƽ{{|{¾{|g}@~E~=~l}i}R~x7~`}d}g}ŵQ~k}S~i~O~}}ٵ|j}{}P~i}\~g}Ă}|G~}÷W~p}øg}o}M~G~v}U~Փ}p}b~}x}h}t}H~t}{}}G~߁}|E~}O~H~N~Q~j}}_}||||{{~|W}|ܖ||z|Б|{Ŷ{ƴ{ǵ{{N}n}{=~ҋ|u|ӳ{|J}Q}}D~k}q}}{=~q}C~D~|;~]}k}v}ʿe}_}X}Ƽg}zL}a}~L}n}C~`}z|~|]}µU~v}˾j}||||||||||{|߼{||ђ}׊}x~O~|||||{ڬ|\}ȡ||^}|{{ý{Nj|`}{{|ɺ||{|||||||p}}}w}l}}}}\~x}p}h}|b}p}ѫ||c}U~k}X~[~l~T~U~}ޗ}}}Ǒ}}U~g~ÿw~~`~}^~|}Î}}l}||z}}ߗ}ϋ}·}i}|||||||||||||||}}ƃ}}}؊}s}l}}K~Z~S~O~}o}||||c}`}¤||R~Z~ڇ}·}}\~i~~|Dǿo~¼r~k~f~k~~l~s~~~HRt~X~g~~z~o~t~]~o~~T~DŽ}v~ф~y~`~]~ۅ~p~W~k~c~}}j~b~}_~ρ~i~s~X~z}w}m}g}}|}{}||ҽ||l}ӿ||||||||||||||||u}|ڶ|Ա||}}}p}|i}w}|q}ʐ}Ǎ}ý~}ބ}I~p}k}x}}|}ه}\~Y~]~Ŀt~v~}Q~}G~u~R~i}Z}t}d}|j}}}o}f}\}f}v|{|{{{|||}}|{{{{{{|{{~|{|||w}|\}||պ|δ||õ||~d}k}|||||~|{{|||||||||||f}v}߻|}~}}{}z}ă}q}|o}}L~}@~ƿ||u}s}p}O~}y}e}}~c}l}||}}E~U~ޕ}|{|S~}t}y}i}D~T~@~\~U~H~µS~I~ހ}}U~|{||P}ɻx|o||ͼ{{׎|ۑ|[}}@~Ʒ]}p}N}|f}\}W}Z}o}Y~~=~E}||T}|Ԏ|a}Α|U}D~Y}¸|{xy|tw|{|~||||||xz||||wx||||||~|||||||||||¼|n}ގ}]~M~{G~|G~O~H~Ͽc}r}|{{М|D~|}t}|n}}u}S~|~g~Z~L~L~J~c~};Q~H~p~>V~K~{<Q~D}Q~<i}|c}||}}|{{|||z}|zA~`~G~|R}a~c}||Ȇ||||P~}QQ~{|l|@~;=Be~V~E~H~F~N~R~A~S~H~C~k}p|O}a~L~Y~\}I~q}E~Z~Q~N~B~N}D~ȶQ~F~Ŷ[}M}b}P}J}P}C~O~ǺU}=}G}S}Y};~F~N}P}Y}|S}N}v|Ӏ|z|{N}]}[}||v|o|j|w||u|m|`|{{{{}|no|{{{{|||{{{wy|{|}S~ıR~L~L~V~N~U~P~T~w}}E~@~E~´c}o}O~S~R~[~S~I~K~I~G~ůK~|:~>~DzU}õo|Ԍ{ʌ{zz~u{{z~}{yyyyxxwwzĵzzzzzͤzƀzԖzݠzvztz\zyGzvyuyay_ywyzyÈyyszɖyyy{zezvzyyxhyyyyRzgz߰z{Х{{{{z|ii||R}D~j{o||{V}o}d}m}lzM}xz{yyضyyy~{{{{{rt|oq|||||||z~|{zēzzc{ŏzwzyyȸyyǫyyuznz˜yĔyȖyyTzǘzzI}S}z|L}}b|zB|@}h|ڈ{e|ǁ||ΐ{^|K~F~S}a}e|Z}Z}i}@~G~\}q}B~c~E`~_~x}i}l}q}}z}ʿ}}v}Ŵ}}}Z~u}|r}|||d}y}̼l}\}T}S~U~K~7>~E~4V~T~]}\}=~u}z<~F~T~Y~d}w}y~~þl~a~`~þm~y~S~N~X~K~B~V}t}ƸS~M~M~F@ECd~D~z}w}y}g~[~Z~o~CV~~~]~}ŋ}ڜ}|||Ó|D~Q~öR~W~L~L~m}|;~@~Aǿc~}G~Q~o}Z~Hc~t~}ſl~\~ڗ}}|}^~}J~g~n~ߕ}v}~L~I~z}ƾk}b}^}B~B~y}F~l}E~[}V~j}^}}Z~}Æ}|{̻{{||{y|s|B~<~c}ʹW}h}k}C~7~L}M}ɺT~}|>~`~N~s}P~{H~r}}}O~}}}w}\~xB~||o}G~Y~z~f~}w}J~m~^}E~Y}˘||r}ǽl}a}f}f}F~M~x}h}R~{G~|}a}|||q}}}_}^}y?~o}s}F~i}ȼj}e}V}s||T}<~]}Ԅ|S}ӄ|k}g}X}}}f}O}N}g}s}s}}}}K~Y~D~~}l}[}Z}K~{:~|||k}P~N~}}}}}t}|m}|||||||||}|ֱ||||||{{|{{{|{{{{˽{{{{x|—||||ɱ|e}k}z}y}w}u}g}}|}{}q}|e}i}d}ܦ|`}u}z}z}w}Ȃ}N~w}Օ}Q~ݿ||ך}}}a~k~{~g~}n~}~g~`~}}}s}}}{}q}w}|s}~}|}||||||||||||||}}֗}|i}σ}}݅}{J~}x~{L~K~\~_~a~T~ގ}N~Q~_~i~g~،}\~Z~|P~ۏ}}Q~i~_~}}}ϐ}ۏ}`~n~}S~i}p}}~u~e~m~Ŀu~r~փ~~~h~}ڏ}|}q}ſ}}}Ԓ}}ҕ}}}þ}}О}o~|~~ށ~s~}}}U~Ǎ}|t}t}}}s}h}n}q}o}°|||||||||||||z}X~}ׄ}k}ސ}~}o}n}g}t}ͭ|t}u}Ä}Ʌ}[~V~}}U~W~R~u}_}|}e~}m~R`~}i~~׆~}~\~Ȟ||v|z|Л||}}O~}J~\}ԛ|[}\}|{||{|y|}|{{{{{{{{~|||||{|||||Z}j}η|~}z}Ұ|c}Q~q}ģ|ھ||o}l}y}p}|||||||||||~}x}u}¼x}m}s}ӹ|w}߸||g}m}ż`~i}|||κ|z}ٍ}n}a}s}a}|®|۵|m}T~b~U~O}ż]~\}v}}G~j}G~G~K~K~A~Z~N~P~e}v}ļj}I~F~k}r}|{{{{{|||Q}^}q}>~>~i||z|||l}r}D~[}|܌|L}b|ܑ|o|{|b}~A~o}zL}|{vx|wy|{|||||z|||np||~||}|}||||||||||||O~Ų|f}o}{J~O~h~F~G~o}Z}J~t}}\}m}»r}żk}`}l}˄}߷||q}׳|R~n~~~m}n~P~p}F~X}U~w~GI|~l~Q~i~k}P}I~U~Ľv}g}^}||{{||||||D~~}f}|t}x}{|{{u||||||}p}S}||̚|g~?F:@~I}g}P~H~c}H~T}K}|<~l~ú[~d}w}e~Q~Fl~O~Q~N~N~Z~U~D~B~I~L~A~U}K}y4~\}c}L~{1<~a}z6~N}9~:~?~øS}L}T}}|^}zA}p|h|m||ǽn|X}|||ƚ||||}||~||ʼ{{{{{{{{{{{||||~||{{||j~7a~Q~O~M~z<~`}S}J~S~J~̼T~U~b~a~b~}[}E~F~E~M~˾[~J~Q~F~B~F~N~F~t:~j}z7~»u|}N}ߕ|c}|~|~|U|i}O}ȺZ}~{x{qzzz||{z¼zȑzz~z͘zߥzzwzPz{JzXz]z˄yvyymyiy·y}Xzyy٤ym{S||n|o{i{zz{{{|f|r|ݝ||ˍ|Ɋ|z||s}`}S}|{|nj|ԛ{{>}Y}L~=~x{z|}zġyy{szuy{{{{}{{{{{{qr|{||uy|||{|{Ȣ{{â{{zzҮyzzz~zo{V{yɞyyzd{z|U}{f|V}h|W|H|{ۈ{̀|z|Ņ|a|r|n~R~;~B~;~[}B}h~J~U}i}J}J~h~ùT~^~`~Z~_}f}^}xC~j}L~~I~J~K~[~ɽ|}s}|||Β|J~J~i~K~?~J~3L~ʺU}֞{Ĺw|n}E~?~h}G~G~b~~L~d~F~M}V}ѼN~{88u~N~s~f~I~9~W~Q~`~f~c~P~A~BV>BA|;u~S~Ǽ]~r}?M~[~Ec~n~Gs~}V~b~}e~b~c}f~Ǿo}N~E~G~q}z}u}b~h~^~F{}W~N~Z~o~}}}}܀}H~j}U~˂}i}`~Z~Y~l}|ú|q}`}K~B~^}|<~m}t}C~C~z>~L~{}ĺm}]}žw}ρ}h}ļv}m}|||||{{n|S}{8~:~|7~e}C~D~}8~|_}=|F~F~J~I~{G~L~}}،}]~I~E~o}a}s}ƫ||||J~}9х}T~H~d|Ї|M~~X}|{{{Џ|P}u}L~p}|ё||̍|Ķf}p}P~K~Ϧ|u}z}\}e}~}`}^}i}l}E~x}g}|m}t}|{ޅ|[}N}j}C~{B~ȿj}\}|H~i}z}v}d}^}e}|p}[~žt}s}H~F~T~D~U}m}C~G~h}Ν||G~x}r}ҋ}W~L~u}|}|||||||f}k}|W~u}}||||||||||||||||||k}d}v}·}Ջ}ȃ}ć}}}ҏ}}}~g}s}|o}|٧|}Y}m}J~}r}||~U}p}|j}}}ތ}y}|||͛}½s~b~|}נ}z}}h~|~Ò}~\~}e~}}}}}l~}j}||||||||||||||}U~}ۑ}ǁ}}L~}}_~[~P~}žo~X~{}}M~V~ȿk~o~ºj~ڑ}w}~}w}ό}[~Z~V~X~þ}X~ݗ}׏}}\~_~V~c~k~g~b~c~z~a~[~m~ܖ}Е}]~d~]~m~m~T~g}ۙ}Џ}c~ό}|s}|¹||ÿ}}}h~n~|~I~l~z}Ζ}y}|Ț}}֏}Ӑ}o}m}}~}z}v}|}y}||||||||||||o}}L~}x}d}m}n}{}q}|r}}v}}^}n}҆}Q~g~f~k~g~}Ƈ}}s}k}|~k}̓}X~~n~`~v~̆}a}_}X}g}x}ځ}l}Z}l}ׄ}|w|~h|ι{|f|{{ǿ{{{{{{{{{~}|{||rs||z||||{{|||~||||ǰ||e}x}W}|٪|||||Q~}q}ν|d}j}||p}×}Б}Տ}t}e}|}}p}}Q~i}Ǩ|f}݂}»r}R~r}}Ҽ|~d}|}ƈ}k}R~ºn}L~}n}ʆ}l}ת|ľ|}ߋ}½~}d}A~Yq}Ŀ|ą|}H~r}q}z}~}»a~X~b}x|}q~O~N~~}՝|u|{{t|{{{{||{Z}r}A~S}ɼz|z|O}w|u:~̼h}T}M}r|@~W}݅|^}u}G~H~@~I~{<~±Z}o|{|{||||||xz|~|||||||||||||||Щ|ٯ|؊}|X~ɀ}P~Y~Q~v}{}~}u}`}s}ǿq}g}ļv}P~}R~g}||n}|Q~L~I~R~^~d}ދ}ކ}|ݤ|P~CSq~<U~}}D~G~h}<~8W~l}]}v|{{{||||}|d}|r||||{{|i}ڸ|ͩ|o}||||{||||k}[}f~H~g~a~^~s}J~~}w}O~|D~S~AǼ^~q~Ⱦa~}<LȼQ~L~L~J~Q~K~T~ƱH~{6~n}<~J~P}ӺV}}2~d}B~U~H~?~a}:~S}Z}a}5~Y}z5~N}J}S}d||@}G}W}G}y|z|{k|t|q|q|p|{~|||y{|{{{{{nn|on|{{qr|{|||||ͬ|NúWDF~[}W~d~q~X~R~F~f}o}Ƽ[~e~XQv~~;i~}:EK~S~M~I~K~M~D~L~J~`}c}[}R}B~~A~v}G~G~F~~@~W}c}_}p}t}h}z{zzγz͛zoz]zXzXzczzKzxKz}UzKzWzlzzءyxvyՙy{M}P}E}|R}P}a|{{{{{{p||||y|V}V}ûw|ļ{{m}|}}b|ȏ|{ü||t}B~y|N}{{{{{}{{{{{z{|{jk|rt|su|ux||su|{u|{Ӳ{||Х|{||uw|{|{~|{±{\|{yzz{~x{{{{{b|||B}k{t|zu|s|s|ٍ{T~|i|_}B~d}r}E}a}f~l~H~a~k}X~o}S~e~T~]~}D~|E~^}ȸi}M~^~F~D~T~O~}}}{|ۅ|L}i~k~Jp~CS~A~ǴE~M}ڧ{Ĺb}B~̼R~t}D~G~S~T~Y~q~³M~B~E~6~x2~V~m~V~O~`~ôN~y6~F~:O~G~>~A~@~>~@~F~b~R~T~f~=ɽ^~S~{C~AZ~C~˾T~H~}@W~h~h~T~]~`~_~a~e~x}Y~u}^~]}w}A~q~d~}n~p~g~X~R~V~g~f~p~[~X~R~R~~}}}h}R~}w}|\}P~B~v}B~K~s}|r}Ļg}k}~>~I~v}R}e}r}U}j}|}¾v}`}ʒ|||||{{ù{|{H}F~_}9~7~V}6~P}<~A~zA~>k~E~\}k}I~U~ĺ`~Q~N~}W~}W~O~k}|||}q~Hy~Q~S}ѕ|a}|{{{ĵ{|{{|j|ً|є||{؊|P}T}H~C~p}^}v}m}ŸS~G~A~q|ƵZ}g}P~z<~|}O}\}h}|]}|t|O}]}u}p}a}~}|f}V~z}x}|}r}{}||ýr}j}|b}[}}>~O~D~@~o}@~B~g}ȹd}U}q}{M~x}|}}}{H~n}}u}|||h}Ђ}O~}Ώ}}~R~~Q~}l}g}t}}~Q~Ï}||||||||f}h}f}p}|t}u}z}}Z~W~e~i~_~}|}|t}Ϩ||||\}r}p}Y}|_}N~`}t}||ޕ}f~z}i}T~~q~}|}޸}Τ}}¾u~y~~¾}~҂~o~c~c~Ì}}_~¼s~[~Y~z}||||||||||||m}n}{}ٖ}}u}w}}}K~s}~}ό}}|Z~U~O~R~O~|P~}}x}ʋ}a~}y~}}}U~ݑ}}ԕ}э}m}U~}}W~V~b~c~f~U~u}ޙ}_~b~g~b~Z~o~v~ýq~}h}Ћ}޿|}s}}Ք}ڛ}[~s}l~h~O~~J~X~[~}|}}X~}}}Ԗ}}}}Ր}ē}̖}}w}u}}}g}||o}}޹||||î||f}U~}ä|۲|ƃ}x}k}ت|i}q}}Q~V~c~Q~}v}ȇ}}d~Y~w}̗}[~В}r}~R~a~~Z~ƿp~_~zG~|^~}|{{|b}W}||͐||ż{{{{{{{{|||||{||zz|~~||||}|||~X}|||ˬ|||۫||ܰ||s}l}ެ|}`}h}|e}ʝ|g}[~]}x}{}k}n}x}N~߄}W~}{J~a}g}W~g}|ɬ|||d}ь}r}P~}}}X}s}||ƃ}s}N~}}G~ٓ}v}}}}ۑ}v}|J~d}q}H~k~Fv|v|{{||X}q}X}o|]}z}|{l}}~`}}|r|e|ۺ{֛|i}X}|r|`|{~]}|i}h}L~G~E~o}z9~Z}T}²e}˼h}t}ɸf}|Z}q|Ĵa}c}b~W~N~^}r}v=~j}z|{|z||||||||pq|{~||ux||||||||||||||||y}|ɉ}}}y}H~փ}˄}\~}|ܵ|d}W~R~W~p}|}}ǿ{}Q~p~o}F~R~H~х}||e~a~g~N~~~}`~n~k}x4~>~A~>~K~A~{9;@~{{ӥ{|||Y}f}{H~V~ȕ|||{|{{}|Ɖ}Կ||u}z}{~ք}||||||}؈}}r~s}}>u~ƻ`~k~X~y}g~Z~Z~U~^~q~_~t~Eg~E~O~V~w}B~ʱN~ħF~˭E~9~G~>~B~9~>~9~5~G~X~A~}6~8~Z}R}^}>~:~˿[}L}@~^}|||Ǿs|a||P|B}{ۻ{{{{{z|l|y|w|{{t{{{uu|ln|tt|{||{|||||||i~ǻYxk>:S~c}j}]~U~~B~zL}r||w|_~AöT~o~|9B>@58Z~N~C~=~ϻS~îN~V~G~@~ĺ]}Z}a}y<~|X}Q}~9~f}x9~t}[}X}{{{zzƩzlz]z|Lzaz{j|~|{J}{|{{{۴{{{{ޫ{y|[}9~_}N}L}|h|n|{{{{{{^|}|k|o||n|o|{e}ܥ|k}{y|{|W}a}Ŀ||{|{||{{{km|{{st{{{{|{kl{lm{wx{{{{|||{֜|ͳ|||qp||uv|{{{w|P}P}{{{{{{[|{N}G}{|[|b{L}֊{n|ڑ|^}}j}{{f|I~z|a}p}m}<~f~H~D~D~f~G~V~M~>~j~J~ɴV~b}E}k}I~x>~|<~V~J~Q~j}||{||I~g}O~Lw~ǿr};g~q}~_|`}H~=~´L~Q~w.ϿO~E~i~q~=~c}[}h}Ƽ`}P~ܖ{{I}A~5E~I~;~B~>~G~кL~A~ƷR~J~ǹZ}X}B~{7b~?D<X}Ļ`}B~Z~d~J~S~z~y~e~U~^~T~U~_~}}y}ͅ}Ѳ||g}K~B~f~R~S~h~o~j~b~d~^~f~k~s~i~{}}O~~}_~յ|n}O~F~}}}n}E~Žr}d}s}E~`~}Y~S~Y~o~_}q}T~B~N~˛|W}A~W}r|yF}S}Ŕ|ʳ|||{̾{|j}L}d}:~r|S}f}Y}Y}C~y5[~Z~R~ǷQ~<~R~s}v}W}|S~X~΄}̂}Ǽ^~T~v}r}j~t~v~Z~{}l}a}|Ҳ|||}|{t|{{{|W}I~h}x|ߋ|o|}|ͽ`}|{9~Ⱦc}˿g}ʻR~P~C~j}F~X}E~G~d}f}K}K}||r||n|„|_}`}f}¹n}j}||Q~}U}k}h}P~y}L~w}d}|}V}|L~I~y;~ŸP~y8~Y}x}|n}@~~A~v}Ն}k}N~u}{M~}}}؅}~}h}úr}o}u}z}}}L~T~L~S~N~L~|||ür}߇}P~i}|||m}|̆}||Ѿ|i}||||||}}k~}~d~y}ӹ||[}z}]}||թ|ۛ|x|z||}О|_}Ѕ}܇}T~}x}|ԗ}܂~Ru~~҈~€~Ɯ}o~Y~}}}}[~]~}{}z}||}Б}ʍ}n}ÿ||||||j}|||}֏}n}|n}Ž}~}|m}|z}q}n}||||ž}½}ý}Y~}x~u~}|P~W~f~V~}}ľ}}֔}}Ɉ}]~}}R~}}X~S~g}}|}ڏ}}T~l~l~h~l~W~ϔ}}|}x}~}}R~ю}}ϑ}n}}}|}j}~}|||}}}¿}۞}ȅ~_~т~X~}}||y}|z}}}}}}}n}p}g}f}|g}}j}w}e}|y}H~K~O~F~x}߄}_~ù_~Ĺ^~L~{}K~Ā}W~Z~M~σ}}}b~b~j~}}|~~~K}}ùr}Y~{}{|{{}|||_}͚|{{~|{{{x|{{|||||||{{}~|{|xy|{|{z|i|{|||||||r}l}j}|}}c}y}q}͊}|j}ܩ|l}|U}I~v}Z}c}{>~T~Z}||ͬ||վ|||ζ||J~|՗|ǀ}¿|u}~Q~m}ڎ}}}|L~K~v}n}ݑ}r}}ɂ}}m}j}ƽ{}}y}n~[~[}۠|{|ž|Ԯ||w}{}[}O~}}|Y}p|P~t|o|\|Ȗ{W~}|I}{?~Z}|_}Ƚ{|ԩ|ߤ|E~^~N~G~]}o}o}`}|S}T}^}]}O}]}k}B~D~ƲK~A~E~B~|^}|{{{||||||||||||||||||||||~|||||t}m}}}||ڎ}[~}}Y~T~n}{||[~|@U~Y~x}g}g~n~;}~F~}~K~Y~Q~ډ}T~N~Yy~V~}y~W~@~2~:~~=~O~J~||K~{@~y|˧|D~z6s~f}ɾ[~_}K}p||b}E~c}{ӥ|S~C{}|||Q~x}s|{||||S~d~d~߮|ƨ|[~}}k}M~R~O~V~e~t~p}H~U~R~~G~O~b~^~D~B~F~P~ɵR~]~x1A~u6~L~R~[~ֿM~=~=~U~Z~?~:~@~R}\}V}M}5~c}ɿX}i}ĺr|O}R}q|r|b||{q{{{~~{{{mo|{{ɬ{̨{{{{~}||xx|||||||||}n}S~u~`~LuH8@6R~v~R̼R~k}<~G~L}t}l~]~`~<~ҿM~45B~Q~o|Ƃ}N~9~C~^~|1;=4S~D~>~u}{?~W}T}G~Y}K}f|l}y|{{{{{{u{{||||{{l|E~7~Y}_|p|Ӿ{{{{x|w|¾|M}S}y|a|o|˵{Ļ{޺{ȵ{{p|_|{{׾{}|n|{j||^}v|k|˚{{Y|K}||Ϟ|||x|{{{{{{zz]|{zzzzjj{z{{j}{||m}½|{þ|{{{x||R}|{ǫ{{_|~A}Y|S}U}a}f|H|~{`||E~L}O|m|A~B}z|c|_|=~X}>~N}A~;~C~L~h~G~R~h}d}D~=~_~DzT~^~L~˼l}ǻo}[}y?~~?~}@~U~N~݂}Ⱦ||||ˮ|~}@Rk~٩|H~T}|z|||{|C~I~X}g|E~B~Y|<~V}X}V}|ʿw|x|ׂ|~|}|F~d}Z}Y}S}|A~@~wC}¶]}Ⱥ\}ƹ`}n}ƹU~e~MJ\~k}^~W~P~9Rr}N~x~t~^~f~r~V~||||Y}k|b}I~ʼT~W~A~Ⱦ^}U~P~M~P~g~O~U~S~Ć}Z~}V~}P~{G~p}|o}h}h}f}F~a}n}D~r}c}_}l}x;~C~M~v}}}o}J~J~n}b}?~H~X~V}X}T}]}|||||{{V}|I}P}`}P}K}X}X}È|Z~`~¶T~Q~b~p}l}S}E~B~י|n}Y~|K~ɇ}Q~U~€}P~X~B_~ſn}S~f~h}W}X}a}|Ӳ|u}x|{|{p|x|}?~U~V}Z}||ه|P}U}n|o}E~ĺe}>~G~\}B~M~f}t}x}F~F~F~ƹg}Y}c}d}ʊ|^}Î|Z}}`}[}||p}j}t}}||{}|}L~e}f}U}t|A~F~G~z;~zG}¿}p||]}?~p}Ÿg}g}c}ݲ|r}ȿw}|Z}`}}W}Y}k}||Żj}Ӡ|l}w}u}׃}^~Q~Y~P~s}v}R~s}}i}|{}m}y}d}m}|w}}z}||h}|}}}}t}v}ű||||||||||||||`}€}}}k}̴|d}þs~u}|~W~}h~t~a~z~~~}}x~~q~b~_~i~e~w~j~i~}|}s}l}p}||}}n}||n}|q}|n}k}x}z}z}Q~֒}|||_}z}v}M~Z~Z~}X~X~T~Z~^~_~\~f~_~_~h~x~Z~_~}}}}}j}|}y}{}r}w}b}{}}l~_~j~e~Ɍ}W~}^~~}s}|u}|h}|h}}p}Ҕ}Ɋ}v}ɰ||{}||||{}}w}o}|}Қ}[~ʀ~s~m~}~[~h~}ǐ}U~X~}S~|}}]~}ƃ}u}|úz}؁}}ʾs}ǻm}|E~I~L~w}Źh}t}}|E~zE~~}L~W~E~X~}K~g}e}|}Ջ}֑}h~Ɍ}||ȏ}¾w~i~x~\~üm~f~}n}|{{{{{|}s|{|y|{{{{w|~t|||m}||{||||||}~||{||||}||Ǯ|δ|ش||i}t}c}e}h}m}t}]}΀}x}`}f}|b}{}ۀ}k~E~N}T}Z}Њ|R}t|{^}zB~ؠ|||l}w}|{}k}T~ӂ}i}ƾy}T~m}}{}z}|}r}Z}|ˍ}T~}}}Z~q~͋}Y}]~z}l}U}{|l}Ƽo}}}I~ܚ|}G~~}a}ûi}R}\}{{e|z:~z7v}l}ߜ|{W}||c}ܔ|>~´R~D~[}?~_}N}Ķb}ȷa}\}r}~K}c}_}l}P}>~}9~8~̹S~M~Y}p|̼a}ɻe}@~S}ò{{{||||||{|||}|su||||||||||||||||}g}|}v}}}\~ý~}¼{}~b}t}V~a~ſ|}ˇ}l~u}ޚ}r~n~L~{}M~z}o~|~Ge~x}O~B}f}f~5~0~2~8~y,~{|a}}c}c}e}e}U~A~~K~ݚ|K~}E~Q}j}l}v}n}||Y~žV~R~A~\}n}b~ٱ|ת|r}|||||p}ļh~L~|}s}|ׁ}W~}}^~Q~o}F~X~}t}U}~}p}r}z}_}N}Żh}i}C~Q~U~]~:~B~X~U~Q~R~N~öK~ǺK~J~7~D~7~L}R}F}D}^}Y}o}߂||ú_}s|`|φ{{zz{{{{{{{{{qs|{{{}||z{|||||||||[}ɽe}b}o}w6~~?~Z~Pgs}ƿ\Hc_@n~M~q}W~r}\}Ԅ|{f|~I}n|ԧ{I}DZS}j||ϾR~[~W~^~L~<~W~Q~O~O~S~¶_}w8~A~I~{6~Q~D~i}N~8~ɦ{{{~}{f|{|}|ߜ|[}{V}|Ƽg}~;~m}øc}ɾ_}E}e}b}r}Z}Ւ||j|h|}|ġ|s|}|ݛ{Ӹ{{f|k|]|ث{{߰{{{{\|ت{{Β|^}o}߭{{{{{ýx|s||w|s}x|{{y|{|~|{{{ՠ|{{{zzzz{z|{~J}|^}{{|x|{{{|~f||8~{{p{Ή{{^|~F}L}E}w}|m}k{N||g}`|d{{d~y|O}^|e|E|\|j}D~J~C~9~A~L~Q}S}g}̿T}I~V~P~g~]~R~J~s}[}y}j~Ļk}S~]D~v~~}|l}b~L~{:9T||Ȯ|ʱ|t}Y}|b}V}ަ||O~^}e|{|H~P~ީ||o}}W~s}v}d~B~u}ґ}ۓ}s}m}k|h|a}D~y7x4A7A~Ļb}}Q}^}G~X~h~X~Z~}{~b~Y~½g~}͏}l}ǽ||X}|}d}|}m}U}{<~?~;~>~E~N~N~P~}m}ÿ}q~T~a~}`~~}|i}|u7~B~L~c}{?~|[}i}M~R}|>~v}G~H~P~ƼX~u}p}{I}?~b}˼`}K~w}؅}[~Y~T~V~}|||||{Ӵ{R}ƶa}|O}c}o}|`|O}L~D~g~j~Q~L~q}[}l}c}f}_~c~V~Z~d~Y~`~M~Y~p~p~|~d~[~U~s}||l}b}[}|||{{Ĵ{l}I~d}X}o}m}U}T}Ĺb}F~b}U}s}{}Z}~}O~|}h}X}\}ƹU~K~G~r}^}r}u}ߐ|ȕ|Ό}Y~L~||i}P~||߮|b}l}|H~}½y}c}l}J~t}S}zK}n}j}@~T}|t||\}a}R}r}@~F~M~i}ـ}т}|՚|]}j}x}d}͜|g}t}\}c}ũ|z}}}P~M~_~|}}}ݎ}}u}ą}}Ƈ}g}r}|x}ڌ}þ}v}}†}}}|||˷|||||||||||||||ç|ݶ|i}|||o}։}w}|i}N~l}l}l}ۅ}Žd~[~Ȉ}|}ƿk~Y~l~j~}e~}}}Ȗ}U~V~c~}}o}{}ϋ}|}Չ}}}ˊ}ٍ}ԋ}۔}Ć}}s}w}w}~j}x}m}|z}M~р}q}J~R~}֊}w}Ї}O~N~e~_~_~V~}ü}ɇ}q}͎}r}ƃ}}f}}}}}}}ґ}|}ڇ}W~e~^~Ľn~y~~j~n~l~X~]~b~ݗ}Ȍ}W~u}||ֽ|w}ɶ|w}s}Ғ}ߞ}}՞}Z~W~}r}y}|}|}w}s}}ߪ}d~~~b~٩}}}ȗ}{}p}Ԛ}Ŏ}ݜ}~T~ˆ}}n~܂}f}}¶a~D~t}}Ÿl}_}}}žu}Q~L~E~b}i}f}}f}Ș||š||k}o}}}X~Y~}x}r~[~^~_~`~k~S~|U}{{{{{{{z|~||||||||||g}`}f}ڮ||||||||||||||œ|Ǭ||]}g}½|b}Ъ|q}ޮ||r}}m}|n}֐}ޤ|[}~Z}ަ|M~d}K~P~^}S}i}E~ʙ|{|ԗ|C~׊|{{||`}ʪ|K~}H~σ}y}}}}h}x}ُ}܌}҇}h}l}ݡ|j}I~i}ˁ}»y}p}އ}}j}w}}K~y}t}Z~q}V~j~{I~}}d}џ||̧|t}x}}j}c}I~b}]AU~O}{|[}e}ϙ|u}O~t}F~̸]}|W}t}u}K~n}Ÿb}b}R}vB}Կ]}x<~I~?~W~D~W}˼z|O}Y}Y}ن|O}ƻ{{||{{xz||||||{||z}|no|||||||||||||||r}}я}Z~֓}ݤ}Ō}{}`}W~ǵ||ˆ}T~В}ľ}o~}R~q~ƿo~T~f~Ƅ}}Y~o~X~X~L~ƽv}^~m~W}W}L~>~K~[}X}a}z}B~G~|}||L~úf~a~z}c}P~U~J~I~||||f}^~M~M~g}]~j}Ā}q}լ|d}t}||||{}W~|Y~h}|c}|}L~x}}n~]~X~D~د||||y|{{y|t|o}Ӈ|c}B~_~F~>~V~d~W~ξM~V~S~N~ŸD~C~@~=~~4~G}M}s|x|`}d}^}}E}|C}Y|{X{R{yjj{{{qt|{{uv|{{{{{{{{{vv||{|||||Y}J}~|]}{7~e}P}^}J~O~:c~}Ӄ~o~>2|/;f~O~x~J~V~˸N~y9~Cb~|8M~ٿD~4~{L~G~˹K~ŶQ~^~L~T}A~D~h}_}o}x}r}A~<~;~;~j}M~a}{A}{{{{v|v|ʁ|^}r}Z}Z}p}A~C~d}o|Z|V}_}}8~k}o}z|j|c|f|n|n|r|b|գ{y|c||J}m|ś{š{{{{{{{{}|ْ|o}Ŗ{tO|ù{n|]|{i|{|{~M~{{|{}l||{{{|||{{{{{{z~|̕{ۅ|~}Ò|{{{J}~q|{{h|Ѕ|C~{{q{Q|{S}G}x|:~N~@~C~y|{[|h|N|M|z{L}p}H}x{x{g|c}S}?~f}H}O}l~F~M}a}x|U};~h~ǽc}W~d~[~P~N~^};~_~C~f~=b~Q~EI~U~úSb9@qv}||ʸ||{}e}a}p}H~P~ͿT~H~9~p|}a~Q~L~X~c~i~EK~g}W}U}u|||~}|7P}|P~H~@~E~r}S}d}W}||[}F~\~G~e~}|Û|{F~||||a}h}}{}A~G~V~I}K}g}V}o}I~E~H~s}}Ã}|}}}E~L~M~}Ȗ|]}q}}øg}i}b}w9~{<~O~u}`}C~}B~Ļ[~E~>~N~a}S~\~t}F~]}I~~}}H~z}}}J]~Q~u}|||||{{|z||G}{F}o}r{n|r}N~h~k~P~}S~d~X~S~ºa~W~}d~R~}d~{}U~üh~~C{~c~}a}|l}O~}q}i}}p}b}ȟ||||e}`}_}r}Ƹc}p}ŶS~M~R}W}T}ɿn}G~^~|C~}v}Ĺk}[}x}}J~G~x?~s}L~|^}}g}t}Ë|d}ف}W~w}h}N~`}||u}ûn}|^}~}h}F~s}}P}T}v}|}V}^}d}k}U}@~c}H~h}L~}G~}`}^}_}V}d}~X}|}k}n}~}L~|I~Ɍ}Ņ}}}a}k}o}R~f~a~}҆}׊}~H~Q~s}||ܵ|m}|j}e}~b}Å}ſ}v}t}ɷ|h}ȵ||||||||m}я}Ȇ}~N~P~W~p}|||||||||||̥||©|l}}t}q}v}҉}s}}}Σ}מ}Ӝ}}Ά}½}}ܑ}dž}}W~}y}u}f}l}u}n}|}}}}s}o}غ|s}q}}|}l}u}e}Ȃ}׍}Š}y}Q~׏}{}k}}q}}|}܋}}i}Չ}y}~N~¼}]~T~U~q}Y~[~~f~\~}e}֋}T~}Q~|O~DŽ}z}}ٗ}}}}~}_~Y~X~}՛}}}q}z}w}}g~[~~T~b~d~b~^~e~Φ}}z}}Ǎ}ۖ}}|}}}֙}}^~…~^~v}}_~}{}}Ύ}̉}ی}U~K~n}}M~N~zD~D~}}P~L~N~O~x?~w}t}|W}y}L~Q~Y~||||||}\~V~}k~s~b~x~̀~{~s~q~Z~̃}||{¾|||||||||||||||˼|հ|ƹ|ȵ|}ߒ}q}|||||ļ||ٽ|f}|k}l}b}o}||h}۱|^}|d}ÿ~}y}¿|]}|ݫ||̫|k}l}ڱ|y}p}V~E~O~F~}H~̐|}||||ۓ|}|{{{|^}{}^}d}l}n}|Ҧ||v}|||T~|j}ۅ}}ƿz}K~Q~΃}z}T~z}l}~K~R~}w}|I~r}_~X~|o}b}}n}|J~g}x}i}s}Ļw}e}S~HL?|6D~|Ѡ|||s}T~]~Z~|<~[}~?~λR~@~A~F~r}w}}N}|e}ͷN~z9~ŶL~K~|;~B~d}Ѿ`}x9~w9~Z}a|߿{{|{}|{{ln|y{|||||np|wx|{|||xy|rt||qs|||||||||ü|||}n}y}|X~X~}}|}|}f~a~W~f~}͒}Z~l~z@L~g~i~C~}E~ʿq}G~L~h}c}||ȝ|ʘ|˘||||ûs}g}|||ջ|x}l~h~T~}ф}w}ȃ}|L~|||Ŀ||܌}M~E~o}M~L~n}f~}[}|||Ɩ|{ح||z}w}e}m}|h}|ʂ}J~T~w}|||||||v|}g||l|{b|g}xA~G~òT~d}F~̻U~c~ǶI~H~M~^~Q~¹H~<~|3~ǽT}Q}H}ſk|Y|>}Y}l}k}\}x|C}Y|w{G|Лyyzzzst{{{{{{{{{{{{{{{{{{{{|yz|ô|ː}\~Z}{|Y}|O}K}S}|}o}}|~{||}|x|ʹP~\~ͻP~CPXNI_~38:T~O~}2U~_~Q}l}a}н^}ƴR}ôY}9~@~E~G~C~Q~y;~w:~t}H~g}L}T}_}A~}6~P}zb|{{z{{|Ē||y|||w|||ʻa}}F}˹m|D}O}c}˼`}|s|k|ٱ{{{ͮ{|d|{{w|{k|{ù{{f|{{z]|{{{{|e|R}|[|j|؏|z{{U|{ڀ|{R}S~{ď||{{|{{}|Й|{{{{{{vu|||||_}a|~E~{z{݉|}|{_|`|J}U}{{~{r|t{X|N|?~e}P~?~O~c~Z|M|T}B}v|<~\~D~|y|K|m}}>~O~M~ƿ[~]~W~m~d}x|T}k|?}@~6~<~M~Q~s}f~ĿY~X~G~L~u}Z~Y~_@K~Y~|O}JWKCH~d~X~|||||ԫ|j}r}ǻn}~}BD~X}¼z}f~Z~^~u~Q|||||{{Ԣ|]}k}K~T~I~C~̾Y~g}y>~{}p}~O}^}]}Q~D~K~H~v}E~u}}F~R~O~}}}S~D~k}W~d~d~}9ͿS~W}S}Ϳe}~Q}B~d}B~@~c}|||v||w}O~|}G~c}e}d}v}u}t}]}Z}R~J~B~p}V}ǽg}ɽg}A~R~T~A~N~P~N~|}t}||t}O~}}\~^~W~}n~X~S~ʌ}}}v}p}Χ|^}ō|щ|c|F}y|ͷ{{|l}L~O~a~W~P~ƿa~s~}z}u}x~FL~}V~}Z~g~h~n~¹`~W~|k}T}Z}U}{M}|n|`}T}|{{|{C~{@~ɽ[~J~w|}a|c|Ļh}N~|j}~D~G~|}s}H~s}~}s}|^}F~G~J}Z}G~Q~P~z@~s}t}}}}N~J~i}}J~G~Ӏ}G~}}}`}[}g}|}p}_}e}v}{}o}Ƌ|Y}b}C~S~f}Y~t~}|j}m}˫|ț||m}ĸi}||k}]}Ǽi}x}}{C~ބ}~}}H~|v}k}ӂ}M~}q}z}e~}J~݉}m}h}~f}θ||f}}}t}f}a}h}a}m}t}|h}m}||}ס}}b}q}j}q}ߪ||ɷ|ހ}t~{}{x||||m}|g}j}||f}g}Y}Ϥ|g}Ӌ}Ć}ƿ}}}̍}|}v}q}}}y}ѐ}}}}}g}s}}|}}|}}|}}}r}s}r}{}}u}f}͏}n}a}y}}p}|{}t}x}͍}{}ü|}ۊ}ۊ}_~}Ή}А}T~c~}|O~m~\~i~S~}O~U~U~^~}y}¼~}x}}Ɛ}q}y}{}э}x~~l~j~h~y}U~Ɉ}ˌ}}M~R~V~}Œ}{}P~P~W~|N~k}ø}}^~ԕ}}}}}~}x}}|}~}ܯ}j~_~c~}d~Y~R~}}k}K~X~~E~~}|D~V~J~K~N~|D~R~P~b}P}V}i}x}}}}Ն}Z~Ќ}}ƌ}֚}{}g}}}͕}b~q~q~~}~t~o~Ԍ~R`~}h}{s|ɕ|ܦ|||||||||||||||||h~|I~s}Ò}||ī||||ׯ|m}l}k}h}f}[}|ݦ|e}|f}ƭ|y}g}u}~}Ԭ|f}O~yE~~X}ـ}k}O~~?~G~Z~^~w3]~v}u}u}s}B~@~{x|}||Z}m}{}r~}ϣ|O~}]}r}|ޕ}˃}b}_}T~Z~}D~t}X~x}M~u}g}Ӌ}b~]~߉}؈}}||M~}c}_~W~I~Ԉ}V~}X}|֍}а|M~U~v}c}h~j~Z~Q~e~`~K~z||֨|M~e~J~Ƚ]~l}i}κL~9~l}r}އ|W}ôb}yJ}|>~°M~@~E~ȵO~v7~e|߂|R}Q}e}˿w|љ{{|y||{{{{{{{oq|{||||qs||||y{|||||||||||||||||}}Й}ҙ}m~X~Z~Y~ۓ}d~ۓ}x}T~V~÷]~u~W~}}K~}}P~|>~C~{}B~}D~|||||||||`}||||w}}y}y}u}~M~ǿ}}i}ǧ|}x}Ϳ||x}Ђ}Ӏ}m}~}}i}Y}M~L~F~|b}m}r}ϥ|i}c}c}ʩ|||e}K~`}{}B~n}yK}w|q|y|Z}c}|}t|{xe|q|Ƅ|S}l}L~Q~z>~G~˽V~U~]~Q~L~ǼO~F~=~5~H}O}J}G}h|d|W|N}:~r}c|Q|_|o{`|؂{yyznq{{{su{{{{{{{{{{{{m||Ү|s}|}}Q~<}\}H}_}Y}b}m}R}w|{{{{ý{|S}>~B~R~Q~O~v+8>\8w0v2T~X~n~18L~A~s};~f~J~ɿh}{;~|V}W}|y|i}C~@~e}˿b}Ŷ]}s|̆|x|~8~K}ӷ{{{{Y}|{͐|M}L~?~;~ȿc}I}V|O}|5~L}||t|y|l|m|r|h||b|{{{{v{{{z_|ǹ{{a|`|}\|a|{Ǿ|||f|Y}g|Dz{{{{V|zG}~v{_|L}O~»|}{{z{{{t|Ȉ|{{{{{{{{{n||ùv|o}{{zx|t|{ީ{y|H}o}g|{[|U|Y|t{U}X}f~D~A~_~:]~D}R}B}Q}W}o}H}D}a}K}c~^~F~\~G~_~L~x}P}Q}:~O}R}R}=~|:~R~>~`~I~};~A~m~{:K~W~L~Z~X~`~J~q~Hr~J~]}Ȟ|}U}|{|||}[~\~R~P~DPIk~B~GCHPHzL~~||{{{u|v}V~}^}`~D~Q}=~[~}X~]}J~`}O~h}`}þS~žs}|}i}~|j}T~g}D~v}S}Y}O~A9b~w5|;~C~Y~F~@~o}p}H~Q}q||P}|\}U~U~G~X}O}Z}u}l}A~g}{=~}{A~F~j}ɽi}|Y}t}i}N~i}z=~t}żh}|?~d}~@~p}Z}Z}o}G~{}}U~r~W~V~Y~Ս}ɀ}}M~üd~}|ؙ|m}f}Ǻb}{Ʒ{{Ⱦ{|ñ|||h}A~DS~J~V~x}g}W~a~n~i~Q~i}_}|}}P~e~_}x|K}W}\}g}R}|Z}d}|{||P~Q~|}Q~l~{=}E~þ||n}i}|}t}k}~}p}I~}m}x}٨|ýz}U~O~W}|j}E~{}¼v}ȗ|}}q}I~^~v~}J~y}ƾs}V~G~\}{C~m}e}]}՘|}h}^}^}}g}l}|{K}N~G~n}M~}d~}t}|t}}Y}s}Y}|\}h}zE~y}f}~}X~O~X~T~ȇ}q}j}}b~Њ}Ո}Ւ}}}ц}}P~Ԇ}n}|q}ٞ||ġ|ۨ|[}i}e}s}}}ϐ}Ћ}ľ}||v}}]}y}}j}|||h}}}l}k}֤||٦||a}k}t}r}o}v}}q}}s}f}w}ń}ʅ}֍}Վ}r}r}~}v}}}v}{}t}}}}~}u}q}v}t}}}w}lj}}}t}z}̉}}ޘ}z}ʅ}׊}}}ý}v}k}|n}h}z}þ|}}W~Z~a~M~Y~Y~~}}|P~}}Ć}Y~d~j~b~_~Z~}Ƃ}Q~X~Y~ȇ}ݎ}P~\~}W~d~n~g~V~`~h~Ґ}ɍ}R~h~M~˄}ʋ}ؖ}}}L~V~M~ʂ}Y~T~Q~Ŀ}ĉ}}|̺|||||}}Λ}č}]~p~}N~_}`}u}D~X~{C~}b}U}^}|Q~F~N~y}r}y}Z}||}q}|_}y}{M~}͊}w~Y~k~f~r~v~|~`~q~~m~n~\UW݄Ga~Z}||||||||||||||̪|˰||}b}x}|J~S~n~}|Ǽ||Ұ|||Ш|e}|ߴ||i}j}ќ||||̙|||}|Ш|Т||}Z}j~{}}}q}F~A~>~Q~J~H~H~?~Q~Y~}öX~c~t}Y}|||Ǿ||}|}`~t||q}r||||i}ڎ}}O~n}Z}m}z}w}l}q}}M~a}}S~K~k}ٱ|}}}y}j}J~c~^~ی}Ǒ}}|}}Ȍ}u}|t}|}[~]~Y~}{}Z}r}}8m~N~y9~u9~k}g}ƹU~G~{B~D~B~`}O}Q}ijW}{q|ۏ|h|f};~@~ƶ_}P}λU}@~v}^}z|e|h|L}ȹa}m||{{{{~|{z{{ps|{||{|uw||||||||tv||||||||||||||||||}z}g~}AR~}żh~f~J~}x}G~H~_}}}Źm}}ـ}F~[~N~@~T}|||||||||ٷ|Լ|||x}Â}}}T~q~Z~߄}|ܽ|h}R~Q~|Ɔ}t}Ƃ}މ}}|y}J~P~Q~}`}o}b}^}M~ût}J~D~F~q}F~E~O~b}³]}˻f}X}w:~m}w|v|x|{|r|}r|p|U}Ʌ|~|W}m}}}W~\~r~`~ĺX~J~E~F~L~F~7~{2~ºJ}@}R}P}w|r{e|Z}[}A}a|G|{H|F}W|f{zrtzz{{{{{{{{{{{{{Ұ{m|Y||r}Ԋ}}}}5~U}|Y}}}j}N~l}{{{{{{{{ÿ{ǹv|ǹ\}D~>~ðI~ʳH~=~W~U~[~^~_~~5b~^~5X~I}u}g}F~\}P~E~<~N~Ĵg}ƻ|A~·a}J~}}U}{A~P}h|;l|;q|zB}х|i|{{j||j|{G}x9~ưK~B~y9~b}`|úd}Z}M}w|οx|q|l|}b|e|n|b|{{{ߵ{ۯ{ż{{|{W|f|f|n|O}qB}s|Ͽ|ف|`|M}~|k|{{{[|zU}{~W|{^}r}zz[|{zy{h|{{{{{{{{{m|ʭ{[|u|R}e|{zF}ˌ{{|H}l|X}|{{||r|߁{N|]}t{B}a~\~|7Q~e}B~V}:~Z}S|E}V}|=~W}D~=~b~g}L~@~}F}|j}M~;~^}_}a}N}Z}@~c~h}A~}Q~f~G>?g~}}}}[~C\~N~l}վ||||~|||K~»\~W~e}p}ƻU~AJ˿S~`~CN=n~M~E~u|{{|a}B~_}v:~N~W~r}A~K~r};~y=~w}V~p}}p}i}w}H~n}|Ȓ|b}f}y5|8~d}¹h}zM}||L~Ff~b~Y~;~>~N~A~=~q}z<~x}q}Ԃ|x|T}L}g}A~K~v}|x}@~N~|=~H~d}[}|T~Ւ|d}||B~m}W}ν`}G~f}d}P}>~G~c}l}v}|xG}@~\}S}D~ûu}ׅ}z}Ո}ބ}y?~|=~w}t}Y~J~R~V~C~l}k}|z|p|[}S}s}|v}o}ĸ_~v}|ǵN~O~k}U}_}R}k}ùV~O~M~o}t}}[~z}X}ی|m}G~v}V~V}|m}|X}Ŧ|{{|a~K~ۀ}a~v}n~G~M~T~|X}d}}}{C~}m}n}}}˖||i}K~n}||}M~R~~B~a}_~}Y}X}|T}a}}e~a~[~N~[~J~ϗ|}H~|D~y}\}ף|||l}ٝ|||~|{u|K~z}܅}ލ}{}ܐ}P~Ӊ}||}}L~؁}||ީ|z}a}s}M~J~y}M~Q~^~k}{}¼z}T~Ɖ}}Ђ}s}}}ۏ}^~ߚ}ߎ}ф}r}t}y}}c}m}\}c}ߎ}܌}ٍ}ɋ}}}r}}R~ņ}ޒ}}}|ù|t}}~}||s}ʢ|^}X~_~O~Z~g}q}|}f}a}m}T}Źc}O~|J~Ѓ}{}u}p}NJ}p}Ƃ}ȇ}Ň}p}ƾ}Ɉ}̓}}Ό}؏}ߎ}y}}I~J~}}L~}r}z}Ƅ}Ë}r}u}ÿ}r}n}i}f}}|}}K~R~V~K~zK~a}}g}a}}ā}ā}x}`}˅}_~d~S~d~T~{M~}}W~z}}a~Ĉ}}_~t}Ȃ}N~O~e~^~a~`~a~g~}Eq~O~X~T~]~Y~݊}؊}ߕ}}X~[~R~M~}M~U~O~X~\~f~T~u}}}}{}s}ڏ}ݐ}Ƅ}~R~}T~}P~T~}O~}D~O~Q~~D~b}|{d}||}j}j}}L~܀}`}]}Ļu}ڃ}o}}v}}}O~j~Q~t~~{~PY~g~m~[~}\}l~|~Wc~i}|{{{{{{[}Y}|}ǻq}ү||||V}|}{}}÷r}M~T~^~~}||||||e}Z}ܱ|i}j}s}f}|||||ڧ|~^}n}W}|Й|||j}g}s}Ƽl}J~I~A~y=~I~{}{B~F~^~k~K~K~H~d}ݚ||{ww||έ|[}t}x}ю|s}{||||||`}o}t}Ѝ}Å}c}N~v}F~G~=~_}c}^}d}||ʃ}d~}ȟ|{o}Ҳ|^}|}i}w}k}}|ͳ||<W~}}ه}~}ǽY~EŻV~L~D~Y}]}y:~H~C~F~V}X}Ԅ|̭{q|_}̿x|j|_|P}k|`}@}J}7~I~ŴM~I~΋|i|S}ķb}k}s}E~׎|{{{{{{{ij|tv|{{st|ml|ll|vx||||||||||z||||||||||||||||||m}^~ƾh~\~\~Y~H~@~h}Џ|ǎ|s}}}p}]~Au}g}}v}B~^}{|||||||||Y~|}x~u~j~L~||a~W~|}h}]}_~½w}}œ|~[}P~}}T~u}|T~ºd~J~V~X~l}c}^}e}K~T~H[~I~V}D~L~P~B~ϹP~f}O}<~@~{K}l|{y|||||w|{v|͏|j}|J}m}P~U~L~ȻR~D~D~D~?~1~}4~^}K}Q}:}g|V|G|}<}>}C}K|Z|{Q|n{U|ʚzzz~}zy}~z{{{{{{{{xx|{{{x||i|}|{|e}f}Y}ӑ|Ê||j}E~Jb~?~f}~H}{{{{{{{ȵ`}̄|x|{M}P}ʽ||M}Y}6_~G~x1Z~x.\~|67P~M~O~{3Y~T~4H~X~A~~I}r}A~z}^}|a}ܤ|n|ڃ|׹{m|}||چ|j}~|N}q|ĴY}±J~B~M}S}j}e|U}V}ŀ|z|Ķz|l|h|{a|`|y|{q|{{ַ{{{{`~i|q?}m;}Ưm|>}T}X}܍|õz||w||I}ʁ|{{{y>}ze}ӓ{P|{{Q~|{ц|{{|E}z{{{{{{{ø{ك|x{ɰ{{{š{{{E}i|O}k|ٞ{X}݅|{||H}p|҄{ŻS}Q|e{j}k~a~a}a~M~?~b}M~\|>~U~V}v}L~s}R~n~b~J~D~D}j||S}=~S}E}d}i}\}r}}M~H~|BY~[~Y~=\HE]~b~F~~}ür}}}||||||||w|{ǥ|}J~R~Y~ɿ]~\~h~ɼ\~U~h~r}||f}~Y}{||T~Q~u}[~{;~D~u}l}i}G~D~B~^~=~||M~v}||ߨ||}|d}u}J~C~m}_}||փ}}}ǼY~Z~[~W~^~J~I~{?~G}Ӈ|r}U}E~k}|r|ʿg}y>~y}V~D~I~ºk}o}@~̿V~d}Y|D~Y}^}M~O~z?~{?~I~k}`}v|c}q}}|S~i}f}x}x|j}x}w}N~f}h}Q}W}l}]}X~S}w;~k}X}R}l}?~c}}@~X}ƽm}P~w}~_}ʁ}i}s}N~||}z}ڭ|j}^~\~r}o}I~s}z}e}M~p}^}U~}J~g~X~žu}p}J~ºj}y}b}|{||z|Ɨ||{{|[}S~_~w}L~S~s}ۇ}p}j}֡|Y}~T}`}}s}ܭ||}i}k}N~W~|t}S}P}^}y}w}~T}_}w}|žs}[~q}n}{}Q~}S~O~~I~}M~L~X~S~y}m}k}ߩ|ɯ|||Ȯ|||v}p}т}ľ}}l}±|¼w}w}y}}t}|Y}]}||{}b}b}}M~Q~}{}q}̀}}t}~}p}m}}Ԉ}T~l~ʘ}P~k~n~[~ߒ}ք}~}r}v}ӕ|S}ܠ|ƺn}\~}}X~|}ي}d}y}y}p}}}z}}y}|||}}|||ˤ|Ԃ}\~V~Y~}{K~x}o}ƾv}~}}n}d}}€}}̄}v}{}͆}V~u}n}}}S~w}w}Žq}]}k}ހ}ûq}΂}o}j}}N~Ӏ}O~ڎ}|}}p}|}͉}Q~b~W~K~¶^~T~މ}}}d~_~}e}|u}}}}w}u}u}͆}ӆ}Ջ}‡}ٓ}W~}s}}m}s}}N~^~Ƽj~s~ʾi~g~g~f~h~l~_~}P~֋}ސ}Պ}U~]~g~Y~ُ}V~[~U~}O~`~Y~ߓ}x}q}}ý}{}X~V~}}}V~|}}ٍ}Չ}Q~|~|J~߁}x}Z}p}[~}]}v}}l}ڴ|ӯ||á|Ϧ|֯|ݺ|ܽ|ì||o}x}ۓ}T~q}Ľv}\}}b~]~~~~|~LGr~j~Ǿc~}g}M~}C~g~}ہ}J~z}_}d}|ڃ}w}}ʒ|g}Z}c}l}{}W}S~c~S~}d}i}{}d}]~H~Ă}g}|u}e}֭|k}X}|կ|m}|}q}|`}d}|բ|ș||Ǡ||}ێ}W}||y}}}}H~~}{>~b~M~ú^~h~i~j~Ã}a}[}}q}؅}}ȩ|{{|||{P}Y}ڃ}|l}}|t}|}l}|}y}Ѐ}W}S}ф}[}y}u}Y~t}J}ю|o|¤|||||||¡|b}||ھ|v}}z}ȕ}|||}p~Q~D~g}Ԉ}G~n~{?~~}V~K~O~B~a}c};~ȼu|z|ҭ{~|R}M}|p|_|µZ};]}a}:~>~ѻU}?~V~ŸW~m}Ƹe}n}Ԍ|ٗ|f|`|{|{{{{{{|z||{{||{{||||||||||}||||||||||||i}z}Z~r~W~]~f~l~U~|h}N~}|ݢ|H~q~^~n}g}P}Y}o|ѫ{x|з|}||κ|ؘ}s}^~͐}}x~Ci~Jn~v}}V~K~q}l}Y~i~J~d}|ȳ|[~U~W~F~ӂ}Z~Ľg~`~[~\~h~s}j}F~d}2~V~L~B~B~@~B~V}q}@~[~O~_}wB};~g}Y}|y<~b}V}َ||j||w|x=~^}¶a}h}M~S~ǽZ~V~ʿQ~D~;~9~Z}^}D}L}I}}{z{H}X|:}C}`|X|w|G|ى{v{zzu{a{zzz{a|z||b|h|h|վ{{{{t|֒||z|{{o|Ҋ||j|U}̾^}^}K~UĸY~]~7G~z:~~|{{{{{O}K~?~K}~?~a}`}g|߫{߄|[}zF}ͩ{ݲ{{|s|`}L~P~R~U~Y~P~H~öG~K~.22h~}l~}n~|}s|i|||{Ϩ||܍|w;~||Ķt|||;~J~z9~Q~؈|~}P}{N}]}|~|{}j|yc|i||y||l|ң{ȗ{{{{|e~wY|K}t<}G}E}U}S}σ|³r|݇|{D}m|I}j|ɽ{{ý{V}z_}m|[|{Έ|J}üj}ʆ|~M}{{||z{|{{{{}~J}I}{{}y{{{{|Ђ||L}ԕ|{{K}L}Y}A~Y}@}Z}F}}{c}k~e~7~ĿZ}F~V|?~Z}N}S}<}p~;~T~?L~5D~\}B~N}F~O}~|R~p~{~A~n}M}n}|}=~i~G~Kz<a~f~FºU<H_Cp~~J~y}~}h}j}r}||||||||R~_~r~f~N~R~d~R~J~||]}||{|ܢ|W~Ԃ}@~E~J~T~Y}]};~C~ǴL~X}±D~D~S}g}A~C~X}S}c}~?~C~?~H~t}R~ʾd}||ϵ{|t}|L~u0H~<~Q~f~J~ºn}z}¹n}A~h}·`}L~D~K~l}P}N~{8ǻY~G~I~E~O~_}ӌ|ߚ|s|S}j}H~I~\}B~>~x9~}L}U}Ϳg}Z}D~f}|:~X~~@~|Z}P}b}d}|\}N}R}R}V}r}\}I~H~V}N}{}P}|E~Y}|x}b}]}}L~}T~|u}|Η}ܰ|U~@}|P~{}S}||||͐|o}f~I~_}i}S~Ž]~G~}<~]}W}d}V}{{x|t}ғ|{{{}}D~>~u}L~}c~u}w}c}s}|m}K~m}e}e}|[}a}}L~{}f}R}L~O~M~K~}}|x}U~O~}S~M~b}U~Y~O~}V~}E~R~\~^~}}N~z}̈́}z}i}ɴ||||n}S~O~҄}،}l}|}f}J~m}o}a}l}`}b}n}|}\}q}m}G~V~ȁ}{}x}Q~}p}~h}̈}}Y~v}}ҏ}}}}r}||{||{{s|O}p}S~žw}l}w}k}|{{ؔ|t}~M~ٌ}Q~ȉ}}o}}}}v}t}l}n}k}p}d}[}}Žh~Ļg~ĺd~|F~e}l}|}Y~ӄ}ǂ}}ي}}֐}}}i}֢|}M~{F~O~~}H~}݀}{}}^}|W}Ά}|}s}r}ۅ}}u}e}ք}K~I~ǽs}ي}|o}y}Ł}{}}g}f}d}g}L~\~΀}r}V~]~O~]~Z~[~}˓}‹}^~W~}U~ž}U~Q~W~R~ùi~y~b~f~j~Q~U~~O~}|ٶ|g}Ņ}y}þ}ŏ}x}}M~}ފ}P~Ҋ}}}}M~X~P~R~W~z}ѐ}ȏ}}Ċ}o}o}|u}s}Ł}ɂ}|l}j}}O~ڊ}}p}^}||||||~|||||||ɱ|||Š|m}~K~¸b~H~H~R~W~¹i~żj~|K~ߩ|g}݀}f}r}j}}~H~b}|xS}|˿q}ǻl}|W}|w|n|||a}\}h}{}|v}\}W}j}|m}}Q~S~k}l}a}[}]}~}u}ٖ|ǿu}k}m}g}|}z}q}ޫ|b}q}b}ߦ||d}w}i}]}|W}~}|?~ľg}V}N}f}b~F~:~>~e}|H~g}z~a~c}||||{{{{z{||g}S~Ɔ}d}}y}}l}P~҄}|\}x}|F~s}m}S~O~S~U}_}Z}ʉ}|{|||ي}Ĭ|q}}r}|}K~w}z}W~u}|}ݑ}Չ}]~}a~`~|}BH~S~_~|9̿V~ĸS~G~~I}x|}}|{|L}l}F~@~~|U}l}j}{9~m};~~6~N}S~8~S}zY|s|ž{{{{{{|{{nq|{{{{{{{{{{{y{{{{||||z||||||||}|||}}y}N~b~u~~}v~ľn~¼}}G¾p~}}|}}E~o}g~|~T~|}}||W}|w}y||}|I~q}|}o}˷|y}}a}m}^~Gr~p~x~Z~}x~`~ړ}j}z}]~]~u}}~}Ȇ}O~g~_~{}}V~P~ż]~Ƚ\~k~}D~{}K~g~@~]~÷I~;~C~D~w9~>~t}~9~|8~D~S}T}c}r|Q}d}V}\}|N}S}E~ڋ||M}z>~}>~e}`}~B~G~>~J~:~<~H~=~:~F}J}h}F}{h{]|v|i|g|S|Y|G}H|a|d{zS{{{{T|~{˿{{{|{{{{{t||||}|||{ȹ{ɳ{~h|{v|U}j}|ۅ|f|{ȼ||v}v|y|j}i}|{{{{{{~M}J~Ȅ|_}|̿|{ɿ{v|||C~O~j}`}i|N}k}l}dzJ~P~?~>~C~ųJ~J~,3K~Cߎ}}U}}|Ǽ|{͸{{{t|˿|~|}J}U}{|Ȼ|U};~c}O~H~|@~ż|Ƚ|}|x|{|j|n|n|l|w|W}Đ|V}|[~˨{ȯ{d~|Ҫ{T}O}A}̵V}°Y}H}^|u|||z_|~|ї{c|{v|m}û{`}S}g|ƿ{|Y}}F}ţ{N}K}zT|o|{{G}k|u||u|U~D~f|{z{ʻz{j}K}P}p|p|n|[}k}|=~}|\}V}@~[|L}b}X~:~E}I~H~D~]}^}<~H}F}_}T~@~L~=~T~o}x~I~o}R}½R}9~`}J~W~:~;~S}b}ü[};~e~W~^~p~Bi~QA}9KYJs~}}{}`}܇}c}Ǯ|`}|||{||Ӂ}Z~<P~F~o~c}|||y|ə|[}{}||Y~k~}}S}إ|t}~>~[}@~=~?~v6~οX}9~z9~@~T~?~=~E~A~B~L~ǷZ}e}Z}L~|}?~v|ʃ||M~]~e~y3;~H~X~Z~ŷc}J~V}|}H~W}c}W~w2f~BɼR~]~^}Z}B~{{Y|`}J~N}}|||E~B~E~N~I~@~ĸV~w}^}ƺd}D~n}j}W}p}c}c~R~i}{}{{{<~q}Ͽ^}M}{}|ɼ`}=~²M~Z~L}}R~u}`}X~g}|||˲|L~N~}Z~V~ʇ}a~T~]~|G~}}˂}|||||Ŀ|k}F~e}n}M~H~G~:b}{~|[}C~}k}|c}|{|{o|k}X}Q~H~H~p}}}y}~}k}f}p}|}s}_}ذ|p}͐||T~U~}}p}m}J~]~M~K~J~N~|}}Q~N~o}z}׃}p}|a}J~W~}R~N~ш}|J~v}|^~j}t}||||w}P~~G~s}ګ|k}a}\}]}ҥ|f}͗|՞|j}X}|O~v}z;~T}|=~M~J~}}Ȇ}Т|q}}j}[}}=~^}}q}|`}g}l}ϱ|z}|||||{|˝|]}_}||Ȟ||j}}`}z}ք}N~P~f~ľn~a~Z~\~}}zG~P~yF~}ρ}yG~ց}v}ك}|}ɾs}H~}}J~Y~I~}s}΅}»v}Ђ}o}g}X}ɕ|~U}]}ۤ|ҩ|z}|a}ϡ|^}c}f}|֯|ݘ|d}a}}U}Җ|ڟ||{{{{|||p}ܐ}}K~}σ}}ކ}X~}ۉ}m}ߋ}}}҆}}N~Ӌ}}}W~}¼}Q~Վ}S~e~^~Y~JIy~w~g~O~U~ɂ}r}|}}ғ}Ĉ}ܞ}}}k}؆}v}|}}K~Ä}}}}Q~z}q}S~u}}|T~l~~c~a~q~}}v}q}}}r}ł}n}^}p}z}|Ş|||||||||||f~z~k~}Y~h~V~}T~N~n}}f}ʃ}}b~z~b~À}|ɾ|M~V~L~|A~ہ}b}|ө|Ԣ|Ԁ}˾r}yD~s}i}`}}j|q|{{{{|x|{{{w|_}̡|o}͚|||||||m}|}|i}Z}|Я||֧|ۭ||ùs}}ɿv}y}}u}_}l}|||œ|Y}f}P}U}[}L~Z~y<~a}^}Q}W}Y}ȑ|Z}||{{{{|y{|{{{|b}j}W~s}t}~_}Y~}f}|}u}}Y}l}l}½i}q}n}}r}c}m}g}}|Ȫ|v}}i}||ج|l}|r}}V~v}|q}h}א}{J~[~]~T~|}^}}m~ù]~K~U~_~M~L~f}g}[}k}]}w}L~R~5?~[}y9~W}Q}ǵ[}H}?~M}D}y|^|p|J}c| {c|G}n|{{{tv|{{tv|{{~|{|~|yy|{{{{{||z{||{{{|||||||||w}|k}|}}f~m~b~u~Y~¼g~Ľe~¾}}t}f~s~Q~~~r}|||||L~}ˌ}}a~}Q~~}w}r}|||||}~V~X~w~x~{~v~~~\~ڹ|[~Ą}Z~w}c~V~Љ}m~V~h~}}Q~Q~H~L~Q~ĸU~´O~y}J~j~_~~74v1N~C~@~=~a}f}A~ųY}}C}|˻U}^}E}a|L}a}ɼ{|Έ|T}_}v}X}v}e}P~T}~<~:~V}[}h}8~_}B~G}q|e}R}M}A}[{ľX|ƿY|m{Q||?|Q|D|r{b{v{h{m|{ߑ{z{{{{{{{{{{{{|Օ|v||p|Ӯ{ɨ{v|M}S}O~k}d}Z}o|s||V}g}?~E~k}|pp|{{{r|µ||M}k}s|{{|h}A~Њ|Q}[}A~H~E~f}8K~B~@~B~@~D~[~ǵL~E~7~G~u5~O}y>~q|ӎ|r|c|t|{{{l|m|Ԏ|U}xJ}Ņ|Q}U}m}ǵm|b}_~x}S~c}ݏ|ŏ|w||m|v|l|k|w|Ľ_~¸|]}{d~o|d|Q~{Я{u}M}J}y3~e}_|zW|\|r|o|i|l|{b|{|u}}a|V}j}[}|}J}I~N}޷{s}X|_|Y|zz\|V}{ͻ{|[}L~o|{{{z{P}K~B~h}~I}P}F~Q~]|C~{n|J}r}a}t}B~P}^}V};~`~?~A}R}_}k|T~B}\~M}Q}W}S}]}DG~V}Y}O~b}e~M}n~V~A~V}]}]~H~M~N~^~V~o~?Gr~FLXBb~]~S~F~g~U~T~̄}|||{{|l}^~a~c~G~J~|{|||{{v|Ľ|J~}^~{}Ӏ}\}}a}`~O~@~v}i}h}=~m~ʿc~[~M~b~9ƶJ~?~h|ׁ|e}W}}H}g}{ȼj}f~a~Z~j~=@EB~u.3X~O}<~ƷX}d}<~ôH~x9~ͻP~S~>@7K~t}F~e}Żc}P}~\|Q}|<~}}E~[}[}~}p}|B~J~p}O~|n}z}y}s}q}E~|D~r}m}|{|{[}O~q}v|m|V}ùV~V~z/P~b}e}z}r}v}J~t}||{|}L~k~X~a~V~ْ}[~n~V~^~e~b~W~S~}ܟ}}}l}b~}|T}X~C~||||y|[}H~k}m}H~||||{{Ӑ||E~}܂}X~~B~}}}|w}۝|t}M~}}a}a}_}v}f}Ѐ}|ә|M~}~L~M~||||}O~X~}a}H~|}{}}S~p}s|}Q}B~H~K~R~}O~M~ޓ}Ӈ}t}_}||||x}zD~ł}~}Ŀ}͊}y}w}]}d}[}[}^}R}~b|{a}H~B~H~zQ}Ÿg}L~}ߕ}ܯ|ǻl}r}a}t}|}ƻr}W~}~Z}n}ގ}݌}Ѝ}v}||||||i}||޲||||]}[}_}o}ׄ}út}|Q}Z}q}p}r}e}R}v}ܝ|zA~w}p}~W}Y}d}X}U}\}m}}}\}Z}o}q}Ո}m}[}Ǽu}J~|}}}}j}Ļu}}P~k}˂}o}\}k}|e}H~L~}ӳ||{{{{{{{|ܢ|k}k}~_}o}r}Ӆ}ڈ}}}}}z}e}΂}g~R~g}N~˂}}̂}~N~P~}ʇ}}P~\~b~}O~}̉}΋}j}j}ΐ}l}̇}}ܑ}r}}||}È}͏}q}z}a}Ų|r}w}x}݇}}ё}n}č}ԇ}Ј}ļz}|m}¾}p~_~}ύ}~}}X~^~|~e~|R~b~}~d~π}՝|_}͟||||ǻ|||}s~~~}}^Pǿn~م}Ƅ}}ۚ}}ʘ}֟}o}|R~l~h}|i}|_}L~v~g~i}͛|}|~z||}|{||{»|||r|{{ǹ{{{ø{¿{}p|{{{{j}]}ۀ}|{{{|||թ|ǟ||{e}ܫ|}|ŋ}_}i}|c}t}d}^}g}q}u}i}_}^}|f}c}żj}\~L~G~I~|D~X}˒|{~|{{{{||||||{{{|xz|||}|f}k}~H~ʨ|||u|p|z}Z}m}˙||c}R}|r||ؖ|h}V~]~|}}}b}|k}|؏}e~߁}}\~V~ԏ}m}ȏ}`~p}x}Ã}V~l~}K~ч}ч}T~W~}O~J~P~ȿ^~T~Q}D~b}l}E~F~t}c~`~v1G~wF}{K}R}у|g}\}\}F}yW|އ|d}_}Z|G}S}^}`}j|m|{{{{{{{{{{{|||||tt|||}|}||{||wy|||||xz||||||}ە}P~|z}||Q~k~p~b~~]~ċ}g~u~u~e~z~l~}z}||||r}`~}`~e~~|||}||}}||||À}P~s~s~v~d~|~U~]~\~ؒ}o}_~R~U~r~d~h~n~E~z}M~S~M~P~ͽT~`~B~yA~̿i}b~f~?DF`~[~A~F~D~A~@~@~e|f|S}A~M}`|l|e|o|yF}U}Z}G~ǽd}z}u};~>~a}`}b}¼V}A~;~Q}8~L}:~F}O}`|Y|A|^|B|^{F|t{K|j{_{z\{l{{ɓzɛzY{z{{{{|~|{{{{z{|||||||}=~G~]~C~@~K~{}g}N}<~wF}^}t}|{{{{{[}³{|e~ʹP~H~I~z}U~_}r}g}S}C~ĶZ}ƶN~y|y;~?~ȶJ~I~>~:~z9~Q~J~C~h}f}U}ل|{|ƃ|{{v|o|{{{Ʋ{u|Ќ||x|w|ݍ|I}c}g}B~X~L~T}j~ԉ|Α|ȿ||Ŋ|{e|ʅ|ك|{|=P}ô`}z|[~I}t|C~t|݀|E~T}L}ôJ~^}{|H}||g|p||{{p|{|V}|U}A~ļg}{||i}L}q}J}m}~|\|zȕ{~Y|e}j|о{^}h}}}·|a|_|ݹ{t|S}|I~e~o}c}v}b}M~B}V}i|~{u}U~V|y|R}h|W}V}<~d}q|P}A}=}E}U|M}C~}3~}7~B~Q~FQ~g}@~<}=~W};~e}t}O~C~Q}m}^~~<~O~O~^~Y~c~_~>DCAGl~l}C~Z~Y~S~S~I~e}`}|||{{|{b}[~ǿY~c~V~G~ϗ|{{{|u}L~?~{}J~f~t}ù\~w}c~l~ȹU~Q}o}D~ԿR~g}b}M~a~}C~t}X~a~g~K~j}R}J}g|n||Z}]}G~\BYDX˺G{39~`~Y~{9~W}T}v6~?~I~M~Q}S~e~a~C~ǺT~~@~W}P~?~w}z}d}`}b|K}L}T}|M};~U}e}a}F~|Z}@~E~n}N~~M~\~W~ջ|{j|x|H}E~A~@~Z|^}G~R~ͿT~F~[}o}a~X}|ɼW~u}j}`}^}|{||w}y}u~~}k}φ}n~g~S~úb~ûe~W~R~U~}Y~U~R~x}݇}ʊ}}u}^}A~W}{{x||Y}w}|t}}h}|׋}}_}|{z|ܖ||P~}i}}d}a}m}r}|}L~{G~t}}Q~þ|}}r}}K~`}y}||w}q}}J~h~̿[~j}|t}`}ـ}l}v}|V}|}G~{C~@~H~r}w}p}|}x}n}|||X}i}M~f}x}΄}}r}}|}x}]}қ|i}P~|}ļu}[}|ʻc}w;~X}E~x}\}ĸj}g}z}E~~?~^}|ߒ}}ˊ}z}}و}}P~w}s}||||||r}Ŷ|ƶ||n}|П|||||{|}|||||Ɣ|ƾ||||||||||||||t||úq}}Q~S~ƺ^~o}n}^}m}·r}`}l}|e}ب|||Ƽw}}ڀ}\}t}zH~e}k}ɼs}L~}L~K~l}|Э|||Z}^}h}}n}w}ډ}}J~ׁ}_}|e}c}ě|_}n}o}~g}}d~q~W~P~}V~Q~}ۊ}P~φ}„}}y}i}m}}j}n}|}u}p}|e}ӳ|f}|||p}˃}u}m}}}n}ۊ}O~f}ח}‚}ǃ}i}Ղ}۰|p}{H~ڋ}p~k~~Ն~N~MV׆~MM~~|E}м|Ť|||||||||}~Ă~v~Rx~Pp~ٳ||õ|||p}ע}{}~Y~l}}H~Z}|S}t}U~n}܀}||{|Z}Ü|~|x||{||||{{ǽ{{{{{{{{{|{ż{{{y|v|{||||}||ˣ|ߦ||e}w}^}{}||k}h}Q}݌|º||i}|||X}h}~Y}|}O~D~|q|r|×|ұ{{{{{||||{f}||{|||||||||j}e}n}m}׃}i}{|||\}c}ܢ|‚|]}{|]}}}|a}[}u}c}||̄}N~žs}q}f}H~}}Á}m}|~}͌}}a~c~Z~}M~}}_}}L~|}}}|}H~|A~`~L~N~L~`~X~G~J~T}t|}||x|j}´k|z\|f|k|{m|L}}7~z4~B~r3~N}|s|vw|{{{{{{{|z||||||||||pq|||||||||||̑}Z~Y~o~k~||_~}ԓ}ߓ}j~\~Z~¼}۞}}u~Y~c~|@_~O||{||t}վ|ԃ~x~Jѕ}_~w}ō}||||||ʌ}h~s}k}c~L\~h~}}U~V~o}}}~}}}{}o~Q~z~e~}e}jy}]}yB~C~X~W~ѾO~57=;8K~N~;~]}Q~f}}?}`}z9~v}[}m|m|a}C~f}M~|=~k}c}m}A~B~9~k}_}P}d}A~9~Z}S|O|?}c}K}D}Z|9|Q{c{̈́zO{zČzzzzzh{ф{Q|{d|~H|f||{{{{{{{{{{{{{{|{|p}ǻ\~Y~J~a~C~A~U}µa}۔|]}p|݅||q|{{{{|R}|{~j|E~M~J~Y~`~X~v}K}U}B~<~@~k}Ƚ||i}q}m}Z}|n|ǻu|}<~V~=~Ʒ`}|{r|u|{{{{{{||}[}Z}ט|Ź|yP}vH}|7~x6~V~кV}W~Y~D~o~zA~ȏ||ɾy|r||ɾ`}Y}{|j~|>~ŲU}<~y7~h}N}F~h}>\}ïZ}?~>~P}b|X|b|t|g|G}Ľ{~{\|~{{||_}g}z:~|Z}u}s|f}C~f|Z}j|Ő{Ɓ|{D}ĸV}ž{R}D~_}|c|n|Â|m}e}\}O~V~Q}P~u}^|f}<~\}a|K|c~R}T}U}I|z}X~k~g}i|`|H~F}B}f|Q}>~S~k~`}_}^~m~G}q}~V}o}k}P}]~d~Q~üV~}1~żP~[~m~6~ĿR~G~u~^~\~?};m~d~k~y~g~>:l~}~}s}{H~K~{}|{|{|}r}B~}>~I~L~J~q|{y|j}t~j~J~N~t~}S~K~~G~}d~՟|}};w1ɻ`}G}Y~[~y|X~A{@Y~H~n}J~~}K~x}m}M~o~]MNeZW~G~_~7b~Q~ے|E~T}P}L~H~?~F}^}B~p}L~J~s}m}B~ǸR~H~Q~F~}}w}b}~||S}9~>~|=~w}o|Z}n}g}źi}Ĺf}}w}b}~}I~k}u}=~wF}>~?~=~G~f}|D~v}\~Y~L~l}[}l}{|t}~<~ِ|Ƕ]}J~e}|||||~|L~d~|}h}{}e~~C~A~h~b~l~}U~k}}}s}v~g}||u}E~O~c}X}||X}`}}N}L~M~_~c}m}r}|}]}f}Z}T~|y}p}`}v}l}_}w}]~K~{}d}I~q}f}}I~o}u}͂}e}Y}H~}f}҂}S~H~K~l~J~}M~`}d}h}j}^}i}f}b}_}u}M~E~O~zA~q}~}m}|ˋ}n}|||v|i}ſw}k}Z}|\}~Z}}z}`}Y}Y}|S}|źj}g}R}}K}Ÿa}m}m}]}h}d}q}E~L~ôd}~[}r}g}ɋ}P~|}}̈́}|}y}~}j}u}x}`}||t}u}z}d}y}}~L~y}}M~˂}}І}w}]}|f}ƻ|z}||||||ѹ||ؕ}||d}||ˆ}^}̱|`}O~K~}a}J~̓}r}߫|l}j}X}Z}e}j}O~P~P~{H~P~m}Y~X~S~R~}n}|}l}||{|p}e}||||`}|ֻ|~f}d}|||||~g}S~^~]~`~j~O~P~P~V~[~a~V~z}o}x}̱|a}g}}Á}s}q}}b}V~j~o~Y~S~u}˃}W~ݿ|½}r}S~W~}w}s}†}p}}M~P~_~V~ö]~ƻd~x~{APRBs~{>i~GO~Fýr~Z~}d~}}|ż||||ӿ|~U~~Xge`Ig~r~[~ՙ}j}n}V~v}t}S~R~m~Gv}ϊ|ԋ||H~Z}I~|R}{q|·{{||{{ǹ{{{{{{{{{{{{{{|||{}|{{{{{{{{{t|||||]}Z}||Y}|||||q}}W}ڠ|s|||||u|||ƨ||٘||f}ٖ|i}|^}t}f}k}|~||ۑ|||u|ѻ{{{|{|}~|||[}||||i}|m}j}|g}|{|}O~]}Ɗ|x}î|y}v}z}|k~z}l}|||v}y}r}|}Ǿr}m}|ÿo}o}j}}s}ȿu}}}Z~y}|Ņ}}}V~}[}o}p}}i}ŀ}T~l~?~[~c~<~C~S}ĸa}˾i}^}ȓ|u}V}ƙ|C~H~ȹ_}j}A~A~N};~=~U}Z}o|R}{|wz|{{|~|{xx|{|||wy||Ɍ}|{{|}U~}|}||ʎ}}}}|Ҕ}v~l}V~b~k~\~d~}ː}}ߜ}`~}g~h~}~e~t}}~}Ô}|r}||{}f~ߗ}~I~t~X~ܓ}w}IJ|{}||}X~~~|{}о|}S~k~`~_~|^~w}}~R~ӛ}~z}{}P~V~l}V~|Bj~C}P}J~´J~]~\~Y~Z~w6]~ȼJFl~>8c~z5R~j}C~^~K~H~j}a}s}P}n}y}[}{=~@~|9~S}:~V~C~9~M~F}a|X|W}Q}U};}G|G}P}Q}u|e|D|F|B|m|üzzV{fzyez{M|ýk|ƶ_|U|{z{v|א|z{{{{{ո{{op{z{~|||̚|V}K}w}T~~/7E~A~6~E~D~I~l}D~z@~Ľ|{{w|{^}b|i|{{{~|y|s|{p}Ⱥc}|||z]|v|x9~z:~t}Ǻd}t}=~^~E~Q}{I~}<~<~W}~|ؒ|z|m|{~z||s|{{||p}m}ڗ|ŏ|\}s8~k}p}ĪC~9~_~DzI~J~x}j~q|J}X}n|_|C~?~K}I~K~a}G~9~S}F~ό||};M}F}G~~<~V}Q|T|c|{ȿ{U}{z{M}{d|Z}M~G~I~U}h}G~C~R}|N}G}v|n}c|ז|{|ɽ]}M}{|N~Z}K}u|މ|؃}X}|`}B~~8}o}w}Ȍ|k}9~?}H}Q~S}E}{[|s{e~8X}P}F}]}K}e|H}M|D~1_}N~ĽV~>~9~B}~9}6~`~g}C~U}K~w~]~B~K~;~j}X~F~B~N~M~]~\~;m~Z~W~a~W~h~g~ɿV~_~T~U~t}|}}|D~}M}R}{{՘|\}G~J~?~k}|d}|ͮ{^}p~It~=~G~e}}۳|v}]~ƾ}[}f}Ǽ\~=h~W~F~T~X~b~N~G~u}U~+@~^~O~z}S~NŹWV˽Vab=m}´O~X~ɺR~;~P~J~}{}{>~w<~G~ǽg}T}[}E~L~P~x}{}]~j}G~ˊ|úq}T~e~k~}}a~E~L~N~E~ӳ||j|\}m}ғ||P}\}Y}x|O}g}};~H~W~@~;c}Q~m}`}w|x}Y~űH~d}y8~q}}L~d}t}ڬ|˾|B~ľ}c}||{||˖|a}D~F~{A~|C~q}l}H~Że~j~}N~U~{~Y~r}h}`}k}||V}~}R~i}i}L~|}׋||Ӕ|B~A~|Q~\~Y~ݩ|{||}m}O~`}|U}ϓ|b}||e}l}J~O~»c~k~}G~|D~}U~}||{q||ݟ|X}~C~w}E~l}H~U~G~_~V~~W}|}މ}b}|g}{}q}{F~K~M~G~}}փ}x}܎}׈}}|̺|ʳ||^}e}g}ʞ||c}ϫ|z}و}U}\}݄}ۑ|{||ڔ|[}ºo}D~}g}|l}r}Q}T}~>~B~D~t}½}}N~u}}}G~y}D~v}f}x}|||x}ǀ}w}̉}O~Æ}д||{|||¡|r}}w}t}u}z}|ϵ|Ƶ|}m}}Ȉ}}Ć}}v}{}}^~k~Z~q}҅}l}p}а|x}}܀}b}}P~~}i}x}s}n}p}j}w}x}s}l}Y}~|j|Q}V}\}k}r}K~ܶ|ܐ}Ή}}g}|g}{}||Ծ|n}q}v}}z}}}|r}}m}n}||ڑ}s}|Ո}M~m}ގ}}N~׆}Q~V~{}k}n}|y}DŽ}}M~g~V~U~Z~Q~P~Z~T~N~V~V~c~R~`~v~~@_~W~M~P~}H~Q~o~t}o}R~W~M~M~V~O~O~W~U~U~N~}^~l~b~v~T~~~q~|~k~w}}ȁ~Ӄ~Z~I~K{CV~p~V~Ŀ}|}j}y}~X}\}P~\~^~M~R~Ӈ|N}]}^}~M|õ`}c|Լ{{ֳ{{{{{{{{{y|{|||}}|{{|||||}|{|~|{{|{}|{{{|||||||l}۽||з||üs}p}ƚ|Z}|–||||||\}~V}||}ρ}}|S}}Q}’|||~|{ϭ|{|x|i}{{||ü|}~}ʸ||߾|||ͩ|т}a}{||||̶|||q|~|}k|¾||\}]}Ɂ}ü}}|}O~ف}|}q}w|¾|u}w}h~K~c}M~K~|¾{v|z|||e}||||}|Ń}]~ς}|w}X}|{Ψ|z}|s}U~ĹT~Y~f~]~ƷL~ǸU~a}r|X}z}Y}||A~m}ijL~M~W}t|a|W}w@}=~\}Ա{²R}z|{{{||~|{{{||||wx||||vw|qs||}|}||t}^~`~}Q~|v}ߓ}b~ā}Y~g~\~`~j~͙}}x}~}}o~[~j~r~j}|~Z~||ٔ}m}m}r}Ŀ}t~}~e~d~V~N}y}y}|Ĕ}ӏ}I[~X~}U~Fz~}|}~T~k~T~Ж}}٘}V~}Z~[~a~~Q~~Dn~}{}Š|[~H~q}H~S~F~6F@c~f~Q~b~^~|}C~N~D~y}|Q}k}c}L}a}X}Y}k}l}`}8~@~X}=}D}I}9}Q}X}P}:}V|`|:~<}c|^|D|h|O|>}H|zz|UzlzϙyݛzŹu{؍{҅{}{z}{{{^|zy{{{{{Ǽ{{zz{||}|{T}l}S}|ȹK~N~Q~?~T}V}G}[}?~{9~l}]}N}z|{\}=~X~N~5~V}d}˾d}@~y>~[}~o|||q|{|k|O}q}C~óQ~?~]~I~Q~J~M~^}~E}<~G~|>~m}ۈ|U}{}|||{{||¦||оg}|T}̺a}n}B~v;~5~T~վN~@~=~i}G~ߏ|K}v6~o||E~I~H~~;~L~x=~A~8~a}p}}M}}b~v4~8~\~йU}ϼU}t|h|Y|W|{|F}Ջ|S}K}U|x|<~H~M~J~j}=~m}<~A~Z}؈|]}\}ȿo}f}Ճ}N}x}n}_}ɿc}j|P}O}^}P}ľ}|~P}v}@~d~R}Žn}V}U}H~k}S};~[~þR}ȿV}`|Q{9}f}E}U};}@}¹L}»_|`}<}ƽN}Y~7~D9?~E~Y~]}0~U}7~A}b}:~J~h~T~_}:~=~9~>~E~i~Q~üX~P~:~4X~V~}F~L~]~Q~úY~Z~K~G~}W~I~u~\~`~6h}g}՝|b}|x}p}J~m}ݧ|`}k}b|e}؇}}Vc~I~s}|Ç}L~U~e~Q~]~?i~~?~U}S}C}C~H~D~@~q}B~\~A=~T~y~h~;ǻTYNǼO=?D~L~@~G~zB~P~d}D~C~G~H~[}r}b}Α|˾d}>~|D~g}@~D~P}`}ِ|B~ù\~Q~a}T~[~Z~X~|}ʂ}U~f~t}U}Z}]}f|U}R}ǻg}t|M}f}V}R~G~@~W}p}ϾR~B~9~@~y1@~S~Ł}|U~Z~P~a~Ї}|z}^~}j}~|||}~|v|Z}Z}]~z@~g~U~l}e}ڏ}h}d}R~n~f~u}||{s}\}m}p}O}q}m}z}U}\}|q||U}ܬ|^}T~]}`}h}f}|}~@~A~p}ٙ||m}j}o}ƾr}ހ}~}R~{H~R~ԁ}ƿ{}i~Ža~P~}u}`}ˠ||{p|x|D~p}X}{U}~@~M~R~T~X~P~T~G~~I~d}|ݢ||Z}p}U~i~~F~|Q~S~}}L~~}o}}}v}x}r}}I~{D~ֲ|^}~]}k}v}I~|S}d}zE~{}c}d}z||F~u}y}X}}C~ĵg}o}{}x}H~e~I~w}y}ûq}y}{B~H~̿p}f}|||||~}}Q~ۊ}ÿ}}r}c}`}|}d~P~||}o}z}r}|u}}z}o}{}Ć}P~ە}}}u}}F~W~W~]~N~}ō}ʆ}}g}l}x}p}}{H~q}`}t}v}}`}W}ȿs}yF~h}b}|Z}_}x}û|}w}L~|z}}}ޘ}ƃ}Ѓ}}|v}|o}v}z}z}{}{}}Ê}y}В}}v}ܻ|ա|q}v}c~ݐ}}p}r}j}ȝ|ͬ|v}Ɋ}Y~}i}~}}n}h}m}͉}}ʆ}}Մ}|}}P~}R~[~Žc~]~U~ƾi~T~K~H~}I~J~Y~Ǿn~Q~}}w}E~K~}^}y}Ӏ}r}[~d~~~}~}ſk~~ƾn~m~y~JN}~ɀ}m~M~}R~}܌}{K~̄}|}ڃ}T~P~T~}F~J~~}\}s}g}l}d}a}M}f}K~yN}{˼{{{{{{{||||||||}}|||||||||{||~||{’|{{|{{ǘ|||Z}c}|g}ە}ю}{}i}c}f}c}l}ľ~}h}o}n||x}r}{}w}}y}|̨|s}|`}ڬ|{{||փ}]}{I~|X~u}b}ݷ|ͽ||||Y}}i}S~}}||d}||y}x}}v}l}||Z}{|y}v}}~`}b}w}|d~}O~M~z}Z~t}h}z?~»l}Ɋ|Y}ϝ||{|||Ҹ||{}؋}v}h}|I~}}]}~|o}Dž}Q~Ӥ|}}ùZ~W~z;~[~H~F~A~Y}X}Ķ`}Ƽ||ķg}?~I~w1b~o}e}|އ|?~z<~X}A~s|{|{}{{rt|{~|z}|{|||||||||||||||}o}ˮ|k}t}X~@Ɋ}x}~Q~a~k}ގ}ľl~O~a~}_~ʎ}‘}ƙ}n~f~k~~R~г||}~J~}|||||}њ}}d~ؕ}Q~c~i~½o~؛}c~W~[~ٔ}]~}^~Ok~i~S~q}԰|ւ~|}c~}_~ýj~g~y~}f~~~Y~p~~Fƾa~j}|b}D~O~A[~q~Eo~K~d~ýd~M~M~}D~g}V~_}y|k|V}|T}ז||b}}G}µb}@~K~<~:~;~^}P}U}M}A}~0~O}9}C}u|1~N}z{W{T|r|W|I|T{zgzN|e{{zyzzzμzzzz{vH}{§{ɨ{˾{{ƻ{zv{{{~|{{||~}S~|?~_}d|S}8~S~8~l|j|{a}k}A~s}g}R}}|~|{:~x/0νM~S~D~~7~G~Z~S~@~y>~E~A~l}s}|=~P}D~z}B~r}?~y}A~E~A~H~_~@~=~l}|V}H}^}q}||{||||͜|p|m|}|_}T}B~ҼS~߾V}>~̱@~Z~<~D~\}\}ʺ_}~8~F~Q~m}̿S~I~X~a}O~H~U~F~z7~K}|}L}x}g}{5~[~J}H}M}ѿn|n|~T||Y|m|P}|a}J}I}Q}s|ͻY}D~<~T~f~d}?~ʿZ}v}N~z@~q}Ŏ|t}Y~g|V}G~j}o|y|c}|ýg}އ|{|N}d}c}L~`}i||\}<~m}f}W~F~D}?~M|D|@}G|P|R}P{?}@|_|F}P~S~H~M~O~X~9~Z~?Y}`}_}7~1~B}~1~U}a}p~W}<~>~B~>~6~B~J~Z~J~h~c~p~P~Q~D~K~~8W~E~Y~Q~}ǽ\~q}}h~}H~h~g}j}R~m}~}o}|O}]}ߋ||J~K~y}`}u}h~Y~\~F~}X~˾^~s~};q}~}Z~H~D~|j|.~N~49A~?~l~ǼY~Nd\~7i~{36OFE;ŹR~E~G~J~C~p}Ƽl}}}|>~G~Y}v}L~z}m}]}R}p}ûm}|}L~úY~\~|}k}V}}B~}C~d}º`~J~Y~{F~W~¼|}m}r~}k}߲|Ă}U~}o}|~|{S}L}_}w8~c}j}j}ǷK~[~E~K~R~g~P~Ѕ}x}}Y~W~S~ٌ}żr}x}w}N~Z~a}f}k}||N}[}l}K~}}}{{|N}`~ƻV~:\~q}Т|W}i}m}ȿo}^}Y}Y}S}S}b}o}g}|g}y|ܝ|Y}~R}[}_}}q}d}|z}w}y}n}w}d}||~V}x}N~R~c~p}}~}x}L~L~w}G~T}]}i}û|{Z}zH}X}ľ||q}B~b}|F~z}{F~b}}G~H~X}¼|~Z}|o}D~N~Z~}z}R~}ڌ}X~̊}~c}||o}w}|}o}u|b}v}ƿv}K~w}|h}y}u}~}f~F~X}|ҟ|d}j}}U}b}Y}Ԏ|k}||}{}u}i}}V}|ҙ||v|}|[}c}r}ˈ}}}|~]}u}]~m~t}}q}}}a~|}y}}|֋}j~֕}m}r}k}z}}Ā}q}|k}L~ľ}{}}x}ā}}[~T~t}}}T~M~P~u}{E~{F~~E~zF~~H~̨|d}ľ|j}o}[}~}L~J~U~҅}ž{}s}yI~^~`~¸c~O~e}͍}Ȍ}…}ʇ}y}}}͋}}}y}}}v}}}}}}w}}}|i}Ȯ||}}g}||f}j}ˎ}z}k}|}}R~}|ڴ|¼|}Y~y}}}I~م}|Ȩ|k}}O~T~S~׈}v}ȿw}܃}Q~K~Żr}d}a}}Q~i}\}~W}ۥ|n}|}}|m}T~h~QþjnWOHFJE~o~w~t~~w~q}|||t}ʩ|w}L~[~e~{D~|}S~]~r}i}v}e}?~q}[}T~S~w}}N}Ⱦ|m|{{{{{{{||||||||{{||||||}~||z{||||m}||_}|{k}|x}j}|d}||_}|e}ƿ|}g}||v}x}ʀ}žz}ۏ}`}W}[}͍||||ϳ||{{|{y|z|˜||p}{}R~}×|f}~J~˃}||zK~z}Y~̐}ש||~|||М||a}v}}}v}p}x}|~~||a}|zB~V}V}Y}N~x}x}}v~^~p}̉}}q~t}h}I~ƾZ~|~||{xx||||Ԅ}|g}M~}R~S~a}|Ԇ}w||n}w}ި|e}O~p~u}A~I~D~R~=~ȹU~T}f|z|ݖ|B~ϻN~N~x4~7~M~k}]}|A~~?~o|ܼ{{w|`}}||{{{|{tu||tv||||||||||||||||||͝}}}}}|S~}T~`~c~[~FT~\~t}Ķ||o~Y~f~þ|һ||m}Žg~|Ÿ||o}Ε}}`~c~y}V~i~m}P~g~Ɇ}~O~m~[~Y~d~ϕ}^~QQ~x}~N~Ӊ}|̹||y}}}}}}f~~t~}_~l~c~~f~Mc~p}}O~d}z}`~|?z?[~S~Z~N~S~}g}|Ļa}`}Í|l|`|֖|M}d}Z}||a}>~ҿR~ǶO~[~@~^}~4~I}L}I}F}5~N}b|g|v|[|R}n{x{l|q{\{i{Ǵe{Ҹq{~{F}B}zyzzzf{{͸{z_|I}Y|uR|{{pT|{{^|uI|ä{t|y}}o}}z}}R}S}J}D}_}1U~C~N~A~ķa}C~̿g}y>~Y}{k}Æ|Ƚx|T}P~v/V~L~J~G~A~I~S~X~f~N~r}D~F~O}y=~B~v}N}V~ϾQ~A~j}>~˽]}X~H~W}j}_}T};~C~d}{R}Y}|||l|w|R}@~:{2K~|8~>~t-V~t0~ϼP~ͺ\}S}A~P~<~G~_~{2~7;~{6~y4`~ϽO~U~һS}@}A}g}t}j}̺N~z8~j}L}F}zA}b|{{i|U}~|b}i}p}L~L~?~z8~>~G~òK~_~E~p}ǁ|ĺZ~V~O~G}i|N~u|K};~i}S}|9~G~k}|~|y|{B}o}xE}g}}j|O}E~`};~ŻL~5~E}h}}/S}R}E}U|8~d}O}7}=}ʾV}T}ſI~W~e~N}X~`~U}Y|;~F~U~<~8~]}=~M}]}W}l|G}g}c}@~;~;~A~F~`~m~l~~@T~T~B~W~Q~\~S~s}{;~}Ļf}c}y@~~F~N~}A~x}a~b~}I~m~J~d}Ւ|[}l|ȡ{||Z}^}M~e~p}j~|}ڠ|̛|R}V~r~h~o~|9H~Q}T}W}M~45\~O~P~~9~5_~Dl~a~ͿQ~a~V~C;{8W~v}}D~k}}~P~e~Z~N~P~@~E~_~L~[}\}Z}}H~I~F~^}[~v~ļk}|Y}n}G~E~}d}Žs}F~u}P~K~و}ۃ}{~}K~n}X~T~~}|q}{||G~?~F~B~_~3`~G~\~Y~ň}ō}Y~}e~Dž}Ѓ}֐}g~S~H~ڀ}w}V~׼|||wy|{z|M}zF}Y}c}A~L~}r|W}B~O~c~m}ƽn}o}P}\}j}U~t}x}N~b}e}o}i}|}|t}f}i}f}f}|ſ|\}h}Y}ٝ|s}Q~o}ƾx}g}y}ޣ||~|^}Q~s~¹^~f}k}J~{}F~|B~}}¶i}e}{O}||F~Ǽk}ɽk}|ע|t}{}{B~k}||k}x}o}T}s}p}|f}p}V}q}^}p}K~Z}}{G~}O~n}z}˘|||}}_}U}Z}|i}\}e}s}T~G~`}[}h}}P~R~R~~|‘|Ӎ|ƙ|||ƞ|L~{|{|~|{{||||e}k}ľ{}j}g}r}_}m}P~\~~J~N~܊}}|l}||}}Z~a~Y~`~[~d~b~W~Z~]~n}k}p}y}}v}|}w}Ľ}}p}s}R~`~`~}|}|L~ƀ}{H~}g}b}f}p}ºy}P~M~l}n}}|y|{}l}Ղ}́}}_}l}f}m}Q~}U}]}|v}j}l}}}}}}}„}}V~Æ}Ń}Ń}É}}˜}||s}||||p}|}S~}}}|L~W~P~M~}m}ˋ}}Á}}}}_~ߋ}X~žn~~^~P~q}}d}|Ѥ|||l}|}b}ɉ}є}s}̰|f}t}v}|n}y}Ѯ|z}|T~}܂~XZVdXH~y~k~w~{~v~f~~M~]~y}y}}‚}h~s~x~Gw~~X~g~g~e~V~u}T~Ⱦ[~Z~t}Z}I~|||j|{{¹{{{{{{{||||||||}}|{||~|{{}||||ئ|ɜ|ƿ||ə|ȕ||{º||^}||]}|c}l}ǃ}t}ϭ|b}|s}ǃ}|H~k}}}J~ۀ}z}}||{|^}||{|ţ||}}g}b}~P}V}||}|i}^}z}|~~||{ѱ|¦||||`}W~z}g}Y}Ĭ||||||j}Q~ɿi}l}p}U~ш}c~l~T~T~}x}x}f}Z~a~ļ^~T~Z}|ج|e}|¿|d}~R~g}̆}f~^}z}K~T}x}]}߰||N~}}|h}o}\~L~e~J~S~v4~Y~x3W}|U}B~<~a}?~{6~=~\~^~ȸV~ޔ|D~‡|{{|yL}Ͼg}_}{|{{x{|z}|||||||||||||||||||||}||}|||}OY~ƿ_Se~q}}Y~|a~|[~}Q~}|}||||w}}Rp}֙}j~n~f~}U~b~x}x~֒}c~}ýq~c~Z~}Lr~U~p~Ŀ}||||||x}[~Z~w}P~[~h~ł}@i~X~Q~~N~\~e}K~P~]~M~S~}K~V~O~K~d}{|a|f|{{d||}P}ߎ|ъ|o|{Ց{v:~_~Z~F~Z~=~[}H}O}9}3}E|Q}a}>~g|X|O|D}Z}B}H}p|T{C|I|u4}>}\}ǶZ}Ͳb|d{̻zzzzs{|{{Β{ʴc|y=}˰g|{{Ư{Ҩ{t`|c|{w[|k|}d|ۤ|R~f~O~o}M}̊||v|C}?:W~T~V~ȼT~a~M~s}x>~ƽ|_}W}{{{T}}8~r}|Y}E~{?~r}r}V}u}U}Q}m}j}õ`}x;~=~B~y7~A~>~w8~||E}V}W}l}i}A}U}_}G~F~Y}I~r||{{|_|a|L}j}6x0;~ìE~гG~S~s/A~]~b}t2~5~4L~z0:L~8μO~<~~5M~y3=~\}V}I}e}\}l}ͻM~W}y:}D}y|T|Q|`||Q}M}b}y|M}żd}U~L~O~B~<~Z~W~C~F~l}T}]~X}Y~ƿm}r}q}}D~l}t|:~`}z4~`~>~||K~^}=~u}N}_}r}¼u||T~H~U~J~O~L}:~N~l}o}<~S}A}X}}3~~6}Q}8}F~=~úN~j~{5~ĻO~O~1~R|6}C~M~D~g}3~?~6}:~S}V}H}X}]}T}X}]}J}M~b~?a~k~G~D~z?~c~L~8~N~y}g}j}q}]}J~˽h}k}q}Žt}}f~}p}G~V~f~r}F~t}ӂ|p|؈||N}m}}C~y}_}|֤|[}O~c~÷T~ɿ^~{H~c}|d}f}H~'<(A~U~N~a~O~}`~n~~=A:K~f~d~L~@~}W~h~[~~K=J~C~}H~v}}]~{}y}P~n}L~F~p}T~¶e}~A~r}k}Q~]}t~l}|\}M~p}x}J~}N~M~k~Z~W~}|||}s}ׅ}}K~V}ŴM~M~Y~_~c~J~Z~]~M~v}|}}}ȃ}l}[}H~}v}X}[}|O~}Ǿ||||u|~t|Ë|i}ǹc}D~I~a~т}L~b~h~G~b}O~|S}|ə|T~y}x}Q~}j}}L~އ}Ҍ}s}r}Z~z}Ϻ||V}c}i}c}}}x}L~h}a}̓|T~J~||\}m}b~S~O~Y~O~U}K~w}zA~s}G~q}y}x}׎||i}ȿk}P}`}V}f}q}u}i}^}\}]}u}e}M~n}~^}ң|d}||ȼm}z}^}W}O}Q}ùm}`}|W}G~}U}Y}Y}}m}O}d}d}Y}_}Ǐ|^}|}|}J~c}||c}w}}}{}Q~T~]}||||k}f}e}y}}¾||||x}x}g}Ž|g}ǽq}p}o}Ԅ}¼u}K~Y~}}K~}}|út}|||||֐}υ}υ}ˆ}z}k}|}}O~}ʇ}e}}}}|a}ğ|v|ϣ|_}t}ׄ}}N~|J~ف}܄}N~W~P~P~W~p}߆}|M~ݢ|U~W~}E~N~}ۈ}úy}}\}|z|}|i|[}t}Ŋ}}}Ď}ה}}m}Î}}~}ʎ}ύ}}}u}ԍ}Ϗ}}x}S~X~Z~}Œ}|x}ˈ}܎}y}ˁ}U~X~N~Ւ}R~^~}ڊ}ۊ}}O~P~c~^~t}s}N~w}z}|Չ}~Q~q}|ǿ|ǹ|g}}}b}n}M~P~yF~O~ɏ}}}}U~~}q}}Ì}~RVN}G~G`~Ȍ}f~~~h~\~\~}_~ň}~}ľ}p~[~Y~Z~O~\~C~C~~i~Z~S~W~N~l~A~v}Ⱦ\~R~~>~`}d|z\|{{{{{{{{|||||||||}}|~||||}|||||||b}f}Z}m}f}̓}s}ٰ|^}W}zS}u}|ĭ|||]}|c}b}||ٳ|t}~}~}v}g}s}v}{}}c}x}||u}||||Σ|y}Ӑ}|j}p}Ŀ|բ||E}V}|b~}Ŀ||Ł}}b}||||||}}_}c}_}||||||g}|m}`}y}c}P~F~c}o~}[~Ç}}U~ʌ}X~̈}h~ƽb~}h}W}~|t}޸|||M~Ս}۩|\~}Ϛ|ɞ|}||p}f}À}W~t}Z~}K~`}ſ}ſ|Q~\~9T~B~y2:~Q}ߑ|T}¯K~ɷN~{9~S}Y~F~b~E~A~}}s}|{[}`}M}X}s|{{{ln|||uw||||vy||||||||||||||||{|r}||||}Nw~|~}}}U~w}|}}}}{}n}|о||}^~ݸ|a~ƒ}Y~t}n~X~^~Z~`~}_~^~Z~p~|~~}|Le~d~w}|ۿ|||||||||{}z}R~j~W~G~[~|ź|h||o}w}d}Γ||P~_}Q}S}Έ|}M}N}m|{{޻{v|ݼ{݊|Ջ|}|h|{d|K}~=~ʿQ~@~b}>~R~T}h|8}F|Y|T|M}Z~N}o|C}U|8}N|F|E}Y|P}B~i|{:}K}y8~E~˳Y}A}۾m|e{z|p{{{O|i|V|Z|~V|{ذ{{e|{{vQ|W|Z|ë{|^}J~q}ߐ|¸f}^}||^}D~D~q}K~ɾS~O~d~|6øU~I~L~w}ǻc}o}΍|{{{{{{d}`}W}K~B~@~K~E~{<~|{{{}J}܎|؄|b}x9~d|{{|{d}<~Z}|^}I~T}{{{Կ{l|i|u|h}B~͹N~H~ǯK~s-Y~~6~<˳C~S~ɮE~6~N~;L~5{-C~?~~8>~6S~A~=~A~T}D}J~R}_}>~K}G}w|y|{{{`|`}f}A~>~J}Џ{~K}μ`}D~B~o}f~M~a~F~;~W}Y~ƹb|i}x}N}Žl}D~M~e}c}ŹM~_}ƿS~i}T~{:~@~y;~{;~D~b}g}~|X}C~r}I~F~E~l}l}I~E~\}U}C~>}U}[}[}P}b}a}R~G~Z~U~M~G~2~Q}>~D~?}D~1~5~P}:}W}=~U}5}Y}R}^}9~5~`}C~Q~d~T~j~ƿW~m~Z~]~R~[}i}n}W}}J~I~L~y}a}q}h}A~}D~|T~J~R~R~y}}]}D~h|ļ|[|_|֜|Ļu}||v}\}Q~^~D~T~Ar~k}|L~E~K~D~W~H~\~T~J~|D~}t}y}M~O~Z~h~[~n~ECp~}\~Q~k~x~n~}}[}ӌ|K~O~L~I~O~{}r}l}|`}ƻe}k}|>~e}ۂ}~G~w}X}v}K~}}c}}J~G~L~Y~i}^~a~Z~܇}Dj}j}i}|ý|c}i}}}7~Q~<~N~X~=źZ~L~r}}}p}T~l}c}R~T~Z~W~S~y}ȿn}}}ʄ}Z~y}r}|}|l||V}|R}x<~p}|V~d~J~u}T}}}|U}E~b}b}A~~}e}ڥ|s}H~}ی}}K~r}|||v}g}s}Ƽl}b}g}߁}E~}O~v}n}̀}|h}i}q}c~n~U~ľq}}r}t}W~_~I~N~L~|}w}s}e}\}_}|Ȏ|c}^}f}b}[}k}^}|֜|ߞ|}G~]}z}؁}n}|ʁ}c}}L~v}ɾi}ʿm}~|ō|t}G~ƾv}f}c}K~|}w}v}p}r}r}G~~E~B~D~r}B~}}n}}G~žs}q}Ր|z;~|>~{A~X~{F~}l}|v}h}ŷ|||k}y}h}|ڮ||}||ܷ|||׬|l}o}t}o}Â}u}ʁ}}]}e}a}o}Z~}||g}|}f}j}|}݋}}r}|ؠ|~}}|}r}{}p}l}||||d}s}܂}|}|K~|}Y~V~X~S~}Y}ۖ|ʾm}Y~U~S~M~v}m}|||¿|j}{}}}Ì}u}z}}}p}}}|}e}o}ض||{}t}}ˉ}Ԑ}Ɉ}Ɔ}ڏ}ڌ}U~½}y}}Ѷ|}żf~V~s}p}y}ł}}}T~a~~}}U~}]~t}ʂ}z}|x}z}ڇ}{}{}}U~؋}Ҁ}͍}P~~}d}{M}p}E~E~J~}G~N~ɂ}¾}ݕ}}Ŀu~~Ox~u~o~~~KXD_~~~ڃ~~~Is~c~w~Lf~v~~Bm~|~~~T~R~f~c~O~c}K~P~M}u}S~L~e}O}{ʵ{{{’|||n|{{{||||||||{|||{{|||`}ɘ|u|ߦ||ȗ|s||Y}»||p}r}}`}l}y}k}w}|e}}ғ}m}e}}٘}|}{}d}»v}Z}|Ƣ||~}{ʟ|{{i}k}v}s}ʃ}l}Ϩ|ץ|b}|q}ڊ}J~g~¬||s}O~||b}|||ڍ}t}||Մ|ȿw}|`}N~Q~j}ݳ|«|j}¼}}~I~h}v}ن}N~÷W~}Y~|Q~}}}]~W~e~ɇ}}W~Q~z}F~|҄}|||ļv}I~]}||y}ɥ|Ŝ|Ƣ||}x}[~V~|~b}ց}|ϝ||g~^~e~x1ɷL~z3=~i}x;~K~D~w:~l}@~M~L~s}Y}R}ݍ|y|l|J}ǹc}пg}{J}|{t||{{{{{{uv|{||{||||||||||||||||||||}o~U~Ľi~q}͢}{}}}|T~^~}m}|r}|~}|l}|׍}|}z}W~ڌ}V~{N~R~\~ûk~}}c~Hy~w~w}|~BO~T~n~}ߨ|̜|Ψ||v}o}y^}}d}|W~x}_}ɐ||uK}ٚ||||||a}}<C~D~E~v?~]}|{{{{{v||{u|ϰ{i|u|F~L~ɾX~R~H~=~7~?}g|[|=}R}J}D}S~T}H}C}:}A}<}M}F}Q}a|I}D~K~V}[}L}ζW}ͰQ}ǭQ}u<}{V|{zxO|ܐ{x_{{{Ô{d|^|{b|{{{z{||{}d|t|{|X}H}w5~<~j}^}}>~\}s}I~h~7c~]~Z~m}g}ɐ|{{{{{{|ʏ|{̓|m}÷_}T}º|c}i}b}|ϱ{þ||t|h|{{{|{{F}V}Q}6~7~X}{{P}ׂ|r|o|k}=~F~T~{3òK~ӾU~\~s4~^}?U~`~M~X~ӷA~{-069@~8~9A~5~6<~G~v0~C}z3~D~X}ɸ\}U}|K}w|x|׋{ٞ{}\|z|ο^}A~H~=~B~l}e}I~@~@~C~Y~^~B~ƵO~Y~S~7?~=~H}w=}|P}B~N~=~U~d}c~T~H~b}U~}8~A~z}m}ûU~t}Ǻ_}L~D~O~W~X}K}A~S~E}T}g|=}m|öD}3~F~õJ~x1~\}C~U~?~V~V~H}T}~/~9~Z}e}[}B~5~4~2}F}E~W}G}2~8~A~@~c};~t}Y~D~`~m~X~e~T~R~d~X~A~I~T~­K~J~|C~J~u}`}b}e}I~m~d}L~Z~k~߂}|||ٴ|{Ե{w|̰{m|V}[|~|N~I~v~f~W~z:o~z}N~}}}A~<~C~J~M~HTR~l}j}ƾn}||g}a~GJMRa~W~{~o~b~Ѐ}u}P~V~a~Q~G~8T~H~V~^}|H~B~N~Չ|]}|B}ĶQ~}n}E~F~S}`}g}L~S~d}N~q}o}H~P~~H~}_~X~l~|{}}||P~΄|a}=~K~R~s}˾[~H~~}N~ùj}]~ÿ}}}|V~U~}T~\~}}}O~}}|u}P~j}}e~؆}g}|x}y}|}Q}U}|P~N~|S}|Q}{}ɗ|{n|`}|}÷T~Ⱦ[~}}O~ޓ}x}^}^}ނ}o}g}~^}N~f}}g}\}{P}||~X}k}}O~I~z}h}x}p}}I~R~S~J~Z~{}l}k}|}{}~}N~Q~c~K~{@~s}u}}}y?~q}Z}h}f}@~C~\}ǃ}_~]}g}h}¿~}}ƿz}d}||˟|t}b}s}E~{B~}}L~}}}D~e}y}Q~G~Ľu}d}ϝ|]}Ï|h}d~P~u}P~L~`}|d}W}Y}}y}s}C~ƻY~R~f}Ķ|μ|¼||t}||g}ٸ||zP}yA~ڵ||||}|||||p}l}}X~ڇ}O~j}|q}f}|b}փ}g}w}|ľ}c}f}|ѭ|ф}|f}||\}\}O~||||}g}x}o}؋}|f}ڊ}}w}t}|`}i}zA~K~Q~P~|[}}܂}ބ}\~n}}||q}|}r}}ˎ}}֋}}ڔ}}t}e}m}t}}u}o}o}{L~Ȉ}}Ђ}֍}~}}ъ}`~\~[~̓}ڇ}}}ʅ}S~W~}ɇ}W~Ћ}k}؏}А}܋}|}||w}}}K~e~\~g}¦|ά||||}g}}}s}}j}{W}||_}J~V~G~V~k~f~r~f~p~ROaUVX^Mg~|b}ʁ}ٟ}}}l~i~n~}}q~_~c~o~OULLHr~}O~N~F~R}~}R~N~^}|||{}^}^}\}`}t}|{o|ӕ|n}Ĺp}n}Ȼr}}^}u}q|||||װ{p|ܧ||ݠ|||Ƙ|ǝ|ģ|{O}z|~T}ľ|z||k}{}b}_}_}Ľ{}o}{|^}s}w}r}||U}e}u}Չ}}~P~g}c}[}|[}Ų|||||ӄ}o}úo}r~ѥ}Ü||zC~S~dž}ي}P~r}j}Z}||||||è|ܑ}ºt}b}||X}{|||N~˧|u}|V~h}T~|J~S~s}`}k~T~S~}||}}Ŝ}k~{~}]~M~p}Y~|ժ|̉}r}j~T~V~I~}}O~܂}{}s}G~U~F~}v}f}c}}~c}|}U~y4Q~S~D~E~˽W~M~A~I~j}x}>~~>~R~O~A~B~ߛ|m|‚|Ń|{K}e}C~O}|x|u||||{wy|{{}~|{vx||||{|||vx|||{|||||||||||||}e}W~q~[~LKS~|||ú||}|{|||}‡}||j}ņ}O~|t}P~|L~]~x~r~}r}p~»g~R~Q~Ą}~V~h~k~K~h}S}ۚ|a}}?~κZ~D~w}սk}d}`}y}Ǹw}Z}X}zN}[}W}U}ч|X}yJ}T}Y~A̹P~G~t5~Ӹ^}[}ɹ|p|{{n|{c|ǩ{{t|{m}|K}[~ŻY~ľ]~E~c}Y}M}h|F}K}`|B}R}L}S}^}M}O}<}V|c|c}R{zH{l{t||9~F~c~K~>~|5~ֵM}W}{7}i|e|ʷ{b|p|Ҭ{ȷ{{~s{||{z{}M}ߓ|Ր|ˡ{{|q||ơ||{|{u|J}ڃ|f|k|΅|V}d}D~M~=~U~Z~u}C~a}_}z:~K}{{{t||ߕ|l|{m}|P~S~\~J~^~i}q}?~{;~Ǻ\}|˻{{p|w|{{p|1~=}R}6~Q}n|{8~G~i}|f}F~пd}M~U~\~~7^~y8~b}?N~N~Z~R~}.9?s-v.X~w05N~46P~?~E~7~˹N~A~@~ðW}|{G}E}K}W}U|c|r|P}k}w}T~X}J~~9Q~w<~h}v5~P~@~ϿV~Z~A~N~[~]~I~<~ɹ\}a}E}S~E~=~d}I~u4~T~=@~A~{<~>~ù`}x}_}Y~D~K~z<~c}J~K~p}X}=~I~O}W}I}H}X}H~ȿP~U~?~Z}E~I~L}S}ҽN~>^}J}@}Y}V}5~;~<~9~\}X}?~6~b}1~^}Q}9~;~F~Z~E~h~G~@~X~[~j~R~}U~e~R~C~X~C~~}b}͈|~A~\}f}|P}j}b}|\}||||||}|o|B~f}}|}|Ԅ|؅|[}||a}|}݄}G~v}\}g}s}߄}}o}M~e}B~<q}s}}I~}l}O~a~FPa`l~f~NQK`~e~}[~f}|s}}h}R}C~»V~f}z|{v}F~t}j}G~y}Y}ľp}V~}Q}X}m}w}X~o}n}]}g}H~b}|G~z~ӂ}G~}i}|c}P~u}h}_}o}P~ߎ|új}g}W}z}òM~H~Z}k}ܓ}W~N~Q~g~~}g}}}o}Y~}U}e}t}S}p}~?~D~Q~ڏ}^~}̊}Č}S~ń}r}S}|`}|a}~?~C~|}v|{|c}E~{:i~~i}ҳ|b}||[}r}h}|öU~D~m}ǿr}d}Տ}N~]}|W}}U}h}G~~}E~s}e}ɿr}F~U~h~R~{}Q~Y~F~|`}V~}{n}G~e~˾Y~z@~y}O~G~F~I~}||o}k}e}e}\}^}t}øg}m}}C~q}|Ś|||V}ùh}j}|||t}H~}q}h}f}Žw}U~|E~}H~żl}|l}Z}q}J~z}|}W~}C~m}ԣ|K~t}}}z=~C~p}L~s}l}||f}p}}؏}r}}л|||ڢ||||||ϭ|c}|h}}a~R~p~Ƃ}r}}Œ}߾|y}ض|y}x}o}V~R~׈}m}m}~}v}d}»{}|{||b}|}a}}T~}L~}}}~M~}Ԅ}~L~|O~ڈ}d}|a}y}I~x}t}}}˅}}}ʉ}l}j}|m}}|v}}lj}Ɔ}}Ό}n}}Å}}y}j}}}M~|}s}z}|}X~^~Y~a~}ӆ}R~T~}c~j~}ю}ȁ}y}̰||J~}ؖ}þ~}}}|d}k}|ر|ƨ||z}_}^}y}|N~}}r}l}k}x}t}s}Ƃ}ȃ}s}|c}R~M~}W~{=|@EPLe~X~v~x~e~z}Ψ|n}׃}}}̖}}|д|}}^~k~q~j~~~S~|C]~J~|ʇ|ї|Y}O~E~I~f~K~S~}}G~D~K~H~݀}|}G~N~E~݊|~P}´e}i}C~C~t}}t}H~ӛ|{|{||{|`}]}˝|||Y}|Օ|Z}|o}{{|a}d}h}q}a}}||ԫ|}||׺||d}ȉ}}s}ߐ}}}t}̩|»r}g}|||p}j}o}||}_}ע|}ҙ||Χ|ԩ||x}}o}}h}m}_}|||i}||`}}T~žo~R~΀}c}y}|خ|}X~Ȅ}ڏ}ɛ|}G~ݱ|j}}|[~||}|}`~}ľu~e~Z~̶|I{}Z~u~l}ڀ}Y~x}s~Q~P~r}Y}g}z}[~R~u}n}V}ˉ||||ő}|||G~g~X~G~=~³N~N~C~R~G~H~Ͼb}H~|8~@~N~ƸS~K~ɺ_};~o|o|||v|D~|X}{P}ϙ|||{{{{{}|{{{{|{||{|rt||||||||||||||x}||W~e~V~c~GM~{~}M~||||||f}}Ï}t~h~V~g}q}||p}}j~R~b~R~w~w~}O~}}}b~}Ai~[~=~;~U}~||d}C~L~K~L~I~C~<~v:~T}V}I}ż{||Y}a}Z}Z}W}K}`}K~Z~ʷQ~e}T}־^}Ѽp|ΐ|u|{{d|ۤ{|s||J}k|]}Y}L~>~L~5~X}>}V}F}=}5}~8}^|Z}K}B}P}?}9}G|`|I|?~=}=}?}N}ͱQ}z7}E}u3~`}5~A~O}D}}?}U|lj{{}y{z{ϥ{{{{{Ͻ{{}{c|m|{{{̴{{{{{{{{{{{{t|e}_}m}C~K~C~N~c~L~[~Q~S~=~\}E~J~H~X}z<~Ȕ|]}j}Z}<~S~x}S}c}j|k|U}{w|{{~l|L}Y}H}N}R}J}L}C~c}[}I}2~X~C~I~Y~Q~C~N~R~Y~<|3J~f}a~[~A~I~5{/;6Q~K~t/Q~˺M~ȴJ~^~|2=~D~9~@~ӾK~ϼM~\~B~ʶT}I}P}p}|;~P}y=}r|O}p|ʳT}J~m}O~X~c~ǹS~K~=~B~E~>~ƶN~I~:~ƹP~̽R~e}<~[}z<~C~=O~k}ɼT~A~P~l}z6P~F~X~f}A~V}K~Z~A~G~d}V}r}J~q}E~W~B~Ⱥ\}y5~L~8~Z~T~h~K~:~7~9~|0~O~?~O~;H}[}8}f|\}9~F~7~.~3~8~<~;~V}W}U~D~A~9~A~V~g~f~q~ǿY~e~G~n~g~D~G~ȹX~e~U~F~X~O~]}]}}w}{{~|z||{{{|{ü{|||[}{{{{{{o|b}||x}}}`}ľ|t}h}Ƽq}J~\~ĸP~t}S~y}|}R~[~{}^}K~}DoŶJMJi~G}<i~H~z}|||Û|e}t}Ț|č|`~h~T}u}ɺj|{|T~~7A~|@~o}ח||}K~ˏ|d}}}|B~G~p}t}w;~H~R~P~ȼa~M~Ņ}l}||u}l}|w}G~p}y}n}m}k}O~K~P~þ|}V~}o}T~^~}F~_}l}|r|X}A~d}X};~R~I~ºv}T~k}`}S~֍}U~}m}ʼn}L~ť|{_|r}F~]~b}v|{Ԣ|}z}{}_~O~P~٬|e}h}Μ|H~}X}m}Ď|Y}|ۉ}o}]}h||U}|e}}B~y@~y}k}m}k}q}ȺW~W~Y~N~}{B~E~p}\}t}m}|~}M~I~b~L~Z~X~[~n~Y~G~{}W}Y}i}]}|m}}}h}v}[}o}{}b}{Û|w|U}}}L~P~I~}}w}g}}y}M~E~z}ļt}~}}z}r}}~D~m}z}d}zF~O~΂}Ĝ|X}V}{C~^}a}ғ||x}|G~}M~V~}r}}Ʌ}b}}l}ߐ}ۺ|c}m}v}o}|||Ь||||}Q~c~Q~ʀ}u}X~w~k~}}}}p}ǁ}O~W~W~O~M~}r}|b}}}I~yE~ǿy}}O~g}{}ˁ}N~׃}ûw}m}{}y}}T~žx}|}h}k}~}I~}F~}`~ˀ}ýz}S~ƽ}z}ſ}z}·}ˆ}ю}Ж}v}̌}}ň}t}}ɗ}i}Ј}җ}|l}`}߉}i}Ǿz}}ތ}e}|k}|u}|J~т}|}~S~v}n}Ԍ}}}ׯ|b}|j}y}v}Z~U~}O~~}¼}ɏ}}~}Օ}LJ}̑}ύ}u}}Ľy}ƿ|}z}}»y}{}r}|ýy}}ۻ|ϯ|}}j}Ԇ}ف}t}L~Q~܀}m}~}~H~Ӏ}k}h}g}|t}r}||v}l}}}}z~x~t~}Ŀ||w||k}M~zD~p}r}N~Z~M~}C~O~^}֙|w|z|{Ӿ{{|s}Ļf}T~J~}B~˿v}r}}v}p}ȿ|~}|||ѣ|n}|||||}W}|ɿu}^}f}|}|r|{{|l}|g}|ɓ|`}||ɾ||||||x}k}x}n}b}t}~b}ڇ}o}߾|}_}t}}Ŏ}|||̍}Ϭ|{}{||]}٬||n}\~}}֮|||Һ|ۈ}n}|ڏ}q}~L~}f}u}|}~g}e}ޣ|]~}v}|~L~n}l}_}|Ƥ|o~|S~}}||b~}Lܢ}Z~\~AS}m}}?ĺ[~}O~e~N~O~]~e}}g~ƾb~<~[}\}]}Y}K}s||{{||Ο|m}H~Q~I~L~e~P~_~ĵO~|;~h}r}K~X~F~`}S}J~}}³^}s||{|z||ŷ`}{[}b}ӡ||{{{{xz||st||~|||~|||{jl|{|||||||||||||||œ}؁~΢}Қ}EP~ſ}Ľi~W~g~h~|}s}|j~}X~}}Δ}Æ}ˉ}|}_~ϑ}x}}ٜ}}ń}Y~n~}y~U~Y~a~|ؑ}J~Z~n~G~@~>~H~m}S}C~p}l}u}m}C~D~o}l}:~c}|s|xJ}|}<~t}M}^}u}x}l}W}r}y7~ưW}G}J}vB}W}||޼{ϸ{q|E}J}`}|S}T}?~i}Q}S}<}X}f|i|D}Y}L}B}X}K}d|3}W|S|H}@}5}d}D}@}X}]}v5~]}]}f}V}w2~r/~W}E}s|]|{{z~{}{ʪ{έ{c|l|_|P}T}z|x|@}]|{Z|ݨ{{x\|ݧ{{{{{{{{{{|P}@~>~?~µ]}G~e~9w1^~O~u}b}|J}ی|X}^}^}||||Α|N~{6¶S~K~y<~[}j}R}V}v|[}{0~]|]}J~3~B~7~I}B~U~M~C}*A~Z~|6\~M~J~_~b~x5<;Y~A~e~\~~5r0@~U~=A~L~зL~y29H~G~I~]~C~B~°H~W~=~v.f~=j~}:~g}@~q}e}L~t}u7~ϰV}r7~G~ƵX}d}:~U~W~H~H~]~Z~L~K~ʽV~J~B~R~N}U}C~>~<EL~z}e~}7~}2E~\~L~Q~W~@~^}|>~}F~U~ĸP~i}`}p}F~_~ʾN~^~W~˾Q~C~J~D~4^~Z~O~¹E~ƿJ~N~ǶI~L~[~V~:~b~2~C}J|8}E};~;~Z}V}2~ĻK}O}3~A~R~S~Y~|4~?~N~]~b~ļ^~k~ƾ^~l~c~u~t~g~D~u}̽]~e~`}w}p}Q}֐|i}|o|{{{||||{{{{Ǹ{{{{{||{{|Ք|x}~J~Ӣ|߲|}j}ݟ|j|o|r|X}P~Kn~z3~O}|¾|M~S~O~р}ѳ||ʹ|h~fDdMYm~_~U~|V}Y}k}|}I~@~X~D~=~S}e}S}m}e}ƻb}u|G~a~\}^}z|܄}{8k}s}n}M}\}z|L~h}`}]}}n~W~K~H~ڑ}й|ʽ|g}_}}f}f}{G~ƺc}]}ڛ|ľ|W}R~\~`~E~a}|ݎ}ļg~w~V~ީ|`}w|\}\}L~[~E~F~u}^}x}}Z}d~X~]~]~֎}s}}R~n}أ|||Z}Y}{}z9K~a}ۘ|»s}¼s}d}m}]}}|N~f}`}i}C~||S~N~X~p}r}{^}l}~]}}[}l}^}k}Q}n}f}W}c}g}j}}K~E~L~E~}y}}{}ľ|_}`}U~S~D~y}R~Y~N~g~V~e}P~x}Š|ʜ|z}a}^}s}v}žs}}F~u}v}m}{=~c}|h}|}W}f}ļq}~H~M~`}ӄ}r}ڊ}S~c~}{}h}y|þ||ҥ|m}܅}w}m}i}ſ|\}[}ا||||j}i}|}|}M~d}…}ݑ}ʃ}R~}N~}Ʌ}y}P~̇}v}|j}è||e}|Ǩ|}|||ػ|k}}ʉ}P~}}H~Q~V~W~U~}}׆}ӆ}v}̈́}O~d~t~i~Y~~H~}}}J~z}m}|u}ϋ}~}Ł}}އ}}}s}k}}}o}u}}Z}a}k}L~L~]}i}}l}}}zG~م}ûx}׏}}}~M~Ȇ}u}ז}u}}}}}y}]~n}u}|k}{K~ļ{}b~O~s}ݓ}½}R~}f}m}̳||έ|~}o}s}~}k}}}w}ȑ}i}}}}‚}p}ĉ}h}}~h}}Ĉ}v}ӌ}p}‡}}q}W~o}Ȍ}}}v}x}q}}А}w}É}b~l}ͩ|||||||||||ӆ}}Ŵ||||}}x}j}T~w~f~~y~Y~L~{J~w}f}T~\~P~q}´s|F~V~xD~}}[}t|{u|Y}u}|^}k}V}Z}a}W}|W}|x||˾m}s||~g|{|||]}Ρ|Ϩ|ƽ||֚|]}k}||]}`}b}|j|w|ƿ||Λ||\}|f}]}|j}f}q}||||e}s}}n}ü}^}|ȿ|l}l}â|y}{P~||{}j}Ѯ|ܺ|g}|||s}z|}|~n}|J~ֆ}o}g}έ||~}Ջ}^}}l}|^}r}[}{}l}|r}l}֍}‚}||s}}i}b}|~}{}݂}k~}X~]~}}~f~`~|w~P~}ӏ}P~j~o~[~Y~r}}ù`~Y~}}}|Ĥ|]}E~K~׍|i||{{{v|ľ|}w}p|d}>~]~ȹT~n}k}`~S~|;~F~@~A~Z}Ă||Z}y}a}A~]}|>~`}Ɋ||{|{֎|j}{||{{{{{|||wy|{||{|||mo|||||||||||||z}|}~R~X~΋}}Í}g~Y~ºh~c~|t}h}}ÿt~_~Ӣ|}}̍}j}u}}}žl~р}j}~}Z~}o~f~g~}ۓ}}q}{}ƅ}~}J~D~U~U~G~H~E~h}A~x}S}G~ɺd}p}u<~P~<~?~>~^}`}`}k}>~J~r|l|Ǻw|̼o|T}Z}L}`}E~>~^}|ѓ|u||{{̮{[}G}V}]}Z}9~]}|l|W}h|ļ\|f|:}J}2~[}J}¾N}i|B}Y}H}>}S|f{p|F}@}C}׿\}e}9~@~Y};}r3~_}9~;~O}B}ۆ{Եz{z{{{||xL}[}L}ֵZ}׶W}I}̰X}{<~l}F}{:~T}ũU}K}̰X}ѽ{rT|t|{{z{{{{k|:~F~W~X~ɽT~t}ŶP~9W~x4d~>~z;~c}|n}Y~t}f}D~zO}X}A~=~P~y;~`~g}P}E~P~W}L~D~2~/~G~B~9~F~8~2~H~T~<~=}O}žV~L~<~G~\~7};]~;y3d~g~e~a~[~W~H~R~T~ƳJ~u177O~E~?~C~E~`~?~I~T~J~344¶T~E~I~@~r0~8~~7~t4~G~=~o}J~7~ԻX}G~G~ŹR~\~[~D~;µS~S~`~T~M~H~C~ξY}ʸV}ĵQ~q}Q~u}r}N~K~M~P~e~E~@~E~V~Y}t}F~W~J~I~B~`}A~Y~E~:~5b}S~<~e~W~W~ξO~Z~w0_~@~J~C~ǸR~:~8~A~M~5~W}f}6~B~:~C}1~.~f|B}7~\}5~I~ɽO~:~2~J~P~N~k~O~Y~w~Bx~úa~Y~{=~N~D~I~O~W~U~|R}[~}[}|{}~|{{{{{{{{{|{{}|{{{u||ǯ|b}v}|`}J~|n}||y}L~p~h}c}{T}Ԣ|z>~>?:v~U~g}r}IcB}>xJNY~I~}d}Q~`~^~S~n}v}T}|{|||\}o}T|Y}{I}ߌ|ˁ|i|`}[~I~|T}C}S}C~B~k}p}}=~c}zM}b~}Y~L~|||p}}I~}»t}S~g}B~w|\}úz|d}G~M~D~U}ց}L~z}`}|=~r}g}¹j}^}O~Ѐ}^~}K~~}]~U~U~N~{}}}|J~|g}g}Ϗ||Z}D~}}p~º\~żj}^}a}c}|s}W}d}{}^}^}Ϛ|||{@~S~K~n}~}þ|ң|t}r}b}_}r}}A~i}h}}z}I~w}y}t}y}O~O~t}»l}h}ȿt}||||[}t}|9c~r}X~s}P~U~n~g~H~Ճ}f}|}}j}k}]}|o}x}b}{{|w}t}c}{}P~S~}^}{}G~`}a}ɦ|i}}M~e}i}ۀ}_}¢||}y}r}P~|H~w}h}J~]~}|||||K~P~j~s~b~R~ш}ی}Y~S~V~}{}ބ}K~}J~n}n}o}}}x}||c}||Ǻ|l}n}|T}y|t|X}p}́}L~}I~}ӄ}ڌ}y}½z}}|}_~l~Ca~T~d~w~ȿg~k~p}d}N~S~W~M~]}w}S~}W~L~U~j~]~Z~\~]~Y~G~zC~I~Q~v}U~}ƾ|}ۂ}p}|F~}Q~Ӊ}U~}Q~r}}Ƅ}}ْ}΍}|ܘ}~j}k}Ӌ}h}~}|٘}}|ޓ}ύ}f}i}v}}}l}|v}|}¾}w}}}{}~}g}s}}}~}~}|}|}Ķ|u}w}}S~ˀ}y}|h}x}l}}}ߚ}Ċ}Ŀ}ˉ}w}|~ºe~d~_~}`}٩|̦|||||c}||κ||||{}t}}}͌}T~c}i}^~O~U~W~r~[~u}f}V~R~p}F~]~\~d}|~|{{ze|g}Z}Օ|}O}{i||ߓ|c}||ѵ{s}V}Ł|uJ}{|||m}[}||||a}׭||Ǔ||g}X}^}~W}T}{ژ|w}ی}i}d}|l}}s}^}Ƞ|||{||{|Å}}[~`}w}}}}Q~|ۮ|b}|o}|||ҽ|b}|`}е||π}K~|M~Ƽc~ň}m}ʁ}{}u}~d}o}d}ٵ||^}Ո}l}s}|}݅}z}|h}o}ž{}}ô|т}ʁ}ɀ}s}~f}}n}|L~̃}v~v~ė}_~~~ܠ}r}f}|{~JIk~y~o}R~L~}O~Y~w}Y}~|||~}P~}ƿ~}||۠|d}W}ùe}J~¾}||e}\~Y~Q}w7~p}M~ǸU~I~F~I~Q}`}_}]}b}H~Z}[}q|m}|L}M}υ|{{|\}||{{{}|wy||||z}||||qs||||||||||||||||ĸ|Q~S~[~ז}]~}ԍ}}T~h~ђ}]~[~P~}|q}}ޘ}k}R~{E~e~ч}g}x}~R~}k~f}|Z~Ȑ}Ň}|}Ӡ|w}T~}M~|V}Ġ|`~}H~I~νe}V}a~u}|}K~a}e}\}u}}?~G~ʻ\~W~I~?~s}D~t}g}x}y}|Z|_|±o|V}K}K}ƺb}[}d|ݍ{ܓ{_|¯{{{{e|e|]}Z}j}6~]}d|a|{C}v|m{{E}U}E}O}J}=}\|Y}O};}D};}N|݀{I|<}e|g}h}>~@~?~d}ʳU}d}˳U}սW};~w5~X}X|z{v{{k|ż{{r|h||?~ھ^}M}E}p8~o}ͼ\}W}ҽa}D~}A~v3P~=~p}w:~uC}{{{{{{{{{t}e~{}?~r}@~y1y2\~9DR@I~8L~~=~A~>~t|T}ø^}ƺT~ŸS~M~8~]}W~S~C~6~?~=~@~J~<~<~9~:~A~Y~3~a~P}e}C~I~<~:~H~;h~a~x3^~~8Z~~7x3Z~;W~j~ʶJ~{2:w4h~Z~U~~7~W~U~u0F~Q~^~~4}2?ŶP~Q~G~ªE~T~͸N~J~{8~n3~@~?~F~>~C~`}B~[~̾R~|8}8ɷP~Z~H~>~N~ϾV~?~>~²K~?~U~P~9f}D~c~ϻP~c}s}k}]~ǶU~ƳO~W~Z~Y~ƻT~?~ǺY~P~J~n}E~C~U~H~k}e~O~S~L~Q~^~:^~[~I~~9~>~D~ʽ[~m};~;~I~^}W}~1~:~A~<~G~_}}0~=~f}F}E~X~D~¸X~^~@~9~8~H~?~~B~=~s}F~X~W~U~M~u}J~I~R~a~_~}||\}g}}||yy|{{{{{{{|{|{p}}H~t|y|\}}|z|֐|K}Q~Q~|݊|Q}}d}}D~}k}Ά}}K~i}ǶS~>Xb~X~d~F~i~`~FRjmEb~~~I}c}Q~B~Z}t}p}{Q}{|{{{||{ψ|X}Ⱥa}|q|{[}p}h}{l|`}n}}:~?~8~D~r}I~O~|}t}|C~}}|V~|u|T}X~C~ͦ|T}H~S~F~q}v|n}C~U~ȻP~x;~v}f}\~s}|}d~S~o}V}v9~̿l}n~c}ýx}͝|M~L~]}||Ę|w||Z}|h|Źg}~C~S~X~i~n~Q~}||{|}d}|þ|W}X}|Y}r}M~y}B~o}|Žq}̟|}y}Z}|~}h}|_}{C~y}|C~}}{}}ƻp}w}}u}Y}|{{{|r}}s~X~J~I~w}Q~V~W~T~р}ӄ}f~G~ɀ}b~m~e}|}g}|{{i}}v}o}P~E~e}Μ|c}}|}W~Q~Q~q}S~d~`~M~׃}»z}́}S~Ն}R~\~}~}}J~s}s}||p}}u}V~Q~Z~Cļg~c~l~W~q}żq}N~_~o~d~}Q~O~T~V~~I~}j}Nj}x}n}w}j}e}i}|~|j||f}k}t}ӧ|s}|}׏}e}x}{}|}n}k}q}s}N~P~O~ω}V~Q~S~Ѕ}P~ݓ}}^~Q~}Q~N~|}}R~V~}O~U~L~Y~X~Fx~S~b}c}}^~Ā}j}{}k}}s}z}ƿ}}Q~{O~R~w}Ɋ}ч}l~e~W~ۏ}n}}̾||z}}}[~~\~|}k}|}V~U~Y~X~|ɐ}Y~}ߕ}}LJ}֎}ۓ}ܚ}U~b~u}j~c~þ}Ҏ}S~Ս}o}}T~|||p}|}T~R~T~u~{~[~[~J~zG~o~}q}Ѭ||||||||||||||||Ҹ|Ʌ}ʄ}n}d}w}x}|||}|Ҍ|k}Z~O~Y~^~}I~¹u}|{{{{{t|Ϳ{{{{{zV}d}|r|Y}Q}t}D~Z}}|{l|_}~}H~ùn}ļu}q}׊}^}ȗ|z}מ|\}d}|s}|Ļ||͈|f}w}d}u~ŋ||m}׻|k}[}}||{{{{ο{x||}e}a}א}c}ֶ||e}p}|v}u}|u}Ɓ}Ŀ}Ή}T~]~t}q}m}}}܎}u}i}|c}˦|ڦ||w}j}}L~ڂ}|}_~}W~}a}^}{}ŝ|w}|h~P~O~Z~t}U~_~þv~S~y}W~~~~~[~U~i~\~Є}X~f~zB~|N~M~|s}^~L~z}|Ь||}}||܎}}߄}H~e}~c}g}w}J~I~~}g}|j}T~b~B~C~D~\}Y~m}O~D~B~|Q}T}h}M~O~X}k|{y|y:~j}||{{V}[}ʛ||{qr|||||tv|rs|{||oq|||||||||||||||||}m}b~Z~v}}}~O~_~}h~`~`~Ւ}a~Ľ{}э}֔}}b}p~ֆ}H~@~E~}z}|~e}̯|||||н|||b}}}Y~V~P~~X}w}{C~?~H~r}S~P~E~g}ǻj}]}h}h}p}@m}D~ϾY~X~I~¹\}Վ|t|ɿ|`}|Ųw|m|Ԁ|H}ͽt|n|J}||{{\|ٷ{y|{ċ|{u|N}|Y}W}N}^|m||W|S|P|J}|<~Y}\}İ\|W|B}=}A}K}B}t7}}{{O|T|^|F}C}~F}l}{:~;~8~d}S}P}ΰN}ܾU}W}ͰO}k|vP|ܜ{мy|{{}[|t|p|ór|{|Q}B}k}I~>~w}źi}źc}ߑ|f}D~@~J~s}ŸU~H~D~}<~ڄ|{{{{{̏|r}F~6T~d~S~ɼQ~X~M~?~^~Z~^~:67H~c}M~D~L~o}_}d};~S~D~F~I~S~g}U~F}B}5~~+~-~5~Q~:~X~R~]~I~I~úL~E~M~E~D~M~P~ǼY~f~X~{::@˿S?~7;`~~3=Z~?::}5X~J~<~F~L~;Y~W~|7c~;`~[~s-N~ڷD~K~@~u1@~>~x8~|8~C~<~^}E~X}C~q~Ce~9|8f~]~^~[~?~G~ƴQ~Q~U~z5d~[~K~G~A~y=~<~ӾP~U~<~B~K~]~`~\~]~F~}:~K}G~B~J~E~x}Q~ǺM~F~Q~O~W~[~ǹU~ɾ[~g~m~p~p~I~U}\}e}O~L~2~I~7~B~:~B~\}V}_}A~N~]~y~o~e~<O~º_}s}H~w}B~R}H}W}G~]~Z~x~U~ľd~w~j~O~~~ڔ|Q}q}{ě|̈́}~~|{{{{{ٲ{ˎ|{f|מ|K~i~R~|r|e|ҋ|~@~p}S~X}V}>~G~U~?~N~G~@~Z~ºO~=~^}n}D~ACg~Q~C~}=~\}z}Ⱦ[~p~Y·VR}f~m~F~k}}B~O~ו|q}X}}}|{|{{{||t||Y}S}|Z}}H~k}d}֛|ɽk}t}}}g}V}ɽY~b~}}}j~k}R~b~m~`~wv|y|~=~yF}j}e}J~ʽg}T}Y~_~~L}ǿk}]}w|y}<\~m~f}t}r}Ԉ|}?~Q~O~Q~}]}g}r}L~k}{}o}ɋ}k}~N~||{|~}^}|{s}S}|M}f~Y~h~J~}[}|m}w}_}e}w|{{{{ψ|o}m}}B~M~E~Y}`}^}|x}߆}ʨ|΢|||U}T~S~̿]~w}g}~B~}Y}{}ȿr}g}|u}}żl}||}}}w}|C~J~n}J~݂}h}`}Ļp}O~y}N~q~i~y>Y~N~|Ή}c}|j}|R}S~V~w}r}L~F~}d}ɂ}K~p~c~m~y}z}W~[~L~ׄ}Ђ}}^~}f}M~ֱ||ű|Q~]~`~P~T~^~Z~W~k~]~Y~X~[~S~d~ĺd~΃}zF~w}m}Z~]}\}t}U~T~q~^~\~[~}R~O~}ܐ}q}||ʩ|`}j}h}}{}}e~n~m~~v~`~}}}}c~Q~{}W~y}k}|Y}|~}s}a}ި|}U}p}~}l}_}e}d}ۅ}]~Y~ӄ}_}q}}Y~z~~n~W~]~Ђ}w}i}}z}x}ɿ}v}}|f}ǂ}w}}͒}V~e~}}n}Ɇ}Ȏ}ڑ}͋}w}ܖ}v}|†}T~T~}X~Z~w}u}K~W~z}[~_~R~i}v}}}V~~L~Q~}K~[~}c~}]~}}}R~W~}|}}O~}^~]~ф}ߐ}M~z}l}L~Y~[~g~K~s}u}||q}||||||||||||||||||ǯ||v}b}t|{h|n|T~a~Z~S~ƽ|{{{{{{|}||x||ξ{{|g}b~W~i}q}x}z}z?~S}v||͋|f}E~}@~s}l}R~}ف}J~x}d}a}ƻm}e}v}K~s}H~G~m}С|ݑ}w}a}||~g|}^}|j}|||{{{{{|a}Г|y|||{||||ƒ}j}}ǿ~}|Y}`}_}S}N~g~U~||Z}|~|}\}Z}öl}|K~׋}u}~}[}~c}}J~P~R~T~}v}½}}r}h~Y~Ǿs}˾[~x9~?_~\~b~y}p~a~p~h~\~k}ži~~=ן||t}S~~}V}R~H~·}~M~~]~||{|}[~S~T~l}ɿc}||a}^}f}I~||{@~q}?~R~W~r}I~z<~f}G~{5~Q}s|}|ǐ|t|}R~;~Q}[}|h}Š|א|Ǐ||_}|گ|˵||{}||su|{~|{{{tu||||||||||~}|u}||R~}|Y~}ޓ}_~Y~]~l~J~R~}z}՜}r}}P~a~|a~|b}X~m~r~i~J~?~{}Z}a}d}|||{f}|ˢ|ʯ|}}[~Ս}k}O~b}f~w@~W~K~y;~y:~Ǻa}_|g}o}a}v}G~O~e~ŵV~ʺX~F~L~;~zJ}؍|Ł|†||X}g|q|[||yD}]|Ю{ɷ{|||q|p|]}N~e}l}g}R}Q}Q}}D}wA}z|f|zN|V|l}C~żf}H~p}C}ɲK}İU}ūM}:~o1~K}^|m{{U|v|u|a}Ծ[}@}°Y}i}O}ųW}K}U|R|d|f|_}~7~P}q|k|m|zG}~|S}߉|Z|n|}V|yT|f|~D}:~E~B~{}ۀ}_}n}T~J~H~K~t}R}E~M~l}|]}xH}ܽz|{|F~<~>~ξM~U~˽O~H~z6}7úX~R~:9f~\~f~6[~\~M~R~M~M~C~F}?}~A~P~Y}D~I~F}S}+~.~:~4~b}=~6~^~Q~`~Y~E~S~ĸV~D~A~K~m}_}p}_~k~~;D~>e~>A>KAǺY~U~̿]~>|9h~}6AU~[~E~K~f~O~Y~g~ؿP~y03ŮJ~Q~X~ïG~f~U~>~:~A~q-ǯI~>~;~c}U~N~L~K~N~V~>~Y~²M~R~b~E~ϿT~ʼT~e~|8|8d~H~E~M~G~<~ijJ~7~}8~L~Ⱦ[}?~M~=M~>~7~A~k}d}T}i}h}x9~B~P~A~T~O~T~w3Z~R~a~p~ɼW~M~C~h}O}O}:~5~[~4~J}0~:~5~:~5~=~@~`~{C|~y~L~n}A~E~D~o}D~ɾm|b|f}C~L~c~{~?LT~ú`~e~~}Z~j}f|{|}þ|{{|p|s||~T}o|e}÷U~O~D~@~|9~a}߉|`}k~v}h}B~\~?~[~C~H~ıK~c~M~B<[~G~`}|ג|m}]~T}F~}J}^}B~j~Jb˽T~JI~ĺ[~x7;P~C~c}|}D~^}U}x|x|{{{{ɿ{­{ùz|yV|s}o}^}u}S~Y~c}p|S}J~t5~Ͽ`}ȹo|=~F~}|M~z}V~e~X~HQ~}L~q}t|[}ȁ|ú^}]~|C~x}e}̺N~l};~d|m|^}C~@~E~}B~h}i}[}y}r}t}h}a~J~U~~}zG~I~m}h}||||ɢ||^}\}m}ɿc}T}N}Y}X}@~}?~?J~\~\}|\}i~\}ޕ||~G~w}\}|{l|{||_}r}\}b}P~s}ږ}T~V~V~p}|H~A~i}|B~|}yB~i}~}[}|}y}|ϗ|g}|w}]}W}}d}|D~O~[}y}}ƿz}p}a}ȗ|g}J~ɗ|؟|G~e~t~X~J~m}Ģ||ǰ|{}|כ|q}Z~Q~|֬|}}l}|x|Y}~}@E~{G~N~W~U~[~}t}b}i}|{}j}j}r}u}}m~|Bo~}ƿ~}^~f~U~}e~c~M~[~h~W~P~}C~ф}s~c~u}||`}Ľt}k}`~m~Q~o}~L~{}}}}I~ʀ}o}o}}||f}y|c}a~f~N~u}m}̆}}K~M~ي}R~e~u~~|@`~Z~|J~Ӈ}R~P~K~r}}Z~M~y}_}||ü~}z}k}{}c}s}i}W~k~u~x~d~[~d~L~p}Ȃ}c}y}o~}c}}q}ý}ю}P~}|r}T~Y~}}`~А}}t}}}u}u}Z~_~_~}R~I~X~ڏ}}}}}}Љ}υ}S~}Y~]~Y~U~}ÿ}}T~Y~}M~ړ}|^~z}}ҍ}}}P~[~Ӈ}Ո}ӓ}}g}}f~{}y}O~R~N~|}{}є}p}}ƌ}~}|||||||||q}||||ȴ||џ|||Ð||n|J~s~|H~w}|{{{{{{{{{{}|ƿ|`}b}||ǿ{}h}r}˿k}J~s}f}ļ|^}Y}r}r}`}R}p}K~zB~M~P~_~V~Ǽ`~e~T~ƺ[~x9b~d~}¹v}m}}|u}N~[}z}}}t}s}Ӗ}\~|e}ɧ||||J~|z}||||||}l}ˆ}k}l}}U~}X~X~}]}Ï|{|ȿu}h}޸|Չ}x}f}~}|}ڏ}|L~}}\}y}}ܝ|p}w}Nj}W~w}o~a}C~X~Q~F~Z~T~R~}|||ʂ}|޾|}Bf~|j}p}}ºm}N~@z}Y}m}@@Y~|{{|m}|z~u}|ºq}ƥ||d}|̭|y}{v|P~|H~N~_}y}Z~J~O~e}n}}L}p|y|ľ|N}Z}zA~|?~k}u}|҅|û~||w|ϵ{{ʹ{{{{{||||z||qr||||st|||||wz||||}l}k}t~Ԏ}c~u}p}j~W~g~z~O~q}T~n}GP~}m}q}‹}x}}}r~T~ºY~L~7~m}Q}|q|||}ȭ|h}W~Z~l}w~j}ދ}|p}ϊ}l}q}H~j~~}H~y8~T}C}c|]}X}Ĺj}}}E~Q~ȹZ~a~A~[~O~8~|8~N}K}V}R}l}j|t|O}e}ǿ{|Ƈ|o|֔|{o|{v|Ə|f}d}t}~>~n}@~m}P}S}a|p|z|}S|c}E~n}b}w4~v6~A~:~J}_}:~S}Ķi{}B}лf|Y|]}ɹc}t8~p3~T}ʲR}ǯ]}g|i|zJ|t{j{R|d|ҼZ}d}K}v?}ιn|~|b}ϹX}J~=~M}[}{{_|{H}|˾d}C~}}t}}}m}Q~w}}p}|l}u}|}B~F~?~ֽc}´|{Ж|m}@~^~^~;o~}:n~f~e~ǿW~A@X~F~H~J~K~n}y:~D~C~Y}:}D}1~H~k}i}L~S}6~8~8~=~<~t5~κX}z9~K~I~D~}}~}A~Z~U~c~d~U~i}H~a~m~D{;°R~ƹU~HPWAM~Z~h~d~·Z~Ƚ^~S~j~µQ~C{8U~@~M~y<~N~Z~O~U~U~ؾK~ּL~V~_~=ȰF~̲K~F~DzK~R~3]~B~G~r}x}y}F~J~U~M~W~^~G~ůN~dzM~͹P~H~e~´Q~y4b~Z~k~µQ~`~O~Z~±K~f}x5~H~D~Y~S~e}C~E~=~x8~L}_}M}f}c}[}R~ȹR~ϼT~P~h~d~[~F~Q~c~\~U~e~F~;~C~N~>~>~<~<~5~4~?~A~y}C~ƽb}U~a~t~P~}I~q}a~\~}}P~k}g}h}C~K~f~}þf~o~Ґ|K~ľm}Z}e}l}p|||{m|_}]}|m|p|o||{{i|b}ûe}_}c}H~O}\}܌|b~R~˿N~D~_~S~C~V~f~;S~A~I~B=^~Z~R~h}|[~}h}c}{}T~ǽ[~I}?<GO9Dz6g~E~^}T}i}i|y|\}s}@~ө{{{{o|S}U}H~T~~}Ҡ|B~e~d}B}Ҁ|r}u|øa}||Z}c}C~}5~Q~}@Hg~|p}}P~ي|h}}݂|t|o}A~}5~b}u}C~=~|g|Ȼ^}@~L}͉|?~T}T}úl}P~}w}|X}ө|ýs}̏|o}i}~@~b}||||~^}_}[}\}Ǿf}^}\}g}r~z}H~{;~C~U~x~R~G~r}}|I~}d}z}F~n}||{{T}ڀ|y|ƽ|W}t}X}u||}X~y}z?~|A~q}n}֣|e}c}¹n}g}z}g~k~_}ј|{O}P}ܖ|}V}G~t}[}W}v}E~v||n}d}y}z}V~}i}}||}O~O~g~R~{C~}x}b}ʎ|Y}|Y}މ}}P~|s|v||{{y|k||C~ƻ_~H~R~]~R~w}ň}{}r}}،}ƿp~x~V~^}П|h}w}Z~v~p~W~ƽg~~~\~]}z}}Z~{G~[~`~}\~}ʆ}_~Y~{M~O~w}|^~ľl~b~t~k~h~|J~M~N~l}{R}ס||{{b}e}_}•|w}F~}b~}Z~X~~}h}}w}r}}C¹`~j~O~ºt}h}b}i}s}½}}N~L~K~}|k}R~͉}}ӄ}g}|g}}H~X~b~~H~S~Dl~ؗ}p}ý}̎}م}f~w}b~}}z}}}ƾn~b~ݎ}a~d~q~l~k~^~P~}ߒ}r}~S~|j}ٹ||k}W~a~}T~ݓ}}ŀ}~}t}}}ƈ}n}|}}ގ}b~O~}n}||u~{~Q~N~Ԏ}}}W~T~Q~o}Y~f~}J~܃}c}٩|b}Ј}}ͅ}}}}ˆ}d}s}}}}ы}x}Ή}Ɋ}ّ}V~t}i}e}ѻ|||w}x}r}f}|a}{}Δ|q||{{޿{{{{{{{{{{z|X}Q~v}y}q}d}c}}@~[}a}x}[}e}`}n}c}Z}G~J~Q~M~T~U~ɹX~R~V~Q~`~b~`~Q~}װ|c}}f}b}T}p|{||v}m}\}ֿ||i}|~g}l}ý~}W~}w}U~l}ˈ}Ү|||{|Ņ}ސ}i}}΄}}}m}k}m}ߦ|b}֋}{z||{}t}|q}ێ}Ƈ}˩|}մ|ϻ|Ȫ|ʁ}s}|}`}P~E~R~_~U~ڂ}a}}@~D~\~^~?o~^~y}m}||u}h}P~X~{~AH~ڨ|o}k}z:]}z}}L~^~}9ǽ[~}|{||é|S~[~|||||xy||~|{~|s}Z}P}]~v}o}e}||||V}|y||}|x||[}S~|}ƻ`}x|j|p|b}{{׹{{{|xz|{{||||{{{|||oo|||y{||||yz|||}}]~_~}_~}|M~m~}I}}ÿ}Q~}^~}ڝ||b~i}}}}R~S~V~ɿ^~p}}X}|}Lj}ڇ}\~}x}J~N~T~l~Ӎ}ɒ}z||~}׊}}W~f~D~}j}|D~l}D~Q~|V}V~}K~S~U~R~H~]~X~L~:~[}ŶX}\}a};~g}T}ƃ||k|w|W}T}|ԑ|i|b}ڍ|U}_}{}e~K~~J}W}H~N~_}s|c|{|{|~|\}F}ïL}}6~y5~E~_}|5~=~Z}X}Q}C~U}ŭQ}f}e}ݿY}]}ܸT}T}X|l|Ҷj|v{z]{X|d}e}_}I}I}_}R}d}I~l}Q}i}^}o}|S}W}{e|»|y}R~]}y}}т}O~_}k}{}|Z}o}P}D~_}N~r}J~ξc}̻Y~y?~T~t~ENEk~?~F~\}e}q}?~CZ~k}c}<~y0G~[}o}4~}0~F}<}A}Q}M~D~D~M}f}b}x6~K~[~M~`~E~Z~J~s}Z}zI}H}ȶZ}x;~B~F~b}õb}m}>~f~d~R~˼W~z7~;Y~c~P~X~ź^~_~I~Q~f~;Ź_~K~H~Q~t~ǼY~P~C~{=~]~S~}3A~Q~@~w3A~^~Y~V~ǯI~͹Q~[~V~S~_~L~L~E~~D~x=~K~\~L~W~ǹ^~ǵQ~\~f~h~ƵQ~ʻU~R~X~X~z8h~e~x4L~X~K~ʼW~;V~?~~>~O~R~|6~E~9~\}a}f}}7~U}U}A~m}M~J~B~G~ijJ~[~f~J~C~G~D~h~X~F~L~V~Y~ľJ~7~5~-~2~0~2~d}M~=~Żc}X}q}t}q}Z}|B~R~D~w}l}W~żd}R~~d~I~Z~Vfh~º]~x}|}}w|}J}i}}|U}L}z;~O~?~x>~ӄ|e|{Ւ|?~Q~K~R}~<~C~k}d}p}L~P~X~H~G~<~@~J~I~E~@~9l~[~A~F~>~w}|}K~R}r}j}i}f}g}Ľh~SNSLO~\~f~V~w}p}p}E~G~o}C~{}t}P}|r|}|S}S}`}žt}f}g}]~MFp}A~x|I}{M}ſ{O}y8~C~G~L~A~]}|K~N~|D~z}ʼ^}ܝ{||»|y:~T~4U}S}m}l}d}u|g|F~Z}M}w|û}|]}N}`}_~@~|B~w}a}߂}_}||}|||}|||}h}b}m}>~E~f}T~^~w}q}E~`}Z}[~H|ſr}M~܂}R~[~J~\~}\}[}||ž|]}ɔ|ݡ|ܝ|u|{{{|||U}ǷU~s}q}s}q}||w|{{Ň|E~^~O~A~~C~^}m}U}r}|B~V}͖|s}M~t}{|ӡ|k}ľu}Ľt}}K~Q}|^}|}\}~G~Y~T~߀}]}\}}\~L~\~S~ߋ}Z~H~I~p||j|y|||||D~}z}s}|Ȭ||}V~Z~T~މ}}x}ߋ}|k~Z}o}Æ}}P~{H~z}}˿d~h~y~o~z~S~Z~}Q~j~l~Z~y~z}L~`~|h}f}Y}}C~K~}Ʉ}}{I~˻|t}||z|^}Ԓ|Ҕ|{n|Z}ûr}[}q}؆}V~}ש|{}݌}}q}N~w=j~}I~}M~~}}h}h}a}e}g}}P~S~y}}}}؍}b~a~֏}X}j}]~R~t~u~M~[~ƿn~~~b~}u}|ý}e~Žg~T~i~~T~͂}}R~~R~}~I~`~[~g~N~Z~^~]~T~o~b~R~v}ߒ}U~p~Ӂ~ýr~e~}֒}}ӓ}z}}܋}t}n}p}m}Ո}Ά}q}N~g}T~Ă}}~a}|f}ф}}K~}Z~L~]~O~P~[~}q}j}}{}r}Ć}}M~ǁ}W~̉}{}}}ޒ}Ά}U~y}y}v}}T~ޑ}Ā}M~V~݅}o}K~H~}N}x|r|{ÿz|o|j|ܽ{޷{ϲ{ܼ{{|w|{{{{{{{y|||¹|Х|¼|ɿu}Z}G~a}ޗ|ψ|c|Y~{C~V~y}M~}n}m}E~Z~E~Q~F~µc}^}T~r~_~}|u}Y~{C~W}||v}Π||~^}t}u}|z}}ݕ}]~|n}~}d}m}o}k}m}u||ſ|q}`}v}i}_}]~f}p}x}q}w}}V~z}Ɲ|y}^}}|¿||Ǥ|||}||ػ|t}|}a}~I~g~c~e~}~Ն}}n}|o}D~9:GI]~u}Ў}[~u}||k}Ƹ|Ѻ|u}M?|^}f~p~p}s}݂}i}m~d~|}||{tu|{||||||||~|{}||||ؗ|w|c}N}|{{{{{Ҿ{{|q|||¾|{A~|}Y~n~T~N~||z}A~o}_}}|י|||}||º|||{|rr|{}|||uw|{|{||{|rs|||||W~ѱ|R~`~S~}ÿ|`~y~Ԉ}~~}k~|K~b~[~c~a~]~HU~||||||g}A}½v}ʂ}u}n}V~}؏}o}Z~ߍ}n}f~`~a~f}д||m}|v}N~v~d}Ӆ}e~S~Y}S~V~ƽf~}a}V~}B~A~x8Dd~9W~N~E~@~o}o}L~S~J~l}|ԉ|Z}R}Z}_}ҏ|i|e|u|̼v|J}A~I~P}|`~i~h~˾g}~}N}Q}E~m}k}a}Z}x<~?~E~·_}?~B~B~B~h}ɻY~ȴY}o2~B~v8~ݿY}^}ʫR}ȨS}H}p=}`}{@}s|z|{m|K}˶S}лZ}w8~=~_}{9~j}|c|r|{|k}~}g}V}z}h}}E~}}|||d}m}M~Մ}~}T~f}\}‰|֕|z?~A~G~d}H~Y}[}l}X}H~J~`~b~E~P}h}c~˽M~T~e}@~~8~Y}c}Z}@}?}4}L}c|D}T};~x7~E~J~f~T~P~W~q~Q~T~J~Ľj~_~W~}|@~ƻ_}F~ïL~`~͵O~c}ðY}{=~M~Q~L~f~a~öW~P~Y~[~v}B~H~@~L~@~B~u}I~H~C~K~J~L~M~ǻ]~ο_~V~Y~]~u3u369x6b~[~S~_~7Q~:X~ȻW~I~v;~I~k~[~L~G~K~÷Z~q~g~`~f~a~a~y5@}7<AEx6L~[~ȵO~T~F~~;~J~H~I~³M~~4~´O~Z}l}^}`}ʸV}S}A~Q~Q~>~K~@~O~Q~U~?~`~Q~L~ĿO~D~I~J~H~A~C~οL~C~3~7~?}L}:~G~L~i}L~J~m}N}D~@~U~Z~[~^~J~i~H~]~~~~ab~I~M~}}e}C~Ĺc}ùx|P~G~c}{{U}S}x|{{{{_}v}=~|=~Q~Ⱥa}L}؍|÷c}:~@~Z~I~d}B~Źh}z}I~ƾc~Hl~|?GS~t}c~A~_}a}S~f}}|H~}A~G~[~ǹZ~a~{<E@f~I~^}r}R~U~Z}a}@~t}U}o|ގ|}Q}k||R}|{|d}d~KF}H~ût}}f~}{|}|{h}{7~~9~P~[}Ⱦj}x}w}V}_}q}r}v}Y}~|>~?~J~k}L}s|٭{}Y||=~>~~H}{s}J~i}Շ|y}g}F~g}}|՚|E~d}öd}|ϱ||||{x|d}{}q}}}S~L~K~w}W~h}m|d}o}l}n}z>~=j~u}g}K~T~Ԃ}g}|C~o}c}]~s}n}f}w}u}ر|r}||||{y|R}p}{?~d}a}||{||Օ|\}o}||x>~X~K~U}O}L~}@~p}{}d}G~o}}|D~{}׏|d}n}o}^}~}f}j}ܪ|x}R~R~H~\}v}o}č|w}}݂}h}݂}J~o}q}Z}{c}o}S}||{ǚ|r}|H~d}{g}}q}S~ƾc~J~h}|ǭ|ɾo}S~}}k}||{|s||m|_}s}X}t}o~|}}|J~ӂ}c}_}t}X~a~T~Ń}Æ}_}|c}~}}L~I~P~V~a~}ň}|¶|`}{A~C~u}j}՚|Ù|¿|h}|{l}½}o}||m}f}Մ}V~^~g}ω}ގ}ς}}m}|Ե|i}}a}^}v}}a~[~h~T~q~P~}L~]~V~S~f~E}@g~Ži~h~~_~ƍ}S~S~O~P~{K~_~}}Q~\~[~S~R~U~~E{?Ⱦb~ǽb~q}}a~V~W~}J~ûc~ƽf~^~[~R~U~S~}K~N~Y~~d}č}b~|ݚ}}ϋ}|}V~a~Ľ}t}̀}}Ҍ}o}ɷ|}K~l}ް|j}ºv}|Žr}_~L~}o}p}d}ږ}ŋ}ܕ}ˍ}ؚ}[~[~_~}}w}֔}W~}}ە}ʃ}x}Г}}K~\~Z~H~e}f}||{{zz{r|{{||{s|||||{}|||{{{m|o|}||{|||y|u}|D~Y}s||]}ύ|x|Ǽk}u}r}[}y}yA~yJ}||m}Ł}|{{w}z}a}]}}}O~H~ɝ|||||ȴ||}i~}||~o||\~|ޞ}|||i}y}}y}p}|h}s}}ԙ}h}¼{}g~O~y}v}V~p}||}|||||||ú|}c}żb~n}G~K~m}q}|{Ϲ||Q~NVGW}||l}„}{}Ӹ|X~w}|k}o~Sºn}l~k~L~ſx}݀}t}}K~o}{|\}|{vw|{|||||||{{~||{yy||ͅ}t}}ͨ|Ҙ}ϰ|Ǐ}||}G~~c|t}|}]}n~L~=F\~ɿb~Z~څ}}T~E~{|x|F~||ƾn}||||qr|{}|||{ô|ţ}|{qr|||}Q~ԁ}|~}}ƾh~Q~}|v}r}}}|D`~^~u~}H~UM~@l~b~T~||W~|ڿ|O~~S~~|Ӈ}e~R~g~^}}}Q~j~K~t}Մ}j}^~p~|{ã|q}ƣ|a}k}_~x}g~Ł}C͋}j}[~|W}}}o}@y?T~U~øW~z:g~u~G~F~F~µV~S~H~Ƚl}y|||\}T}_}[}·k}Ӄ|ވ|U}݅|d|e|{s}}Z}³a}R}Ⱥ\}}F~S}J~f}ƶZ}̼`}C~M~C~F~X~M~{;~J~ѾW~r4~=~M~8~r5~`}I}l0~Y}J}лR~Y}vE}zF}|B}c|E}Q}D}i}C~K~n}P}h}Ͻ|ƒ|{u|W}a}X}e}Ȼi}v}z}x}ݎ||r|T}V}]}}y}W~\~T~}ܛ|||t|xF}E~{7@~V~z}i}v}C~Ҏ|h}z}N~J~F~71D~\}˾V}k|4~5~U}a|C|J}[}v5}s0~ֿO~ðU}f}@~zC~I~N~O~T~[~a~S~k~Ǽ^~p~e~C~H~L~J~b~~7Q~6~J~q}{=~n}w}W~Z~`~N~F~J~~}D~J~M~{}zB~H~Q~B~{A~t}S~H~Ǻ^~ƸZ~Q~нW~]~C~Z~a~|2z7v7U~O~Y~T~W~v3=c~y6:e~K~q}T~F~R~C~T~˼\~p~a~Y~D@a~s~e~?~_~̿X~a~\~R~G~O~H~H~G~ѿT~B~w1K~C~B~;~G~B~:~_}>~I~@~>~;~B~V~U~W~S~ҾT~̹Q~a~U~O~W~W~D~8~=~h}P|D|9}M}_}F~=~A}ʽV}\}n}t}}s}V~]~\~o~U~\~V~\~\~W~}_~x~M~žp}a}|I~y}|C~ʁ|q}j|q|{{{}c|{{{{{|Č|}N}|R}xA}I}ʾd}X~J~B~L~J~?~\~T~Z~̼R~KLC~ĶQ~f~AP~R~H~S~o~[ºV~e~~|>ȼT~Z~|D~L~|O~k~{}Z~Fk~j}x}u}a}|q}Z}ʋ|{{{~|}T}{€}h}»r}I~I~}P~i~]~Z}E~q~l|{|s{~|l}C~S~˼W~|̓||}c}||n}G~Q~U}ø^}D~x<~S}D~Z}j}|ʅ|[}a}v|Y}n}t}<~|a}ý|^}m}|{X}D~f}y}|||{y|V}Z}@~]}f}Q~[~s~i~b~e}{|ļ||m}g}r}z;~v}C~P~}}ǿl}ùg}z@~W~Z}[}|g~|څ}k}W~w}|}v}|Y}|o}¹h}|}Q}o}D~K~i}d}|{{֋|U}~`|½{{g|ʄ|Q}U}X~A~Y}e}^~G~d}T}f}s}}E~C~|C~\}}\}i}f}]}k}m}O~W~x}[~}_~q~t}K~}}X}||||@~Y}||~|x|||Z}\}͈|~P}X}Ȍ|T}ˍ|}m|p}e}a}p}ӂ}l}{|W}|}E~||||{|֚||W~D~Q}^}{}h}U~_~V~ƿv}d}q}|S~R~L~}}}_}ƿz}|P~U~H~W~S~N~ĉ}w}}|{{|ƽ|]}n}]}m}T~T~i}r}½~}p}ȶ||||||}u}܎}Չ}}M~K~X~U~n}|}g~}S~\~֋}}h}a}Y~Q~׈}ԁ}[~U~]~W~v~F~i~t~~¼o~[~K}߷|У|i}n}g}O~Bk~k~[~V~}}R~b}|ʈ}̉}̇}̂}żs}g}|||v}ž}}،}~}}}y}~}v}і}Ȏ}}Ń}ă}t}|||`~d~}}}Q~q}R~}}N~ϫ|͆}Y~}T~{L~Ā}X~t}Ί}Ɇ}z}}T~ߐ}Q~Ѓ}}Q~u}}؈}}{}s}|Ą}ƽz}҉}ý{}g}V}_}۳||{{{{{{v||||}||{|f|~h|s|||q|{ij{{{{{{{|||k}Q~}}^}o|{]}ޟ|o}o}ʻa}E~v}d}z|{{||¨|n|{{{{p|{||||||||[~¹|u}ɔ}}h}o}v}|}v}o}{{|̋|ǿ{}}x}[}Վ}Ň}|}{L~t}V~l}p}}݂}Q~փ}|||}}|h}|||o}p}ό}}Y~j}ގ}ˁ}Q~n}|||{}|W~\k~KIb}||||}ʴ|R~K~b~}}j~\~P~:܀}}Q~~}||ٶ||}}|}|{Y}{{||{rs|||{pq||||||}E~j}l}Ї}O~j}t}Ʌ}d}F~}|K~k}]}M~]~o}Ƽ]~ǽ^~X~t}L~x~s~O~zE~[}}w}~|\}ע||||mn|||||pr|||||h}d}}y}}||[}z~́}S~V~|||d}}i~{P}t~}a~Ɖ}f}Gg}Z~q~Gv}ګ|ƥ|ڑ}~e}}N~}U~}~~i~e~Ǖ|g~T~G~R~m~}||s~ǿg~q}O~|Y~}}{|c~g}[~|z|P~P~_~}f~R~P~]~k}y~O~Y~Ee~d~Ǿf~u~f~|~U~D~X~Q~K~A~w;~·^}|ǃ|}|[}T}o}W}g}P}J}a}k}s}r}q}x}S}\}p|Z}m}{}X~ƾj}O~^}~}b~[~ۇ}u}}T~y?~o}L~òV~>~@~c}?~>~O}ӴS}C~@~n}A~G~ӼX}ͺ{b|w@}E}k||ƺc}ķ]}=~:~A~}|I}Ӄ|j|~|r||L}|ގ|G~a}f}`|U}i}~}|||ټ{W}U}n}^}|{|k|b|a}j~L~~}f~¹Y~|v}[~|~U}c}v~G~d}I~=]~}|a}zR|R|3~>~3~~4~x5~c}ƲW|ǶX}L~c}l}J~}J~|E~Q~S~{H~X~p~t~S~g~ɾ_~`~r}C~G~T~E~U~W~V~X~ͽV~`~h~i~N~y}v}|}X~yD~Q~Z~U~O~N~P~R~Z~^~[~K~Y~V~ѿV~_~P~D~M~̺U~W~=I~̽\~G~D~u/H~įG~սM~A{6^~;e~X~a~|B~{}r}}>~}?~q}K~P~_~e~^~}8CCg~a~Z~x;~R~ǹY~X~Z~J~V~ƱN}x6~{5z6îE~ƷQ~G~I~U~H~S~J~F~C~<~?~@~G~E~ȷS~c~\~Z~V~V~D~e}H~L~E~D~@~=~Y}F}}8}c}@~>~9~D}y5~F~j}>~w}K~R~X~j~b~O~K~AAo~E\~Y~c~`~u}~}j|N~~Q}ºp}w||w|{{{Ǿ{ž{~|{{u|{{{{{{||m|l}@~~9~C~ǺP~z:~M~b~W~S~F~y3a~R~R~R~EB<]~D:q~z6R~û^~y5:~>~I~|E~Z~v}f}X}<~p}K~G~y}n}c}C~d}я||{{{{{yz||ܳ|{}P~}>g}n|P}K}R}U}L}}D}J~Q}V}~|p}z|V}j}Y}|G}{\}a~[}R~w}F~~?~s}T~K~D~܌|g||U}Z}a}X}C~t}{;~k|W}|p|ľ|{γ{|ſ|R}x}>~|||y|V}yL}S}T}d}w8~X~AZ~Ǿ]~|s|{˄|E~F~z<~x}h}m}I~ړ|f}D~z}i}o}}e}|}؇}Ӂ}f}q}c}`}m}Б|g}Y}x}Ř|e}u}t}x}|zR}Z}r|{v|{p|ڍ|e|̓|||j}Hb}k}L~^~R~e~I~z}N~J~s}H~k}u}v}z|`}ܛ||d}c}ƿr}ހ}[~_~|S~s}ǿg~N~yE~ƺi}\}}X}q}l}m|{|Ѥ|½|Ž|p}h}W}Ã|ʈ|o|}|΅|}|^}r}}r}żr}l}f}wC~D~r}u}Y}V}||||{ˇ|E~w}|E~Q~R~m~}}f}b}h}f}e}v}}}}b}ľ}||l}]~Q~}H~z}w}|||||ԣ|Y~Z~|H~|I~M~P~~M~S~zJ~X~R~׋}i}h}˄}q}u}R~||}J~M~yF~M~}üy}|ͽ||؅}}L~}M~~I~M~N~̄}}f}j}\}Q~T~K~N~J~_~[~i~}~v~~t~b~f~S~P~O~Q~V~n~`~`~N~}¸n}e}{}{}{N~zI~f~}ˏ}u}|ߚ}ׄ}z}}}}T~Z~J~}}{}}}ۇ}}c~\~b~܎}ύ}}|||n}t}}r}ʆ}ƀ}}m}|}Օ}]~n~k~Y~P~}Ņ}~}~_}}N~M~v}}~}|i}i}|a}{}Z~}p}}{}Ʌ}u}}J~N~}Ӈ}}}}j}|e}̥|׭||{{¹{{n|||q|p|||{{{{x|{{{{{{||{{||]}v|~||g|u|l}~}q}xP}{|||||||{||r|{{|{{{{||~|{|||||||m}{}j}ь}f}ܳ|ߕ}m}|}\}k}]}{{}}Ԑ}s}|K~t}U~y}S~R~[~s}b}|q|{}||l}|׸|{}S~s}P~א}׉}o~v~\~ƿp~͈}a~}\~و}ٔ}~j~p~Aȿd~}|||}͏}v}[~Ds}||ф}a~K}S~c}||||j}|ק|k}o}|j|ό|ݎ|s}|ú|{}˲|{||tu|{{ׯ||M~֨|h}}X~ہ}V~|ʎ}|ݥ|g~l~|a}~~f~d}~H~}}L~ORRs~{~}j}|||||||oq|||||||Գ|}~L~[}ƾg~f}|ֈ}Ľe~Ӌ}s}ܮ|̮||}n}X~q}V~W~c~Ɍ}j}|~e~o~T~Q~~G~ڇ}q~Ќ}o}u}}W~\~Y~_~}t~P~|Z}u~^~Iq}ˆ}}ˊ}f~`}O~f}}r~|}|j~`~Ɇ}Մ}|d}u}ی}T~W~}֊}_~х}T~`}\}k~H}X~s~ƿg~}w}N~h~Q~E~O~n}z|{[}ǿ||ż|d}b}`}Y}Y}`}ûq}a}R~q}J}F~z>~|R}W}T~}W~y}O~|}q}P~Ă}Z~`~_~W~q}h}H~C~O~A~_}ƫR}O}~?}¬S}Y}B~B~z:~z8~F~zG}r|}|I}W}P}ܥ{w|T}O~ӻd}v}S}x|{g|ǽ{{|\}Z}p~||{Й||L}Tp~ǿb~V{E~Ȼl}T}d}x}J}tA}~|{q}|S~X~}}h~Y~ߞ}Ab~{8{}f~X}?~F~C~S}b}{|޿k|J}n||.~T}3~ϴK~ìN~b~O~^}\}q}y}Q~g}t}X~T~W~F~g~T~Z~l~e~J~h}i}K~S~u}M~ôQ~X~6g~c~Cm~n~d~L~{B~~E~d}]}^}=~{@~S~G~V~O~V~[~Y~G~=~F~X~v0Q~ʹN~N~Z~9~?~ھN~s.E~ƴR~]~a~A^~H~W~f~J~e~m~e~T~G~|B~l}{}U~@~q~n~@B;c~`~y;L~h~a~a~i~ϿV~ðM~L~L~9h~v1I~B~O~X~<~L~C~ƵN~J~?~F~ɷP~I~M~\~8v5P~f~<S~ɹ\}E~a~J~H~B~8~@~<~G~F~A~?~<~=~E~g}I}`}U}^}|}P~E~c~f~s~|=z~h~_~}|K~L~|}~ýe~||ľu}|Ξ|||ƨ||{{{||{{{{r|d}|{{P}H~N~D~P~X~;~ɸZ}y6~V~E>̿O~G~z6H{~s~=O~n~6f~_~KGJɼT~S~f~y}Y~I~P~7~i}|V}|c}|F~D~H~W}g}w}[}Z}X}Ҫ|Ѕ}||~J~||Y~l~Mf~{=D~x|n|n|}=~W~W}^|v}M}J}y{P}^}X}S}u|ԯ{ֲ{~|M}i|~H}C~B~J~p}źc}X}zF~u|c}b}w|P}d}Y}Ջ|{|Y~Q~|r}|Y}{y|߻{N}_}g}?~f}||{ζ{{||c}ôY}Y}?~c~{}k}Ѡ|w|{j}G~@~ƷP~G~||B~]}q|Ê|j}D~C~p}o}c}u}h}g}J~[}|Ƈ|b}}V}|{U}|¾|~R}m}|~|||S}{{{{{{°{{Ã|j}7}}|s}}BF~Y~}}e}K~G~z}T}r}o}ý|f}u}|]}w}V~T~V~g~f~m}|i}Z~q}_}|||ߨ|W}ϔ|G~[}Y}]}}s}k}e}Й|a}¹n}Ȼj}j}ߚ||{L}o}u}ķZ~a}Q~E~w}q}{||DZ||{{ʸ{{¿|}T}||}`~N~Ӊ}݊}}Y~c~a~}G~b}n}}‚}}a~x~~L~w}ý|}|g}ƻ|u}y}|{}܌}}ĺp}s}R~L~Z~O~Њ}`~}߇}q}S~[~T~}L~܈}}L~|}}S~b~T~}}M~S~f}o}p}źu}R~R~{}o}||Y}ƽt}`~c~O~}H~L~l}d}p}e}}·_~O~I~S~j~h~T~S~I~u}}Q~ùb~}i}߉}úz}v}|g}v}}l}ľ}}}}j}к|||}T~}N~}ȋ}j}h}Z~~}||y}|L~}Ʊ|||}ݗ}w}Җ}ݙ}h~ي}}}}M~]~r}}Y~||||l}n}|J~}҅}}U~T~m}}]}n}V~Ѕ}ѓ}}}p}V~؀~}Ή}z}|]}||Ĺ||ɩ{{{ʫ{z|{ٸ{|||{{{{{{{{|{||{{}|{|{{{||{||{|zz|||{{{p|o|r|{|{{{{{|||||{|{s|º|||Ϲ|҄}~_}~^}}d}X~|}m}b}h}ǿw}ʉ}a}|W}|{Ƨ|׽|}m}~}h~[~V~u}l}|ó||m}||||||͠|}΀}v}݉}Z~]~p}Z~z}R~\~}x~q~S~m~m~Z~M~m}s}|||˫|ɸ|V~@^~|I~й||v}|n}}[~ٶ|||v}d}߭|u}d}úr}ʨ|e|{{dž|¿}{||~||||{{{{{|{K~R~f}ׅ}j~x}V~|܅}O~}|Ϳ[~`~ü}h}l~}~^QIKU~}e}|||||||{{{|||}}͋||Q~`~d~e}j}ĺx}o}d}}̄}}n}{@΂}}|e~L~y~گ|y}ٙ}W~c~f~žj~}\~v~z}|}T~v}Q~y}r}}U~j~\~b~Ջ}ā}}t~ƿ|}l}q}g}l~Q~b~d~N~o}||e~R~h}g}~}|{m}r}P~W~Z~s}^~}Ɇ}t}c}L~y~·}P~{}z}b}`~F~c~G~=~D~{;~̾`}{}L~̘|Ж|s}Q~i}|e}ّ}|ܖ|Kނ}|}p~{}_~Q~T~b~X~h}Mc}p}j}ʄ}}[~Q~ʻ`~÷]~xB~u}E~F~F~V}зW}̲U}O}P}[}T}t|U}Ʋ^}]}T}ք|T}N}|p|yJ}k|}|w<~m}]}\}x}}AyH}~@~N~c~A~<~{I}r|^}N}N}e|a}\}d~}N}N~J~z=~U}r}U~F~a}d~ľl~Q~s}V~O~M~}|Η||Ǿh}r}l}A~I~l|K}K}N}y8~J~x|ɼc}}<~ο^~V~F~m}[}Z}Z}\}H~H~F~U~}M~S~\~O~`~e~D~B~M~G~F~Ź`}F~T~R~h~e~w~w~n~h~N~_~g~b}ɂ|~|T}?~b~F~G~\~\~@~C~õW}I}g}O~ɵJ~ƵH~J~=~;~ìC~­G~v0v/T~`~`~u2J~f~\~´V~F~Ǿc~V~ŷW~Q~t~[~e~Ŷ]~Ź^~X~F~X~[~c~r~j~`~{=l~k~e~|~\~s~o~k~A~V~\~{6[~`~I~;J~G~R~_~W~˷N~U~J~G~x3J~=~a~\~w5f~h~h~W~N~Q~b~@~<~d}q|ǺU}K}P}O}>~]~¶M~?~;~j}~<~Y}~F}i}Y~[~Ǿ[~ǽ]~[~Q~}}o}lj|z}S~p}Z~m~V}Ϩ{||߷|{{x|{{{{{{|{{r|֐|J~`}|c|=~J~;~G~@~ʺP~U|Ϟ{v|<~C~M~ʽY~Ǹ_}EY~W~X~;:;X~E~y=~9?x2ķP~j}M~ûd}J~Y~|ƅ|\~ǻT~D~S}^}}`}e}d}W}}@~[}d}R}b}c~f}^}||u}T~\}|}||O}ۑ|N}G~D~B~|n|f~|}H}t|N}|źs||{{{{{|b}S}ĸd}O~w}N}Ƚ}|S~Ⱦb~̉|p}d}A~N}c}Y}P}e}_}_}úk}o}V}ŏ|u}r}ȅ||i}D~W}~|{{{{Ր|[}b}k}]~I~a}l|{{|s~X|O}i}ƷR~|=~`}I~X~X~Ļb~}|z}C~V}R}W}b}Æ|̒|U}o}M~Θ|Q~Ĺh}|ȇ|q}b}ž||Z}W}W}c}{{{{{{h|_}ԯ{|U~B~_}|o}E~N~|}^~o~K~E~]}}}}r}~\}X~s}P~K~|}żq}Q~c~i~ƻ\~D~}c}|Ξ||l}J~d}V}ў|I~e}i}m}k}`}}D~J~F~]}|ٔ|U}f}p|R}Ļ|d}E~i~u}I~ĶY~}U}_}||||¿||a}c}|g}Y~N~߆}р}|΃}|]}{}O~}Q~م}R~҈}ӊ}؆}}O~q~~~b~}}|s}Ӌ}m}||||}r}}}p}M~ӄ}U~\~]~{~Y~O~}}}f~j~n~N~}s}]}||ݭ|}}}}}`~}g}s|ÿ|}P~}K~P~W~]~K~J~b~^~_~v~~~j~o~J~a}T~z?~G~}b}Ɋ|w||a}U~l}}r}{}|D~}G~}ʄ}Џ}}||g}W~}Ք}͋}}}}U~}Z~i}}q}|n}~}}ɍ}y}z}y}»l~p~[~W~~}յ||}}Y~~}}b~W~`~J~}T~˸||m}}u}y}_~}}a}d}W~e}||}ԋ}ʈ}Ս}Y~}}P~V~ľ|}g}z}|ݿ|ū||||u||{|ݘ|{{;{{{x{|{zyx{z{{{{{|||͸{ʽ{{{{j|{ҿ{{{{{{{{{{{{{{{z{|{{{{{|{||{|{{||{{ܸ{{տ|i}q}|||}}i}Ί}j}y}n}Ջ}½}}}ǟ|۳|||||}n}W~}w}}s}h}k}\}||{|||||s}q}]~_~}M~܇}k}||l~L~}}}|x}E{>j~~}k}~a}x}c~||ȃ}|k}m~Z~|||ֆ}g}|||v}Ś||{}}|}{}|N}۠}F~W}ҷ{|_}ū|{|{{}~|{{}a~c}{ſ|n}΅}}M~R~z}|v~}i~x}k}|q}}W~ː}~I܄~H¼m~Fk~CR~|G~|||||||Љ}|z||{{{|x}ڏ}k}m}J~J~V~}x}R~o~x}i~[~\~Z~}}|}p}}}øZ~[~w}||n}u~P~_}r}V~X~}u~X~m}`}Ͱ||џ|a~B~?e~X~}Ǽd~j}|ȇ}|d}f~_~d~ω}|}}Ջ}r}||M}{{|g}¼w}Q~Y~J~ǿh~|O~`~i}|>t|||b}`}Z}}}}T~E~~A~R~\~M~Y~|c}ƾd~T~]~r~V}\}V~Q~w}ެ||Ҏ}Bt~}|I~k~w~Z~{}{}U~\~}f~ـ}}}R~r}ĺWP~ijW~H~A~=~{7~r1~M}y@}U}xG}h|s|Ȫ{˿|a|{|Q}i}wH}N}˹z|̺r|L}t8~i}x}`}s}n~A~B~G~M~I~?~L~{9~\}]}|}G~s}s|c~\~O}n}V~`~t~q}N~Z~½}z}|v||}g}¹^~j~ƿp}y}ϖ|||qE}S}L}b|k}k}`}e~H~R}|R}Y}ޘ|X}X}b}a}Y}\}T~p}a}r}e}}H~K~H~v}J~P~M~D~R~ۘ|q|y||Љ|b}H~F~U~^~T~}}]}S}D~g}~|b}b}~@~J~8b~]~óC~f}~:~K~T~|<~i}Y}]}r0~X}ǴQ}E~s0ԻI~ŪA~?>y3w6:e~`~÷[~J~h~O~J~|>Ż]~¹a~n~;`~e~V~j~c~a~c~u~@m~g~h~n~m~a~u~JO~Y~8^~7i~W~`~g~[~8g~ϿV~ѽR~ǰG~ڿM~J~®L~{6D~e~g~d~d~b~U~ŸW~X~|;\~T~<~z<}C}{7~c}Z}O~:H~X}U}y@}c}l|~7~Y~Y~g~żV~a~g~E~M~}J~Z~b}þs}i}c}n}a~þv}|ƣ|n}{w|o||J}f||{{{z{{ն{t|ϊ|U}T~X}a}F~z5~;H~6~K}<~_}L}ϻ\};~=~ѽM~@~V~AY~d~\~;b~z7~6y2^~?I8F~?~K}{{=~E~Q}Ƽe}c~|>~{}Π|b}`~e~@~V}R}O}փ|W}|q|^~|i}_}ſt}k}|||}A~V}v{|õQ~O~@~||e|zg|r|w|z{{{{{{{|M}N}n|y=~_}g~l~M~v|K~P}h}n}X}}|S}E~}q}|e}f}k}÷h}j}}h}]~[}q}ܤ|}Z}}|Q}ԉ||{{q|z|a}J~=~_}}?~ʁ|{|d}L}n|{<~H~>~A~{C~¹]~Al~g~^}d}|O}t|t}s}||b}|]}\}v}s}_}[}m}|͆|̉|z>~Z}{{{{n|ϻ{{|N}I}d}|e}Z~ŸU~Ò|½|n}G~e}k}D~t}_~Z~[~z}s}ƾm}{}}ۇ}x}t}x}zE~p}m}|K~ه}l}d}u}c}||{_}݅}==I~}m}||||t}s}]}_}c}s|{||k}̼{||؋|Z}\}]~L~K~W~S~S}|{|y}߀}h}ˀ}L~}I~N~r~Ľm~^~u~Z~[~Ո}t}d~}}ł}ʈ}M~¼x}U~U~s~X~b~¸b~h~c~Е}ۼ|P~Dž}||n}Z~}U~q~g~_~n}p}|چ}}}[~}Ђ}P~^~y~H~K~n}Q~~O~_~d~i~}~i~T~K~}V~S~S~ۑ}܄}}J~Z~h~I~O~D~{}w}Y~|}W~U~o}Ψ||}F~q}a}zD~}ǀ}z}k}q}~H~}}}e}s}g}|H~V~P~^~ѕ}}Ζ}|ٽ|ƕ}}j~{P~Ď}Ȕ}}}}v}}|}}}m}V~S~^~M~W~ܚ}}g~U~[~a~}v}}[~E~I~K~}}x}v}|w}e}i}}y}t}^}}}c}|ƶ|и|||}||[}~}ُ}}}||||²|g}Ӷ|ҧ||||||÷{{z|{{{|{{zzzzz{{{{w|{Ϻ{{{{з{t|{{{{{{{{{|{{{{{||rs||{{{lm|{tv|{{{{{|||ww|}}|Ӛ}u}|}ъ}Z~M~]~o}H~}^}h~}}׋}Ǖ|||Ս}|X~}܅}~}|Q~}}P~K~W~\~||ƽx}||f}b}w}p}I~]~א}l}}^}x}Љ}h}E~R~}c}||w~o~_~~k}]~Z~Ӈ}Ւ}i}||||Ɯ|v~ڀ}|k}Q~||{|{|נ|̢|‰|i}ȯ||ʊ}͆}`}^}E~x|{}i}||{ܳ|m}{o|ه}p}|˳{ŝ||ſk~T~ǿ|}X}i}|V~߁}^~m}w}}}yC~}F~}І}v~܀~q~~VR~}T~||{{|ww|uu||ҳ||}}}|f}M~O~l}^}[~~}|M~}х}~ž}d}Am~}L~Ǿe~M~p}z~o}}}}{||m}~}M}C|S~z}z}V~j}{|]}j}c}R~d~V~x}R}N~̂}{||||||a}M~W~ƿ~}ӳ|w}~c}i}ɕ|||||щ}}^~q~p~I~U|T}{}k~v~u}Ճ}}k}a~Z}|T}P~}e}m~h~FE~^}a~m}B~Y~^}ӧ|\}π}v}H~Q~{}a~[~{}\~c~y}V~~X}»|υ}}y}r~x~}t}z}K~̾[~s8~h}K~ʲR~H~q3~Z}s=}N}]|k|ʽ|wK}m|O}v|`||´`}s}h}o}F~[}վc}Q}H~||R~\~c~G~J~P~L~M~L~;~V}W}`}|f}|؅|^}T~~}|}Ŀx}~}T~V~|e}g~ȿp}|E~Ľw}U~s}`}Q}ل|R}V}N}pF}Q}@~w}^}ù|ю|ǿ||ž|Z}M~s}}y}o}a}}q}Y}l~{}{O}e}a}c}v}H~G~c}zM}Żg}e}Z}c}|{|~||s|x||Z}a}d}S}vC}~|ҹ]}̵j|q6~<~F~K~b}j}|>~M}?~M~B~J}M}Y}t|Z|K}z5~=~@~Q~ҿJ~155z.u0Gr~h~Ⱥ\~ȿ_~H~ʻ[~l~Ļ_~]~k~j~p~Ļ_~m~g~̽]~f~I~N~ƻ^~o~o~EAd~u~l~A}:?b~f~L~E~μP~[~T~ʼY~ʾX~@W~L~[~L~X~R~U~[~ǷX~ҿQ~9?CZ~m~n~ɿa~`~X~T~k}Q}i}i}U~~83P~>~j|Q}]}X}<~E~9~n}]~ƺR~h~B~O~}B~x|½o}k~t~}`}ق}}b~i~E~{s|d|{c|R}E}C}]}]|ζ{{{{{o|{Ώ|L}A~w|F~D~<~8~D}R|y|z8~L}ֿW};~R~X~A~X~1óL~M~d~?LIG6ƺO~}6l~p}ƹb}Z~P}|s}^}|o}u}Z~d}a}I~Y~d~¸U~Q~`}|׋||||ر|£|||V}c}W}{y|ɼ{k}O~}J~Y~`|k||n|]}U}`}D~W}~H}|{{|{{}|̈|o}r}¸a}N~Ƃ|D~v7~J~Q~j}D~E~z=~||z|M~d}{}ƒ|~}e}}N~ˈ}N~P~~}~}j~٠|W}|E~}U}{{{{{ɘ|_~]~`}h}ӄ|ܝ|]}B~n}ޑ|\}z=~>~F~h}k}G~d~ȿW~W~R}|Z}z:~o}B~>~x|ܑ|ˊ|{L}µ_}}>~[}F~u}||̿k}}||{{{{|f|}|`|ƾ}|^}a}D~\~Q~[}|}d}U}|l}Q~r}t}q}}}u}|L}|}Z}v}y}k}m}^}|i}|ġ||a}}p~G~S~οZ~K~c}>BW~O~Q~i}|||f}v}o}<~m}|M}Q}P}̈|w|{b||O}w||}}L~h~e~Z~}|{|~J~{I~}}^~a}k}w}}j~a~n~o~E\~؀}o}ф}M~}}}Y}s}S~Z~U~`~ûq}U~d~Z~[~_~Z~ۊ}|{{T~I~ȹd}H~`~V~܊}u}׳||||Ɂ}|{||}|~x~FI~^~Y~}ދ}h}s}w}b}|f}i}^}٦|k}r}|d}t}}Z~ºd~K~R~K~R~R~M~G~^}|u}T~u}|\~}Žu}۰|}L~r}د|}}w}u}{}g}w}ǿ{}\~Q~ѕ}}͸|r}v}t}V~}}½}֔}}}}V~[~T~̓}}e}f}g}˃}P~U~Q~Չ}û}}א}}o}}¾}|}k}|n}o}Ľr}y}}}a~_~^~Z~u}m}w}Ժ|||||||Ϭ|l}j}~}}}|||{{ˣ|c}Κ||ƪ|c}|f}f}xy||{{~|||||ݫ{{{{{{{{|{{{{{{{չ{{`|{{{{{{ž{{{|~||{{{{uw||z{|tv||||pq|tv|||||{||{rr|{{̒}q}|M~^~P~}}V~Y~}g}r}||׍}t}x}}||ؒ|ױ{{ժ|o}Ԏ}}}|K~}t}}o}z}||p}Ӈ}}x}X~_}ˉ}|`}Ɠ}΅}f}}Ղ}_~y}e}}x}V}s~|~e~z~X~a~~I~^}|{{|~}d}||{||}ٴ||{|g|a}}x}q}||ۂ}|A~^}W~x}|{{|||}|||[}ص|Ʉ}}g}j}}^}]~|p}q~v}ٺ|¼~}O~҄}|h~M~l}؃}q}Q~M~l}y}g~{~L~¿|Ԯ|W~||||{|~}|}|N~Ä}T~j}͆}}b}{b}P~}}}e~٘}}F~h~Z~z~ކ}d~T~o}s}}j|||zz||u}j}b}ɿo}W~~}N~G~B~T~ȁ}h}|ߚ|ӓ|l}g}ùn}|_}|ܪ|}~|z|||||{|||~Fք}d}}|||Z}||{|||j}L~m~}GX~s~v}v}_~Y~V}M~w}I~N~]}m}Z~Y~}T~s~~I~f}O~m~ÿ|z}g}|}N~S~^~f~j~{|ز|}]~½y}|}[~~H~r}l}W~h~f~G|?s~h~^~b~L~z}M~?~H~X~˺R~I~H~ռa}v|l|Ƶx|K}L}tF}N}^}]}O}N~o}f}j}O~ʻf}s~G~}~t}\~]~L~G~S}C~Q}l~}}|}U}[~|o}s}\~M~ʻV~@^}Ϋ|||}@_^~D~U~P}}l}b~S~}E~w?~N~G~B~d}{A}I}ӂ||O}W}W}a}p}M~J~N~N~y}z}B~j}H~r}g}v}]}Q~r||T}Њ|ʇ|Å|}l|v|{c}O~Ļu}X}{}Ȼ^}m}K~t}O~U}ԍ||T}ݏ|Լc}~|N~͹k}=~Z}k}A~c}ޅ|ǻ{|w}Y}U}S}Q}M}~M}X}c}^}e}>~R~4|/|.<u/V~==y=B;k~X~F~ξ\~f~ʻW~J~e~i~i~k~p~o~óW~M~C~K~d~ĹW~}<Ei~_~b~m~k~l~ǻW~a~K~~>~=~R~f}?~?i~X~W~\~8Y~\~_~X~Z~`~u2`~m~w6@c~Y~A~K~w}]}L}I}l}R~h~H~\}\}[}Y}B~S~[~h~^~S~n~f~f~a~i~žc~n|K~`~c~v|g}e}׀}x}g}r}g|֒|ҵ{o|\|~>}_}ôY}yD}{{r||m|{{{{q|ϊ||j}z:~T}=~c}v|Z}n}C~C~?~мH~O~E~Y~H~c}a~d~7;D:ƸS~}@~a}<~q}C~C~[}M}||\}}?~Џ|p|~}W}|f}I~c}K}]|}_~|}b}ץ|c}|{{}z|l|x|h|{Ĕ|R~f}}T}m}8~K}B}ǺY}M}]}Q}b}`|{n}|||e}O~H~U}}D~?~[~j}?~=~z3\~e}]}~}n}P}_}f}f|||u}a~}}o}g~E~{}}d}p}w}m}g}|V}X}ʿc}`}x}܂}J~E~k}h}|v|ґ||}f}|m}M~u}P}e|؏|b}n}V~C~@~d}m};~R}{q|n||΍|j||=~<~b}}@~k}{L}~i|¿{{ɿ{u||F}{{n|y:~=~G~W~?Ⱦo}T~J~_}\}v}L~¹k}L~O~~C~~I~}S~T~|}Y}|||||˯|u||}m|ˆ|y@~H~t}G~G>d~P~_~a~v}i}||{|[}_}p}ٍ||ݘ|p|||Q}yH}Ѷ{{u||T}k}=UFR~|dž|n}}j}գ|f}u}}}}p}Ƀ}U~}M~r~ƾh~\~P~}n}e}h}„}a~_~a~d~c~X~Z~Q~}n}}]~||{Q~}}B~}B~P~I~a~M~z}}w}|Ҁ}}{}Ҙ|||_}}N~s}{|g}x}q}ۭ||p}{J~|}ֆ}ɂ}|}^}۶|{Ф|܌}ż|}ùr}Q~~}L~O~z}e}}Ǿb~s~{H~|F~Y~|}p}}I~R~e~p}ڿ|{}}ˆ}ȋ}}}f}¾|j}x}}|ĵ|ݖ}~}n}Ҏ}[~ّ}Ã}}Z~}|Ի|r}Ȍ}|l}h}l}}||l}}}}޷|y}r}r}T~֐}޼|Ī|ș|^}ʪ|Ӑ}}z}a}u}͆}q}n}}|||o}|ÿ|||n}w}||{|||j}ľ}z}b}||´||}}f}һ||||y{|{{ݰ{{q{иz{{|{{{{{{~||}|{{{{{{{{{{{{{{|{{vw|{uv|{{{{{{}|rs|op|||||{{|{{{}b~g~P~U~ߓ}߇}}Ȋ}o}t~ّ}f}|v}m||||zz|{||z}|Ń}}~N~ғ}o}}f}}~U~̩||o}o}}ٶ|k}^~ȯ||i}؈}ό}Z~ſ}R~Q~s}j}{@e~Om~¹g~`~Y~y~y~\~¸||vx|vw|||y}٣|ߐ}j}k}p}ߗ}|ȳ|||u}|T}Y}ӆ}c~|i}o}}}_}º{|օ}r|||{q}|}~||||||f}b}c~}M}|ӊ}؞}|{}^~ˆ}}FX~L~~V~}b~]~o}V~~~u}´||k~g~Y~ܿ||}||p}p}|||}Ӽ|c}}>q~{}|k}ڃ}K~p~}c}݄}}O~[~Z~U~BY~z}Ǽa~M~R~M~J~|j}|{}~|}}||||}|c}̋|r}f}}f|ū{^}o~n}G~zB~۠|ѫ||||||||{{{|l}T~|Q~||Ư||Ա|o}n}{||{||ʋ}}Z~{}`~b~}˨||]~s}R~V~X~{}n}h}o~O~O~d}u~Et}]}H~m~i~ƿs}R~W~@~]~ǼX~S}{|ȫ|\}|}Z~}~~}R}^}}Ȁ}T~}}k~N~Ϟ|]~xB~w:~w7~ñP~X~e~d~X~E~g}{B}zE}h}X}k}V}]}W~C~n}C~ǿg~{}܌}P~]~v~N~k}o}S~Q~ż|m~|}Ab~BDz}^~i}q}b}p|~j|zK}^}I~Q~]~}p}t}|}t~`}x|u|b}x}z}ĸb}o}N~N~W~дM~D~@~X}E~A~j}A~E~?~G~x9~p}a~W~V}s}C~d}||؍||u||a}|t|ºp}\}R}^}||N~>l~f}U}?~úd}g}a}}T}|_}^}e|V}v;~E~D~M~?~Q}ŮU}<~X~d~]~S~T~B~G~H~L~s}H~a~O~x4:x4[~y0Y~V~T~=x-X~{<A=q~e~=~J~b~U~@~N~a~n~Cp~M~Z~U~^~J~V~X~P~f~k~|8};j~v~i~o~{7M~D~E~};~F~S~f~X~H~L~ѿX~h~d~@b~~8g~x6z8p~P~Y~?~D~n|V}y<~g}W}k}ψ{v|[}³U}m}A~ƹK~J~R~W~Q~Z~?EWX>p~n~g~MR~p}D~z}y|}}}}{ȋ|Ȃ|~|½x}}}|b|F}ŶL}h}l}ſ|{U|I}K}߮{ب{{ծ{{zF}||=~}:~>~C~r|ĿZ}7~@~f}Y}e}C~H~E~q}=~ȷN~_}W~_~b~w4X~X~c~ǻR~s}I~{}q}W}m|û{|ߓ|~|C~v}e}{v|{{۴{X}I~{:S~l}{{؍|{z|{{r||||w|{g|h|h|u|B}d}S}S}~|||{k|{{{j}k}b}u}~}H~g}|˗|M~f~>s}Žg}ƹU~J~K~C~Z}|}ދ|l|`}[~[}||t}ʁ}i}ڈ}ڀ}k}c~s}||||||||=~j}i}֐|N}wJ}R}S}j}[~C~o}N}ſ|W~b}e|T}J~f}L}ĺ{|و|||u:~u1B~øh}~@~a}]}\}Q}g}R}û||~Q}݊|C~Ži}ړ|k}C~@~|σ|υ|n||]|ԯ{ģ{{b||q}K~L~E~r}d}}}s}d}K~N~p}||g}_}|έ|u}c}Y}|^}|K}{||\}D~D~Ԏ||b}C~O~ù]~w~{@~M~P~|||q}ɭ|}}p|l|܊|{V}||W}Q}\}L~s}͋|ڒ|Ր|Ӑ|t|b}s}˾`~o~o}h}ۅ}J~J~}f}Վ}K~}|}̓}|K~}ӂ}S~h~z~\~Y~I~}}zN}||i}T~P~\~~M~S~\~a~[~r~o}ǿw}|J~ƿ|}h}Ӥ|}}P~P~O~c}M~a~Q~W~[~k}g}p~zD~U~]}~||~}}~J~{}]}||N~ݑ}~T~܏}t}̓}r}M~g~W~n}||Ɔ}}h}D~U~}I~}Ľc~g~Y~y}]~I~V~Žb~q~\~T~U~a}x}d~w}ǿ}}R~۔}Г}n}ˆ}}v}Ӓ}S~}{}}ƾp~o}i}L~t}Ȅ}Ň}{M~|}||֔}}ה}|}s}y}}h}||}}ě}~}}|v}e}a}^~h}v}i}g}}j}|}ĺ|v}t}e}x}}ÿ|||||}|p}}}|r}}h}w}|||f}Y~ֲ|}Z}Žy}|||}҆}|¾|||{{|{}{zz||}|{{{{xy|{||{{{{{{{{{{{{{{vw|{ll||pq|{{z{|yz|{{||{||||{{{{{ѽ{l}}Q~[~ûq}u}Щ||k}}}u}}|t}}|||zz|{|{{L}\}ҋ}}u}}}}}s}]~Ӓ}u}֗}}R~|u}ʋ}}X~zG~Ȇ|}}}m~}Ǽc~d}}}j}b~|}ɾ[~z7·[~o}s}l~S~U~|||||||ԑ}{T}}\}|}P~׫}|¿}||m}}c~U~M~~Q~v}ɍ}O~zD~\~ă}Ӌ}^}s}||އ}||||h}b}f}a~σ}|F~|Y}p}||½||O~x}p}W~[XRƽh~~R~|Oi}}Ǒ}v}|||}ebJ͠}||ݩ|||||Î}a~i~|َ}q}}~}[~}}P~`}s~s~٢|]~v~R~N~E~y}k}u}E~D~Ձ}b}i}||||{|^}|Z}Y}{?~d}K}p}k|K~f}n}n}u}|?~A~}\}||z}l}||{g~r}r}a}ϙ}r}O~d}¿||||||{z{||~~|t}ҍ}|{}Β}q~g~i}|p~{}р~Q~q~ֆ}b}ԙ|m}|q}C~c}o~}=\~}T~l~i~o~P~P~`~z7T~}A~Q~||R~b~u}L~k~||||r}P~o~}~{z}S~}{=~D~]~\~?~ƲM~E~D~9~>~`}H~L~ȵ[}g}k}B~K~]~}d}b~|¾|k}ü|Q}W}f}L~g|o||V~z}}H~Վ|[}d~|h}ĺf}P}ٔ||}x}\~S~F~\~y~ü`~˨|l}K~}|ƽa~d}^}O~q~K~߆}JEa~E~>~<~t}?~D~o}zB~K~?~o}N~G~Q~S~c}Ǒ|V}Y}M}Y|ʖ{ĺm|{Y}P}Ǹw|S}C~I~T~7ķY~y}B?m~O~p}u||\}zR}S}y|u||U~V~f}^}U~T~ʷO~H~O~Y~|:l~AXBa~q}^}k}f}:~S~8=|8DĶQ~`~D~c~O~U~^~M~[~~?Dm~V~L~S~7W~z1~y8~[~A~]~h~N~J~~}K~H~X~i~K~}a~w~U~p~z;o~S~Y~U~G~F~E~H~I~m~I~ķV~e~k~{9n~a~~=e~O~i~c~T~x;~I~[~d}ɽb}S}r}z;~I}Z|E}t6~B~K~Q~V~]~T~L~ȾX~a~s~@[I~`~z~n~E[~L~\}|֒|ٙ||ϗ|o}X~O}b}Z~J~L~s}L}U}7~h}x|~|F}a}{}o|Y|xN|w<}Q}S}q}@~ҿ`}@~V}i}D~<~l}f}5~>~W~C~J~U~x2>G~G~P~d~Bc~O~C~r}C~l}\|a}O}P}||T}x}r}t}|d}q||{̳zȎ{P~B~{{b}ƽm}{{{{{Ҽ{{{xa||}|c}j|S}v|u|ƿ|g|Z|{{̞||{|{{{s||l}x}^}΃}||E~k~:Y~M~T~l}p}v}A~Źc}D~?~?~\}|<~E~^}r|~|zL}˿k}[}g}K~t~}}M~q}t}w}|{V}|V}W}^|}|n|L}V}V}҉|Ą|ю|w}?~G~L~e}T}\}I}R}f}q|{~||̿i}D~]~{}I~øe}Ǽd}b}W}|||{wx|mm|||O}z|I}x7~ƽz|m|ׂ|ƺ{|h}ĸ`}Z}J~Մ|ǽk}_}|c}u}a}L}x}¸k}_}ߥ|a}`}ܝ|ȣ|ǝ|Č|||n|G~M~{?~ºq}^}|ҋ|z|{|_}C~g~µR~d|ۅ|J}}V~T~h~l~c~g~}ɡ||β|}}}t}_}z<~Z~w6M~j}T}[}X}Z}X}|`}X}|j}ûv}K~L~}݄}r}ƾh~N~P~M~a~m~|G~~I~ɿt}z}Ļp}n}m}Y~r~úd~S~K~}^~e}|}u}i}}a}¼z}|H~d}a}N~R~}}~|U}j}ž|Ⱦn}ƻj}K~T~p}e}b~^~}M~V~|e}\}Q~{G~b}أ|ݬ|j}x}t}o}K~S~[~_~S~}ч}}Q~}M~S~Ǽf~_~P~k}g}_}n}R~O~y~~F~v}}M~Z~f~}S~Z~K~e}ݎ}|}}ɍ}Ϗ}b}҅}֐}[~|N~}s}V~ۏ}u}Љ}ڊ}}b~_~i~ʿe~}d~w~Y~Ć}|p}}u}O~{M~Q~a~}x}m}~N~d~_~W~P~O~O~K~}}}S~ߕ}ы}{}|}|g}v}|k}~Y}|||||j}u}k}k}|||||d}j}|||ג}h}ȷ|Ϸ|˪||Ƕ|l}}}b}׸|޺|Þ||{y{|{{{{{{z|||~||{{|}|{|||||{{{{{}{{{{{{{{|~|||{z|||q|p|{|{{{{{{vt||ѩ|}r~^~R~Y}p}|_}ћ}||||||||{{||{||Ă}n}n}Z~r}h}}}H~||}|j}u}u}S~}Ƕ|~_|{|}^~k}]}Ӡ|`}~`}|ܤ|`~p}|||Z~X}Ļa~}ʼ|{||k}}|||a}]~}||}پ|||f}}}S~d}P~O~M~i}ь|Ǩ|r}b~a}|l}}|ܟ|I~{}|o}o}b}V~}W~y~x~ݚ||}}?_}}XV~¾||‹}\~i~z~h}||{|||ſ|J~LT͆}{}|||||||||||l}ׁ}}W~Ǿl}[~|I~úo}l~a~}Y~}o}H~R~H~R~p}[}z|Ι|{{{{|V~~}O}|}t}Ǽ[~n|K}W~_}I~d}||ѕ|T~H~|{|{{ۧ||Ȅ}†}Š}v~h~~L~Z}|I~ƿs}z}S}||||~|||||Gt}x~|o}i}z}g~G~w~҉}z~}k}s}|̒||Ѓ}_~L~e~IW~y~¼m~O~u~É}g}»`~b~R~a~j~R~ҟ|~i~t}h~b۹|}¼}̍}Ca~]~v||}L~`~R~t}R~?n}H~H~?~9~~8~Q}±[}k}~}f}l}I~P~|;HUa~˃}o}r}s|͕||Ҫ|q}}|h}Z~K~m|]}V}^~g~ю|Ϗ|b}{I~ك}|]}x}ז||e}o}G~ˍ|y0H~j}ӟ|||T}Pd~J~_~°R~E~C~H~ǸU~P~H~{}X}w}F~E~|D~e}Z}¶`}j}u||Ņ|T}R}W}W}{_|B~O}C~]}G~6>ƻOL{~Dj~J~Ƽg}L~J~ùw}Z}W}`}h}E~J~w}^~M~t}_~N~C~t}f}P}E|8|G~x}Y}s|q}C~T~e~i}|?~|?~̺W}?~<~<~>~F~q}}6~e}F}}>~P~P~V~ʺR~89W~S}f}B~H~K~i~W~Q~M~Z~¶_~E~N~}H~X~U~L~o~o~N~B~S~ƸZ~˷Q~˲H~e~k~?{~]~t~V~ɽ`~o~|8e~̺W~Q~i~ǸU~ƺY~C~P~P~|C}S}l}q}K}d}<~̸Q~ѽU~b~S~U~L~|?~c}\}Ļ_~e~m~K|>~T}V}ļj}T}Ȋ|G~k}|Y}e}n}}E~}L~o}Z~k}|}^~Y~i~i~w|ɹW}b}N}³V}Z}?}|j}|9~X}a|F}3~<~ƳH~t}|=~C~B~@~źJ~h}S~L~B~»F~y/8~O~y/M~KSB~}i}g}i}h}r}I~p}k|{i|U}L}S}q|{||^}d}]}}}}b~{ڣ{A}Y||{{{{I~Q~{S}b}ȳ{S}̻W}k}F~C~ȇ{b|s|c|s|_|֒{_|i|x|{{{{{{{|||{||q|†|b}K~<_~>~\}R}z}{}ĸe}<~g}Q~C~l}@~T}}K}~|ȿ||׃}z}K~_}f}O~b~}}U~N~փ}|{{ƾ|ݐ|}H}Z}т|{Ǹ{З|Y}Z}E~j}q}A~Y~B~v}p}}>~\}ގ|l|j}z=~}?~}B~u}C~p}A~d}k}c}[}^}Ǿo}ӗ|z||F~Q}s|x|Q}wF}։||L}k}|W}9d~l}}L}|\}~R}zH}L}|b}ߨ|Ȥ|t|v|S}Z}Y}}{}_}||E~W~K~{}R}ú|h}|s|ƺ|[}H~9z;~y}|B~J~G~I~W~ļn}I~ǹ\~;Q~{{{{|~||a}}k~k~w}[}|T}Y}j}|ԕ|Z}Í|^}t}^}ºy}v}h}y}]~f~Q~}}[~k~d~F~c}p}}~}I~[~z~Ey@k~ȼc~n~H~n}ƿ}»}}S~ӂ}I~}h}p}p}|Մ}g}m}Մ}~G~U~M~}}X}o}_}ބ}o}}Y~U~c~k~o}L~}ݫ||r}i}i}y}V~Z~]~ȿi~M~K~V~b~x~EYs~V~l~a~b~a~}~i~g~Žc~e~[~^~U~}x}̀}^}׆}Q~i~_~܇}h}߹|d}c~}z}р}j}~}ޕ}N~}a}Ɇ}}f~_~b}V}ߌ}T~K~}X~~ޚ}}}}W~}d~N~ý}ΐ}Q~W~{}}އ}q}ڊ}c}||||s}|q}o}y}|||~c}l}Ԯ|~g}t}ٖ}}}a}~}}}}W~P~r}t}l}|||||||x}|||g}~j}p}}X~}}}{}߉}p}||wy|{{uv|{{xy{{x||{||||||||||||||{{{{{{{{{||}k||u|{|||{|xy|wx|{{{{{{{{|[}o}܎}|o}t}|u}|s}ׯ|||{||qt||||{||~^~}}ԑ}s}|ۉ}c}xF}ڭ|n}}|N~}W}Æ}f~||¾||Ik~|}}i}̮|}ē}s}Њ}]}ÿ|}~U_b~Y~q}|||||ǿ||{{^~^||r|||{||^}}}}J~G~e~h}x~}|{{||ˎ}Ԉ}|o}|ة|y}Ķ|u}}Lk~}|h}}i~S~H~^}|||~k~{|w}~R~}|||||~B~K~|j}{}ه}Ǿt}||m}p~l}||||x}O~}l}{K~}\}H~d}G~Q~g}N~U~S~}R~n}o}F~m}ǽg}]}Н|u||{{{{{|O~|Ad|V}|ĸ\}l}f}Q~j}J~c~Q~ƽk}_}ٮ||{|||||r}{|΍}}i}CS~||ýo}g~`~[~ߞ|{z{|wy||||}}J}|n}v}}\~L~}P~Y~W~ڍ}j~}~|`}y}}p}l}W~a~S~ſo~Z~K\~u}ſk~܄}M~|}j~Q~v~EX~J~Yf~|Õ}u}Q~P~MH~o~|?n~u~ς}Ǻp}}C~q}N~I~w7~K}R}ļ||Ԛ|||f}I~L~H~ƽf~o~R~Б|}}u}r~H~ſ|t}|H~C~I~M~d~y}i}{{||e}źl}ûm}}}{||ľu}{I~u}|ك}{{|||||c}V}S}u}B~C~z<~h}d}O~E~x:~A~F~}W}Ǒ|z|X}i}}>~J~T}ǼZ}a}a}>~2~R}:~@~I~@~o}ĵx|S}c}\}p}\~d~Ĺi}D~]~}~Z~T~~}}ƹa}T}|m|u|l~?Ȼ^}J~J~t}R}n|k|g|~|||G~W~u}E~B~|µy|k||}xG}R}z7~<~X}²R}9~x5~K~ȴF~6~U}y0~P~N~ȵL~O~T~2K~Y~H~Z~G~k}D~~=~I~Ź[~Z~h~c~W~[~V~L~O~I~H~Y~}@~J~X~L~Q~T~8D=;j~c~Y~ķW~}=i~˾Y~b~N~`~a~W~Q~f~W~T}M}d|R}A~t}k}j~K~\~h~n~H~U~D~~C~L~W~OD}O}ַ{ж{{v||{~q||}v~n~g~}N~l~j|O~m~q~<y8||ȍ|~F~~@~·b}k|x|w|B~Y}R}^}{`|`|D}p}y:~M~w7~i}A~B~T~V~J~-V~A~Z~L~t*J~?K~w<~ߘ|Ƽ|ŵm|b};~S~K~K}ùh}a}]}d}c}K}V}ǽv|ü{}F~k}|i||G}{{{{|½||`}y|b}q}h}x|h}D~·V~c}M}g|i|zzβ{o}w4~x8~p}{=~ۃ|f|{{{{{{ƿ|~V}{O}w|{{dz{{Ԏ||C~]~¶U~a}N}ƺZ}U}{<~9~9~?~};~ӆ|˿{{{{ڨ|{{xA}Ѿ`}z<~[~EX~`}C~l}[}{{Lj|k}{m|b|{{Ç|p||y<~I~@~@~T~M~|?~X}h}G~T~ƽ|}w}E~_~]~|}a~~=~l|l}ƻb}`}Y}A~x}h}Y}{e}DzO~k}Q}^}Ѕ|g|Ķb}ɻa}|I}{d|X}J~q}i}||ŕ|||լ||P}}F}R}Մ|\}B~w}q}|O}U}x<~N~`}òY}j}w|V}J}_}r|{d|Q}c}?~v}b}j}Z~_~K~Ⱦ]~Q~ƻ`~g~Am~ĹX~l}{||{|p}n}ݿ|y}H~}|_}o||e}_}p~F~Q~M~ߚ||P~||k}X~S~u}^~^~U~X~^~a~}|K~j}{}l}ň}P~}}}P~g~y~yA~l}|]}k}|j}||€}~}}¶o}|]}n}c~e~U~K~ۅ}P~{F~I~؁}|}R~u}g}|n}S~d~d~Ӏ}}}}|e}Z~`~s~|~\~Y~}}Ň}Ї}}}ς}Å}ύ}T~Z~f~x~[~f~}J~ք}}Ǿt}]}[}|{|a}҇}Q~W~}|̻|n}p}ڌ}t}c}m~|}}t}|`}|n}Ŀ}u}\}J~Ї}̑}}ȏ}}}}}ږ}i}i}͇}e}}O~Y~u}ʼn}ݝ}||h}j}ߺ|ǃ}Ɉ}ˆ}u}x}|}ݜ}_~}}w}g}u}}_~n}d}|Ľ||}y}X~}†}Nj}v}m}t}|¿}}}\~|^~a~\~W~T~x}|˱||n}Ŭ||||{vw|{{||{{z|||y|x||||||||||||yy|{{{{{{{{|w|v|v|n|||||||||yz|{{||{|z}T~h}R~ܩ|ݐ}j|\}Ѫ{|y||ϱ|~}||~||||{}Ɍ}R~_~y}}|ܘ}ݜ}}U~x}m}|h}}~}P~zF~s}u}||||Ԃ}}}}X~u}}^~ԓ}}|p}}s~^GN~|k}|||||||||Ľq}M~~G~|j}{||ˏ}}}e}|e~Y~e}c~[~|e}T}i}§|X~}|~R~j}}}X}~^}j}||Z}¹i}_}h}؏}}Oi}z@~]}n}V~Z~ԋ}ȶ|i}|ɐ}^~||}ľ|}n}U~Q~\}_~l}|l}h}|ۭ|Ԋ}S~~}}ľ{X~}}̉}́}c~|l}\~[~c|j}V~j~]~a~OS~a}s}V}Q~o}O}ǹ{[}P}||à|{{{|e|ž}}||ǼY~˿Z~a}øZ~}[}~||r}n~R~D~|y|{|}h~{w|q|z{|½}}`~@x~{}|ҭ||a}}\}}~||||||ؚ}}l}o}r~|}܊}m~q}S~}c}O~~S~T~N~֊}a}`}W~g~l}M}~}g~Jc~X}~r}g~҉}y}h}}R~T~}L~r~n~g~}}Z~M~c~n}ڬ|m}}j~Ľj~Ec~N~y}Q}n}P}Ł|ʌ|l}j}|||h}|e}[~K~}e~q}[}N~¸RC~i}¼}s~{}Ľx}O~G~P~LJ~?~}|s}||}H~c~~>n}}u~e}ʒ|۸||b~ž}}J~X~u}|ء|q}P~^}_}i}B~y;~o}w?~S~e}i}ǻi}Żj}P}k}X}S}xL}p}=~@~»A~U~Z~Z~;~@~b}j}b}[}^}Z}Ĺ|Ĺ|ĺ|ʼ||}|¶Y~W~ĺ[~T~B~h~H~ĹU~Ca~{D~F~m}\}[}΋|ϑ|F~`}S}O~V~L~K~Ļj}p}}t}R~]~M~>~E~q}E~<~οV}QM}оq|q|ŴO}˷O}E~a}E~=~S}<~A~I~Z~ѿT~6~M~G~J~_~Q~Q~g~=k~e~g}H~CY~p~l~n~g~h~D~e~?~M~ź_~ʽb~]~Cl~K~7<{8C{6@j~S~y8D~Q~i~K~u~¸_~b~i~a~J~S~J~S}T}K~y:~c~R~x=~N~M~E~s}j}~}J~y=~X~r~q}}o|{{{{׼{h||y}Ŀj~½q}x}~E~~g}t|V~i~f~|C~}||y}k~{X}]}z|e}я|`}?~_}}s|{{~^|E}S}n}}:~=~I~f}A~N~X~V~5M~N~ξL~{p|F~K~ôS~X~p}x9~T~:L~R}Ԛ||S}Z}x}zB~J~s}i}w|̷{ͫ{k|Y}I~҅|}|j||Ȇ|O~}~}ʻW~p}_}a~B~l}=~p}{|a|{Z|{zx|R}|~C}V}U~L~{{|}{{{z|n}v};g}Q~ĸU}b|ց|c|u||}|ܡ|a}ނ}G~\}m|}|Ǿ||K}K~z9~^}{Ⱦ{{{g|x|͸{Y}r}A~@~˾T~B~s}f};F~r}_}u|ۺ{{Ï|{z~{{{َ|ļf}d}˻T~ȹN~D~E~=~\}[}|||w|a}E~G~Z~U~̼T~r}N}d}a}k}Y~}m}]}]}ݚ|y|º|N}a}h}Ã|~^||{|r||{x|̌|d}h}g}d}d}}w}J~j}R}»w|i}l}}r}]}|{{Ǹ\}T~M~]}՗|s|ݱ{{~||Y}|U}}7~l}H}߂|Y}|q}T~}X~}=u~n~P~Ko~ø^~L~m}k}O~ʿq}|A~x}t|x|c}i}e}¸j}W}e|f|||y}D~S~z}}U~W~h}֦|}w~}k}}k~T~P~}}}d}}i}|Ӫ|R~V~݉}w}O~l~h~E~Y~}{F~R~m}|p}e}Ƽn}L~Q~\~q~g~W~Q~X~z@r~T~]~ɾe~݇}W}^}}}L~X~N~~K~_~~n~V~\~Z~S~Њ}P~y}l}c}|_}i}a}~||_}Ƅ}W~j~Ƽg~Fh~q~Q~b}|v}H~N~ӊ}}ɿa~d}|{|[}q}}^~g}ѳ||a}J~}||s}|خ|ƹ||ï|y}s}z}`~X~R~Z~`~}}u}||s}P~T~}}}s}ݳ|Ԕ}||||Ű|ƪ|`}q}|ۑ}}S~¹t}ي}o}|}}|l}f}v}x}j~׃}i}|x}Ϸ||}}Ո}N~h}~g}u}t}u}i}|||w}}Ȓ}٘}d~y~b~|}}}^}||Ы|~d}||||xz|{||{{{{{{||||||||uv|{{{||~|{|||||m|}||{||~}|||||||{||i}T}}||||||{{{{|}}||wx||{|||ˋ}u}o}}n}r}r}q}ͩ|q}s~|||s}Y~ɇ}FH~||||}q}g~r~}}|}}j~}y}}u}ȑ}r~¼m~Wa~t}}i}n}ƹ|Ҵ|||||||R~|K~|}}}|Ǫ||Ϝ|t}^}Ç}k~Em}S~r~}g}p}f~m}O~[}|H~a~}l}ӌ}y}|}{|޼|y}}|j~V~N}V~љ|ۂ}h}}s~||҆}ɀ}z}|||S~Lj}|q}}S~}]~Y~̚}|||_}e}X~W~[~g}}d}[}|{n{N~|D~Ƌ}^~NT}I`~ӱ||~G~K~g~ȻW~_}ݏ|]}}b}ހ}||{{{{}ºm}z|c~u~K~b~|||ë|\~^~ʿX~Z}{xz||k}m}|І|~I}|–|Z~U~|f~m{~>}||||||}s}`~e}|r}ٲ|ݚ}}}t}a~`~j~؆}~G~W~Y}g}f~ō}v}r}M~ȣ|ͥ|}R~m~}m}f~w}~}j~x}k}I}n~F~>Q~|Ձ}m~y}~}V~Ŀy}k~\~~d}{~z~f~M~ߴ|}p}U~t~q~Ch~n~T}|}h|m|m|S~|u|{|{\}x|\}|r~w~}o~žj~p~o~P~إ|j~q~w}}p}W}F~ő|N~ľc~|}{d}}|֮|ˡ||o}|X}X~Q~FZ~m~]~j~\~P~Z}b}|ڔ|e}j}h~ȻW~z:~F~K~B~J~D~G~Ͽ^}l}ĸd}I~Y}>~o}n}M~:~=~B~E~ĶI~C~U~W~=~f}l}d}r}֙||x|wN}|J}k}Z}o}E~k}C~N~|=~D~_~A?Y~͹I~h~8ĺZ~v||Y}^}Y~e~I~J~h~N~F~A~\}t}K~´Z}V}ĺg}ފ|yI}Ԅ|ƀ|пt|\}M~[~U~\}ðR}R}?~ԾC~оJ~]~M~C~{6~=~|4~W~X~m~N~@~J~S~s}B~M~L~T~q}t}?~}?~T~|}P~Z~k~T~c~˿`~v~p~O~X~F<3G~]~F~F~S~_~M~R~C~P~`~r~b~ǷV~}}o}R}J}°Y}Z~b~J~J~}}U~Q~N~Ⱦ[~ƻj}v|_}}}j}}zQ}q|{{rzzY{Q~k~{~PIX~o~~ch||Z}|}c}ڀ}L~x|{o|x|||f}X}{Ч{|m}}_}L~U}С{}S|E}V}>~<~[}d}c}ǹM~56]~8W~J~?~[}F~T~v07³M~C~||v}K~O}ֺQ}|4~ҺG~w6~X~Dw~H~I~T~߿{{~<~ƽJ~̿Q~L~U}{{i|~>~V}^}¹k}Ǽf}d}L~R}|B~_}i|{V}T}{{g|o||P}4úm}{{{{{xx|^}|}[}>~G~Q~:~V}h|l|v{n{~i|g}N~Q~n}]|S|[|d|ъ||{|||{Ⱥ|Ƹh}]}L}m}u}C~F~{y|G~X~|<~?~v}|{{{{{z{{t|h}F~x}6@~||ۚ|`}ޓ||`}v}F~<~R~{B~|b}R||l}J~»o}r}w}ļo}||{w|ʌ|Q}|U}}G}{{zL}|{g}|z>~L~M~E~I~}a~ĹZ~E~}?~ړ|Y}|Ѿ||||{b|R}h}ӼN~±\}׋|{߷{Ҳ{{^|y=~y||W}o}};~̿b}ƾv}|Y}R}h}E~X~R~]~?Dz~r~~=g~]}{}~V}R~e~P~W}P}zM}||{Ɛ||M~U~O~N~ʾd~|=d~@BO}z|p}~I~J~x||k}х}P~ʿe~j}|H~V~n~Ԅ}Ŀ|v}W~~~}}_~c~\~N~v~T~ûo}k}ۉ}I~U~V~V}o}H~M~h~s~f~¼x}ލ}ڇ}م}ɀ}}ه}ļr}Ƽw}߆}}j}֐}͊}q}}h~i~t}b}}ɬ||||Ձ}K~l}k}k}|{|||Щ|||܎|xB~h~HJ~H~d}j}||m}v}s}q}\}׶|t}T~b~[~z}ā}щ}u}u}||||||||U~s}e}||||~|||o}|||r}t}||||w}}|¿}ň}y}}ޒ}n}ڮ|||}v}~}v}ʍ}ג}t}{}|}y}u}V~Ֆ}}‹}}|©||}c}v}|d}b~Ќ}|P~W~Ӛ}}}|||~}t}ݓ}x~Ճ~j~`~j~~S~»x}x}~h}f}|||||}|{|||{{{|{{{|||||||{||xy|{|||||||||{{||||{|{||||xz|{|||{{||n}Z}`}¸q}ž||ä|y||ʤ|||{||}|ã|ԍ}}}|I~z}n}T~|}y~}}Ŀ}c}||r}O~}N~U~M}}||ü|_}žz}V~i~y}j}o}~}Փ}y}n}|}|~a~N~]~G½}c}Q~|||||^}}m}~^}r}l~|‡}p}S~{{|}N~u}t~X~g}R~[~{}|X~q~F[~k}я}̈́}W~Q~j}||||ٵ|t}z}h}þ{}b}@S}|o}R~S~}|z?؈}||||յ|~P~Y~l}q}}r}g~úm~ΐ}Q~}}s}]~c~|v}b~`~|k}Z~ȿl~s}{z{j}d}H~e~p}r~w~a}Ľp}YJ~N~B~|=~{}k}|e}N}<~@~{Z}{{ѝ|}~N~w|{ќ|Gc~ĺi}||{C~||r}{||g~a~{|{{o|j|Q~փ}{|s}t}G}|}}||s}|z}z}}b}ֿ|o}T~Ë}x}}L~}ë|Ȅ}ς}[}f}cО||K~}~}~O~}Ѝ}||Z~N~}i}{~{K~g}Ҁ}Q~ڭ|n}w}т}_~l}k}O~þg~p~N~[~}M~N~{}e~n~`~`~V~m~}~|~}~ż^~};Ⱦl}ż]~|?`}P}s|}|½v}׼||b}|}]}||o}|}¤|i}f~p~x}T~a~r}c}|ѕ|S~՚||Ș||T}\}|{{ؾ{`}W}F~_~Ak~Oi~{?~}}U};~K~w|ݕ|ڤ|ŕ|z|e}]~>N~óQ~a~T~˻R~?~˽g}C~C~u5~?~>~C~R~d~j~Z~·V~E~H~B~<~ǶO~e~y}}w}^}ي|ڇ|f}{U}zM}y}s}Y~G~L~j}s}m}S}q}B~x:e~b~^~S~d}f|}@~}r}|Y}]~C~p}n}Q}Z}v}J~M~{<~J~R~I~|=~_}z:~ȹN~C~d}I}E}_}:~@~_}7~<~x3~[}g~`~G~G~ɹH~N~O~d~a~Z~M~Y~ƻP~J~o|y>~|<~{;~Z}R}Z}v}J}Z}L~A~C~^~^~]~Z~\~5V~U~óH~R~8~3~?~F~ĺ^~M~L~Y~^~Z~M~z>~D~y?~ȵ`}M}|=~Q~_~T~P~K~P~K~Q~P~M~Y}j}^~˼[~f~i}q|ß{Y|l|d|Q~Qm~v~u~l~u~aT|}a}{u~Ur~{|[}ʍ|o}{=~T~D~{{w||@~g~=K~x|{H}؋|̼\}B~e}l}w6~z03T~X~|3D~C~T}e}Z~P~=~X~>~}=}w|b}»S~<~[}U}dzF~K~I~H~˿Z~O~B~}||¸\}C}z|m|ƈ|u}g|ց|X}|{||L}±K~G~R~H~i}Z}|X}{zҷz҅|t|K}D}B~|I}{{{{{|c}e}m}R}h}}?~j~l~=~O}|{IJ{|r}U}Co~|l|r|zH}܉|Ԣ|{||g}Z}m|||]}Y}|W}|ѱ{з{|c}i}Ƚa}f}ْ||{{{{{{{‡|d}h}k}p}o}ɽj}|{{|||ݧ|||C}||u|s|o||s||`}՟|ޙ|͖|c}_}{{ʿ|;~?~K~źc}W}Y}K~R~o~u}S~X~q}:}i}H~M~i~U~t}x}a}|Ǹ||{{{{{s|L}]}w3D~|{ȯ{{g|׶{{l|F~W}ȷZ}@~X~v}zA~V~˂}P~[~T~}}X~h~g~AHp~ún}a}P~Q~^~Dc~E~}g}|{|ͮ||{T}Ƿb}X}@~Q~~;z}{{|z}}q|~||a}}|}k}χ}Q~}n}|u}d~}M~Y~u~|{ɑ|}J~}G~{K~`}]}}C~R~A~xK}|y@~H~}T~H~f}ċ|Ж|֔||֤|̈}|||~|||P~ٌ}||ۏ}W~M~؀}h}{}r}|ȃ}ك}N~t}}b}||||ƿ|U}e}T}i|~C~Bo~e~}g}n}ۢ|z}}J~U~R~e~}͔}}}n}||||i}g}i}o}}M~d~}}}Ľ{}€}|}ڱ|u}פ|w}y}P~Ŀ||||||c}g}ȥ|||c}}͵|i}Ɛ}ۊ}X~f}Ϭ|||||w}Ԇ}}}}ԍ}}[~}}i}`~}}|U~n}y}f}[}||e}l}~M~p}σ}P~g~܉}Y~c~c~~JN~Jք~}~N|~Nu~Z~h~[~ϊ}|̄}r}|||{{tv||||||vx||||{{||||qq||||||rt|{vx|{|||||||||{{z{|{||||||Ր|m}x||l|ؤ|}N~d~s}Ɉ}p}}|˚|o}t}e}{D~J~a}_~b~ƅ}O~Z~k}o~˔}|}|U}|a}a}^~~R}||u}T~}q~|}v}q}Y~|||Y~t}V~}Y~X~ā}k~u~n}}}}ѷ|zz||]~Żw}¼w}π}ģ||ÿ|f}|b}e}i}g}|¿|a~g}c~a}~~Q~|Eƀ}Dž}`~ˎ|p}r~ƿs}~^}޿|ϔ}ܪ|c~O~ƅ}||ϱ|}}x}n}N~R~T~g~||Q~|z~Y~ȑ}^~m}|||Ջ}„}Б}e~ֿ|||w~r~\~Y~}c~|~}{}}~h~|ս{o|\~~~~}}|հ|||||c}}Yû_ht~}{~^~CC~m~R~q|U}X~\~Q~Bo}E~|{g}e|{Ϥ{l~;w}k~~:^}}z}|wy|vw|Q~E~S}،||||ϯ|M|{|||}þdܰ|~||~}|y}||||k}Y~|}||ľ}̉}R~~}^}̣|Ľ}y}҇}[~N~`~Љ}|}}`~g~|é|}}|Y}Ֆ}ښ}k}c}n||zQ}N~~R~f}̆}|||΅}\~Ȇ}}|}~J~d~|}t~l~T~R~R~}}_~d~GNMd}A~M}S}շ{{|}|||{v|}b}Ү||o}}}|S}ƌ|F~Iƿ}b}G[|l}Z~}]}||a}üq}{}Ղ}Z}o}O~|m}|u}^~a~W~^}N~|`}NR~n~z7V~|U}m}N~E~U~Z~ĶQ~T~E~}A~y;~q}@~w}F~^~i~ĶQ~@~ùV~~97M~Z~G~~<~G~q~}D~U}r}¾d}d}~}r}E~W~T~¸h}~D~|{}L}M~H~n}V}ĵa}^}Ͼ]}c}Q}z=~J}|R~N~?~k}m}u~q~S~M~E~B~E~S~G~g}O~a~\~\~V~?|2[~?~=~A~ƷN~ijE~R~<~7~B~8~J~e~B~M}w9~ȻY}H}H~c~J~n}I~Q~^~S~A~P~K~P~|6~@~O}~;~A~K~B}_}X~˾V~]~M~мP~Z~J~x23S~տE~νK~O~ɷG~Ⱥa}I~yE~w}r}a}ļl}a}s}n}t}b}|@~F~}}o}b}y}p}H~~=~z;~O~h}V~|6d~źZ~T~M~K}p}G~q~o~V~Q~U~|@q~l~]~|}}S~|M~]m~|{{ܽ{x}K~{}n}Q}|Τ{m|G~6f~}M~B~B}E}k}d}A~{27T~w16Z~J~f~?~[}D~63x0M~V~C~D}~}K~L~L~[}\}b~=X}F~M~ƸZ~x}L~k}|||l}>~@~V~g}[}}|{{ء|R}@~D~@~z<~{W|{{{|n|{||L}ùy|Y}z}G~{{h}q}o|{O}E~T}W}X}xG}<~[~A~L~}>~O}݊|M}>}e|ƻk}|{ء|a}`~|6j}V}}||֏|U~h}р|j}g}|ļ|j||d}W}X}a}N}Έ||{{{ƻ{{{{X}^}||{|Ѥ||Ê|ϡ|||||||`}h}X}c}u}e}ۇ}ې}`}X}d}p}|Ƚo}L~a}ޟ|||O}G~M~>~Z}V}`}X~M{~BDs~j}|F~P~c~b~Z~M~ة||{|||{{{{v|=~ӿ\}L}R}M~{{{ν{{{i|>~I~L}R}V~I~b}i}q}C~O~{C~|A~N~L~Q~S~g~n~S~O~^}O~K~zB~b}l}|Ŋ||{{{{{{{y||]}u}W~q}Z}X}۠|Ǿt}Y~X}ֺ{|l}q}}z}G~}}ĺj}V}|v}m~ɽc~ن}|r}o}՜|||||r||yQ}b}B~z?~||_}U}M~c~C~^~»i~}~I~Y~~L~}s}[}Y}ދ}q}}[~Љ}|ľ}t~{~S~Ļe~P~}º}s}{}{}R~~E~t}z}}x}k}^}B~v}`}y}[~IAļr}ӟ|}N~U~||ը|ʢ|Υ|f~Ž}Ӊ}|Ց}t}|ֺ|g}a~}}l~e~`~}f}ǿ{}}q}ƀ}͂}׏}҉}ƿ}{}}؊}|||~d}|n}R~W~~N~Ą}e}]~}||r}u}ؒ}|}|}v}|||}}}þ}½|g}w}||ˍ}R~ߌ}N~½r~M~~P~|M~̈}u}}ݑ}]~S~]}}ڌ}p}ۃ~Js~}f~I~[_Jd~p~m~ǿs~Y~m}V~þ||բ|ع|vw||}|ss||||||~||||||}|||||||||{}||mn|{vx|z||||||z||pp|||ww|{{{{||||||l}M}ݫ{H~c~|w|^}a}ղ{x||{{|΅}΂}}d~v}|c}ٞ||`}t~||||{[}c}g}~Hl~\~}}t}]~z}O~j~r}||w}p}n}̄}}l}e~|}j}w~}N~}}}h~|k}څ}P~}W~®||c}|||s}]~U~||ʌ}}»i~v}v}~_~b~^~L~x~o~v~p~U~{}w~π}}j~}}|{{{|n}}Ď}Ԅ}Q~ـ}ö|a~|l~\~`~||}Q~Y~a}}؈}X~y}¾{}f}|{|M~u~|P~HQ~}||Ih~v~M~̠|[}x~d}ۜ|͈}|e}{|}}^~||~M~d`y~>U~J}}úe~d~@ʼY~{}۾{~|ӂ||8C~e}~V}||T}|t|@I~p}]}b~O~|{{uw||}}E~Z}j}|{r}Ȥ|||}|||Ų|^~Z~||\~}f}r}||||||~}z}||߇}V~c~m}ý||ޟ|}L~i}~q~\m~|P~t}l~||վ|s~v~҄}e}|}Ŀ}Ƣ|Â|g}՛}}Į|||w}g}O~f~x}[}ڼ|Ǩ|^}}r}}w}~e~X~|a}S~`~T~@~~B~zP}l|{û{Ľ{ͯ{~}{z{ۊ}ި|~}Ļp}Ҍ|J~M~]}P~m~p}S~u}~A~^~G~I~Q~R~w<~p}k}{C~K~X~C~k~C~]}ό|úk}I~{}G~l}Љ|~~d}I~٫|{}Ξ|{C~~<j]~I~Ļk}F~}}G~X~z}yL}D~P~h~L~z@~[}=~^~<3<;~ƸK~F-w*:[~O~[~G~~;~R~I~B~W}W}~|C~O}|j}>~Z}P}Ŏ|Z}ǶN~N~N~q}ξc}|<~I~|<~T}ɼ||μ|z~Tz:W~P~P~A~P~S~|}U}J}Ԇ|¹c}F~e~h~?ʿX~ǻa}H}>~L~>~A~~A}K}d}U}@~@~A~>~F~G~]~H~8~p|z|s|d|e|ρ|[}Y~Cf|N}T~U~^~G~:>HķL~O~w4~>~|:~:~E~y3]~A~N~{0R~Q~M~Y~j}a}g}X}{K}|Z}K}L}g}R~B~{<~}}O}]}j||O~B~m}y=~Y~K~^}p}}B~~}]}J~R}n}D~|m}X~X~ƒ|Ĩ|̓}Q~Z~|{h}Y~U~|||{s|Z}}K~X~V}v}e~o}M~@A~j|I~{+L~8~F}K}Q}@~ƹP~X~R~w/Z~S~Q~v4\~̻T~]~9L~E~g~?\~C~z}L~T~z;~o}L~=~|M}6~F~]~Y~}Q~X~O~G~V~V~J}Q~j}|G~e}<~ǺO~<~H~h}N}Q}{{{zz{|K~_~L~Ǽb}|_}|k}~>~f~Q}vA}P}>~I~=~q}E~W~w0F~;~G~r|X}E~}|g||Օ|o}{}l~]~G~|g}b~07M~Q}||Ƚf}z||z|H~]~B~{}y}J~K~{}}|{{{q|Z}y}N~A~B~k|{Z}d}`}S}u}y}h}|{{x|Z}l~V}c}C~Y~i~S~|}T~}k}G~s}l}f}S}U}֒|||||||a~Qm~e~f~}}̥|~}\}d}F~T~i~V~X}֒|m|{|||{{{{J}D~J}^}ɶQ~οV~΅|p|j}|i}m|~r|u|ց|C~l}A~R~]}t}]}s}yK}E~A~v}S~}C~N~{}üj}s}P~|}a}Y~\~L~z}~D~|}||||||{{{{|O}v}|G~Z}۟|Ü|~n|Ļq}K~|^}n}W}ӎ|m}N~j~Ȼ`~|}̛||~H~O~r}{ľ{}S~||{v|y|k|xN}}`|Ԇ||{A~C~W}ъ||o|}Y~~}e}L~}}H~v~\~N~}O~ƿl~J~d~s~g}||r}ȍ}ƈ}·c~Ƽc~T~~K~U~M~zF~M~O~K~žz}}K~_~}D~y}u}P~x~}@Z~||yA~P~¹c~\~Ʌ}|||g}w}|h}}x}|}ɭ|Ѝ}}z}Ք}w}^}ȧ|e}Š}f}m}ü{}ǃ}ˈ}}v}ц}t}y}ڃ}{}|n}n}||n}ь}`~T~t}و}}}}}¼t~_~t}{I~T~l}n}ºy}l}d}p}˅}}}}Ӗ}}X~_~I~d}a}V~q}}|}}˅}}u}b~}~ē}}e~\~^~Q[t~EAI~}}V~}|}Y~}~}}u}h}~c}|||||||||||||{nn|||||||vx||op|{{{{{{|~||vw||}|}~|||wx|{np|||||t}ހ}Z}W~a~|\}ȑ|{{{\}o}з||{{{{p|\~T~ۯ|ǿ|j~}|`}}g~}}ۃ}h}Ą}|W~r~K˿`~J~I~Т|w}~}}L~_~j}|||i~k~z}||}Ԑ}T~x}b~}J~ܓ}Α}ޗ}X~w}||~]}~X~|~^}}|}_~L~y}}}~]n~ļ_~^~q~}>j}q}b~`~\~b~H`~ڎ}DŽ}dz|o|f|{|і|v}َ}n}ԋ}j}c}b~ށ~||w}|}|E~W~~}}}~~}Ɖ}~߰|̈}L~Ɂ}{{}]~}JW~}||b~k}S~@|z}͇}{|}t}|Է|u}V~p~z||}I~ſe~Jc~Z~I~J^}T~i~Ƽe~m}xK}n}w~AY~o}h}L~ӳ|K~֝|̤|ͅ|e~W}B~]}C~ۢ||{y||_}ƼR~T~N~|{||z|}}N}||q|L~|{|}o~J~|}ʴ|{r|{|}||||||}l~l}F~`}W}|}k~h}||g}ց}}y~Y|{||̓|]~}݂}]~Al~j}}j}|yO}ɿv}c}Ä}|Z}|E~}a~G~s}|~e}ސ}j~m~}Ƃ}}O~n~[}ƾq}}h}Q~H~M~|o}m|`}G~ƾ|{{|{|l}N~Q~̡|i~s}i~|r~}{s|||H~T~c}Đ|a}F~O~N}T~N~Y~n}إ|}|||Z~}üo}Z}h}j}c}y}[~Q~Z}|||X}s}o||||R}ʃ}A~C~M~n}p}`~V~J~ʻ`}W~5:>_~µG~S~M~ųC~]~y2Y~˷S~O~øP~I~Y~DX~w}h}U~|2P~F~x}i}пg}B~p}U}W}_}}=~<~y;~B~ķU~;~p|o|||~M~p}˂}L~}>~a}Ƽx|||Q}[}p|g}A~}?Vb~N~I~i}|G}C~?~=~A~c}B~X}d}:~O}a}K~O~U~I~´J~ɾM~L~<~K~R}E}M}}5~x3~E~e}j}F~`~Ž\~}@~n}y;~V~P~a~~6_~D~B~N~>~U~?~I~D~b~6r}a}́|j|Ǿ|c|K}L}L}f~W}Ķ\}n}[}Q}X}q}[|c}P~Żh}K~}\}Y}i}a}b}t}ø[~f}m}a}U}K~}L~o}i}}?~b~S~S~ޗ|c}^~]~|{}|{א|N~S~\~V~e}Q}M~S~J~B~s|]|e}T~G~x2~f}B~d}Z|c}e~?87Q~ƷR~;d~E~ɺT~c~Y~P~Z~[~U~_~M~Q}·e}y}u}M}|l}U}=~[}J~L~T~R~{=u~ǹY~H~ĸ[~<4F~~;~ƺK~O~_}`}N~}|ƾ||~>~N}v|{{{|{|i}j}E~a~b~o}Q~P~x~u~i~A~||m}A~E~_}@~<DH~K~e{˞{|d}L~s}q|M}Ⱦg}üf}?~?~B~S~З|S}b~8^~J}Y||Z}ņ|{zt|O~M~M~B~u}̓|{{{{q|o}P~ʽT~<_~t|{k{޵{k}}B~z}}B~D~K}{{{{o|Y}e}_}}}ƽ`~d~E~C~L~n}|u}J~O~m}܏|x||||||||]~@J~{8e~b}^}|t}Ȼk}~@~|?~F~I~ļr}}P~]}|{|{{{~K}p}h}^}]~I~O~|||u|d~}L}||{{n|e|ȸ]}F~y}b}P}c}y}_}T~S~D~N~}P~}}};~M}]}x@~I~i~}D~܅}h~n~`~}}||||~||{{|z}Z}W}\}{}{|{e}{U}|Ö|||Y}`}a}}V~v}є|J~E~}}{W}|S~Ǿt}׃}~R}g|v|Ƹ{|a}|ݖ|a}H~E~s}g}||L~Z~|}}}ȋ}}d~v~s~Z~d~Žh~a~a~c~ԋ}|f}}Û|f}^}¹q}ܤ|u}}N~W~M~}G~{}h}h}l}p}Dž}ݗ}n}|k}}~}N~}k}}i}d}N~^~|¾|\~Ԃ}{L~c}}υ}R~m~f}||ջ|||||}}ʋ}†}Ŀ|ܬ|ƿ}Ҏ}~}}}|}z}}}O~}}w}r}~}w}m}||h}p}k}s}r}|İ|b}}Β}V~Q~~Q~µo}}ƈ}X~U~͊}؍}ȇ}a~M~i}D~R~c}u}w}N~ڇ}ތ}Ɓ}\~b~f~Ҏ}}}~N{FL{~`~q~u~[~j}}Y~}r}q~a~}}}v}|U~S~|^}v}||||||||||uv||||||}|{|||{{{|ss|lk|{{{|||{wx||||||||yy|«|Ļp}L~}F~||T}e}U~u}|ž{x|||ü{zz|q}}|ۮ||n}Z}k}ƙ|L~q}׏}n}|||~Q~a~T~j~FT~k~ȹZ~ɗ|d~a~Ȝ}|}S~|||||}~|||x}ܜ}t~i~`~b~Y~}s}}ޡ}Q~b}L~^~|²||||}ͅ}T~ԉ}s~}}[~ǿi~_~V~K~`~e~Q~|K~g~h}]~X~ډ}||}|}|{{o}b}P~N~}l}w~|{}||}{}}_}~O~n~P~h~}L~q}|՗}}}t}||\~|~SG~۩|ǫ||||ӽ|}|z}x}z}_}o}՗|N~|\~}Ļ[~»]~ƽh}W~|}o}}}P~|]~~}{|׈}}s}|R~DP~G<G~V~`}||a}{|X~a}_}|Q}||~}||d}w}>~f~S~U}||{|||™||ޟ|||z|||Z}[~a~^}b}D~k}{}~||}_~p}||y}_~ľk~g}F~L~n}q~݂}c~X~p}ȅ}þ}|b~t}}ͪ|||d}ۯ||w}÷a~[~C~i}æ|Ч|v|W}k||d}w}}T~ƽr}}i~z}Ʀ||||v}٭|׆}c}d~J~΋|S}n}j}b}W}m}ƽl}~}Z}e}{|i|{{{s}֍}R~}ʁ}k}}^~χ}V~ľm~l}P~ܯ||q}B~N}Ǝ|xP}u}K¹o}z}y}e}ŸS~J~{|||||BƿV~D~V}}^}b~~~z}v|k}ȝ|z}Y}ή{S}W}|R~I~J~R~T~M~D~|}U}?~X~:a~`}ͼT}j}>~_~w3F~A~y7~{?~?~µW~^~LF~Y~ɼV~{8F|6f}?~D~|<~a}m}A~G~=~`}Z}M}|y}d}t}Y}Z}|ٶ|v}@~`}_}?~|~S}z}x}t}X}ùf}9Be~e}q}}E~c}@~N~`}B}c}˽^}=~@~<~7~6~L}g}C~Y~b}u|l}E~g~6S~I~A~M~C~>~K~J~m}E~;R~X~J~a}_}>~h}k}d}L~R~U~N~P~Q~C~Z~Y~C~E~m}b}U}K}݃|Ӻk|H}U}Q}E~s}f}@~t}n}z}L}R}9AV}͍|R}U}}u}}}]}^}׈|y}R}h|X}||т}F~j}K}g}e}Z~H{{|{}{z|`}h~}[~o~ļa~}~Io~M~K~]}t}m}N}C~~|F~U~O~:~_}`~W~j~8R~K~O~ȷL~F~w9~B~X~R~ŸY~b~P~m~K~~|{g}ɿh}_}˿g}F~Q~Q~N~a~b~R~ȺW~ǼV~a~V~J~m}b}};~y|p|S}F~d}W|P}U}l|ϔ|C~ϖ{z{|T}||||}J~T~z7O~Bi~V~}I~i}|w|s}h}~<~g}DF]~b||ӣ{}Q}|g}c}{|߲{L~x6~{|U}c}@~`}O}~:~~:~D~~>~c}N}u}D}k|z׸z{{R|Q}l}x}G~|Ǹ{{{{M}F~[~5z3U}zV|T}k|zI}|\}}N~N~F~I~N}{{{|K~p}y}@~z}}B~B~s}z|V}H~~F~|{{|ܧ|^}h}|ٲ||||I~>\~y}D~r}Z}{|f}d}Ⱦl}|}}}O~h}}Ż`~||||{{{ǹt|;~C~d}h}C~C~J~|տ{e|S}|||~|{i|Ⱥc}a}b}|9L}U}l}A~ùZ~J~r}j}|F~H~}w}_}ùv}S~Er}w}ێ}O~L~R~Ӈ}Q~݌}d}`}V~D~Ԓ|}|o||ח|[}}}F~g}s}{}U}x|{{|`}U}j}Y~U~}|Y}i}Y}Ձ}n}X}Z}a}}Q}|{{{{~R}Q}e}B~}}]}F~ϋ||F~Ⱦb~N~g~^~U~}}ùb~ۃ}¼||g}]}}`~o~p~zH~׆}d}y}S~M~R~P~Ⱦu}}M~x}b}l}h}|||||||||ϳ{޺{|}h|||ٿ|w}h}¾|ʌ}]~N~P~ڬ|b}q}f}i}g}|f}}f}|||||||}|Ќ}}d~R~|N~V~Q~O~V~}^~|}}Ӗ}S~|||ۦ}|r}|||}}s}„}W~ƃ}}}̉}v}Q~}̋}[~\~g}e}ֈ}j~}P~͋}}}|ܾ|p}Ɖ}s}{~~~MKKGG~Ez~i~T~}|z}_~e~}}t~]~x}s}~R~}g~}}q}||{|~|||||||||||||||z{|{|||xx|kl|{{||||||||||||||t|}|||f}|֪||{{|{|{{{{{W}W}u|c}q}}ƽ|a}Օ||[~\~I~y}|g~N~Ň}T~žo~}O~|}Z~ȡ|v}w}˲|Ƃ}g}j~|||||ߗ}|||]~m~d~r~}u}}}|Ą}X~|ų|l}Ɍ}~}{}k}m}}w~`~}z}|}l~ǿi~u}Ů|؀}i~W~u}s}U~i}Չ}||||U~U~|ݕ}Ã}s}T~z}t~q|q|q}f~а|||||}}c}\~W~Z~x}K~ǃ}ȼ|l}S~[}s}||н||k~M~M~p~V~||P~Ã}Î}z~z~z}o~l}f}W~l~GSe~H~:~a~b}v}E~ͿS~A~\}v||||ߟ|y?~}?~I}HT}w|U~`~D~AN~ķa}U}m}h~A^}||t|{x}n}â|\}U~Z~j~S~}O}c}X~i}l}M}||r||||{s||K~d}}H~m}||Ľy}M~{=~|`}|||||e}K~L~x}¾|||܎}V~y}e}|Զ|ě|}Ȯ|t}з|ۨ|}Υ|||ё|f}y>~|?~a}x|œ||}l}z}l}o}[~r}~c}ư|`}b}|}}y}^}c}\}f~@~S}S}Q~H~X}Q}X}`}p}c}~?~H~Q}|Z}~}|~T~`~a~_~V~}{}`}P~|~n}N~™||m}}F~X}e}L~ƽ_~J~ýa~~=׃}}z<~l}á|F~`~ש||r}g~R~y~\~~@X~~5|r}}}~K~˂}o}u~e}[~I~}9~M~O~ѿJ~<~E~P~F~C~p}||G~DM7y-ĵI~n}:9|4E~F~C~L~´L~C~G~p}B~ǼQ~::H~~:~s}<~X~µT~H~p}E~A~l}~<~L~i}O~c~W~B~t}|Q}ƾ|m|m|||?~G~N~U~ŷQ~y5g~ʿZ~w~FDd~S~?~F~J~]~F~~;~]}Z}|k}F~;Y}?}ƶT}?~N~\}I~@~=~Q}uC}g}d}:~{3~\}X}I}~7~J~˹X}̹U}?~y9~̾R~?~>~9~7~|9~z8~h}~D}z?}_}P~F~?~L~M~x}t}Z}X}M}[}Q}C~W}׿^}?~@~C~E~Y}{}T}Z}k}E~t}d}v||¸g}[}X}|Ԓ|||]}I}{|g~X~s~Dʇ|f}U~i~Pº^~ԛ|^}|{l|ܫ|d~s~Z~\~w~x~^~Y~l~B~V~څ}d}=~T~I~V}E~z7~B~V}N}x7~w2~;~:~v}`|||ǼR~H~9~ɹM~y3̿Q~~26\~c~z8~s|{”|׎|K}u}a~z8[~a~;f~v}J~^~̼U~>~^}}8~h};~~|a|K}c};~Y}{t|W}Z}{N}{{|o}o}p{U}N~~}M~c~JQ~t}Å}}|D~v|||O~a~?6R|>~}<~s}Ҽ{{||}l}O}o|~b|Y}ŸR~E~ŷQ~b~J~z;~N~ɾd}P~G~=~ނ|I}{|ܢ{~l{{U||O|a}k}Ļe}{?~P~T~W}{|ʽb}i}d}?~Ϳ[}F~O}b|s|zP}ѓ|T}׏|Ϸ|t~i~ł}_~u}{{{|@~e}Q}:}˃|I~?~S}|{||Ѥ|ſ}ʈ}~}}c}|||q|ٛ|\}w}Q~`~_~D~f}v}v}}@~{<~l}w}W~V~i~`~ߩ|||||||_}˺_}Q}i}F~E~S~_~\~r|֦{j|u|{{x|{Ͳ{j||z|K~\~c}@~K~W~L~{}{B~r}h}E~K~Žn}_}ۃ}_~g~V~R~P}P}}E~`~U~I~X~c~}k~̿[~~A~·k}j}|V}m|z|r}M~I~H~P~}}c}|k}^}~F~[~I~ûn}y}w}|b}h}ǻi}Žj}ׄ}|}l}\}֜||{{ג|||ِ||~}K~{}ļr}H~x}l}J~½||Ö|b}U~|J~}E~R~R~ֈ}|d}Z~\~d}l}h}}}݇}ڀ}~J~}M~Z~ۊ}||{{{{z|}n|~r|{{{e}ˢ||_}||x}Ľh~y~۝|j}}N~Y~֋}n}}u}k}€}Y~|}ҡ}}|}q}Ë}}|f}}d}Ǯ|޶|ý}}a~y~`~O~S~}׏}}Ɍ}}|ē}}|_~}\~ûl~~m~|J~}}ψ}|q}|߶|ž|}Ӷ|m}~M~Ȁ}}x}|M~|ϼ|}~֚}a~|~ـ~k~|Dz~Hƿk~s~m~^~Y~o~i~l~}~|C|~~Kf~_~h~k~Y~}Ċ}ƿ}}}|}ɫ|u}p}}}|||||||||||||~|||||||||{{{||{|||||||||||z|z|||t|ʧ|||||||||{|s|{|â|}|ʙ|}X}}ݗ}z|z}ʆ}JT~|\}||ű|a~x}c~}p}||z}ɂ}р}r}||s}ټ|}||||»|ٲ|p}ф}m~}|}v~d~}}|||}x}V~c}}||X~|]~|Aގ}W~v}|Q~Kg}J~a~[}||c~ч}|||þ}`~`~||{}p}c}ۄ~S~f~w}}p}̧||j}}\}|c}}|Ʈ|l}t}}l}ȿi~Ⱦs}q}t~Q~}|L~Žv}|Ō}}}ֽ|o}~R~ł}}֍}f}o}{}˅}LF~Bt}r~OKDBh~h~E~q}|ԝ||m}L~FW}|p}y;~Ґ|Ӂ}y}h}}N~J5_}_}|{z`}K~P~HI~\~<x|{{{{G~d}u~|{}M~W~I~ż\~;~E~C~V}_}u}|r|||_~c~}|r|||i}}|X~g~H~T}z}Y~r}{÷||l~P~w}J~v}ϋ}P}]~}e}i}|||}{ſ|þ|`|x|ǒ|^}r}|a}I~\}š||{||`~KN~||}G~n~r~]~L~}^}ơ||t||a}r}|P}A~ƳY}Ⱦi}އ|Ƚh}˽h}g}O}q}U}U}J~ƾy}t}~?o~_~n~Ǽ[~W~Y}|}||||||`}j}W~[~q}|~}o}΋|ކ}Z}s}d}]}U}B^}}f}m~^~w~}E~N~PHZ~H~q~B}j}U~؃|R~@{2\~P~³H~F~:~>~F~ɼO~ùR~h~M~F~_}W}N~_[N~E~W~}4˶J~?~D~`~36]~V~IZ}y6~X}V~8x+>~C~K~øW~Ĺ[~ɽ[~Y~l~ʽY~P~@~d}F~a~ʺR~F~v}j}|u|l|}k|؈|q}?~T}R}r}K~T}k}V~Eq~P~|>ǸN~<~D~H~N~H~I~v)w+=~\}N~?~O}[}Y}ʿe}T~ͼI~E~H~O~I~:~>~E~C~N~D~i};~>~Q~N~e}h}<~=~R~ͿS~J~@~A~Dz?~˺G~L~C~R~z7~|n}Gj}T}l}n}µV~P~E~[}o}P~E~}_}_}݅|||}T}zC~׍|\}h~R}^}||r|c|}|K}|}Å}|k~Ec}r~b~m~Eq}ϕ|l~b}|}<z~Qùe~W~BPJKIXs}a~W~K~K:B`~BX~~n~b~^~K~r~@~Z}r}B~g}|D}E~>~7~S~M~y}~E}|ȿ||[}{G~F~t}[~w6HX~Cz6y|W|̿b}U~S~I~m}<~<~R}a}R}I}{r|Z|]|J}U}{|`}k}}O~t}d}}>~M~ŸS~N~L~B~Ļ_}z}|^}O~w}y|z}F~¶P~y56O~c~b|||{{z|{t}T~a~M}Ŀ{h}`~\~GMD8G~f}w}u}˾]}}|{zv|h|o||\}U~Z~U~B~B~9ǺO~Q}\}}B~K~|^}\}N}X}a}S}S}N~}||{}Д}c~^~T}X}{R}u}{=~y:~a}@~N~v}ɿ|ą|}Q}|]}u}÷b}Z}؛|y|{{{{{ƀ|l|T}I~O~{;~V}V}i}ʽk}|:~t}g~G~=~ʷQ~DD~||||||t|o}r}|t||X}}}\~9ѼR~|f|{̓||{{P}Ż||o|ގ|^}Z}øi}~}˾X~O~V~T~^~o}}}z}i}m}|W}s}J~e}m}s}f}c}O}G~Q~G~z}o~}@k~r~a~z}{}z}z}}U}{|n}p}ч}P~S~J~L~L~v}l}\}i}W~N~Z~N~o}||m}Y}T}d}~F~m}Q~M}[}}R}–||{{{{v|z||w}g}|w|{~|ɸ{{{{|||~}}}||||ة|}|f}{}}Ձ}||ܹ||e}|xx|{{{{{{{{{{|Ľz}̙|y}Ԇ}T~zH~އ}X~r~f}Ň}ť|ɢ|u}`}}U~{}ƅ}ž~}T~Ή}q}}}ÿ}ь}q}}n}}S~g~d~}Cx~^~~O~}ɀ}}{J~O~½}}n}p}}Q~|Y}}R~\~[~d~O~Ľx}t}Ǿy}K~|q}||g}|~d}|ѻ|}}|}L~P~}}|}o~HIJ}Cl~}{}z}Q~j~m~}{N~e~e~ül~{~n~h~b~}f~~|~Z~}ǘ}}o}|v}ԉ}n}}|||||||||||||~|||||||}||{{{||{yy|||rs|||{|||yy|||||g}v}Ҁ}d~}l}_}|||||{{{գ|߰|X}|{p}X~|}[~v~t~ƾh~}~P~ޕ}Y}ý|l}M~ި|Ս}}r}Ż|Ì}{}}<ܟ|`}v}ʍ}}|}|||}|}}^~~~z~}΀~s~|ɿ|l}}}q}Ɯ|h~g~|}̃}Iw}Б}`~}ԗ}c~}}\~q~¯||}q}Ґ}M~e}x}|}ؗ}||||||}g~z}p}Z~X~ԋ}s}|¿|p}lj}|{}̧|v}||j}\~Z~|ڲ|}}m~`~_}\}V}z}~|~J~x}}T~f}|o}M~W~N~]~ߋ}n}e}i~l~i~Ԃ}@=^=9b8G~{}˫|||||_~Bբ||c}`~V}~I~_}S~_~i~>Y}[}|j|_}||k~q~s~o~}{{j}d~{||v}v}i}r}M~I~e~a~I~A~g}d}P}T}K}M~g~|||^}L~||Ơ||Yj~ʪ|q~g}r~}}|֌}|g}Z}J~N~l}½i}X}y}ü|x||z|{r|||K~ʂ}|b}M~g}}e}~P~||{|||ýd~Gf~|}}~~o|Z}}k~I]~X~d}u}|z}|X}||Ä|X}~>~ĸ^}zD}b}l}s}X}b}|}}BO~|~}ڃ}m}L~f}}Ѕ}Q~q||{͔|bǾf~׻{|q}z}}}ſn}i}|}|f~U}[}|e}d}N{I~[}D~Z~d~{~G~Z}E~hTV~x}W~HS~ց|l}T~K~e~CZ~@FT~D~U~O~H~M~@~^~~.=~|F~d~j~ɷX~C~i}O}P}t}ƻP~]~N~<K~N~\}yC}z<~H}I~9ͻBλB68÷IXC̽HEi~C~C~?~G~{<~s}|}b}Z}j}i}||i|{؋|z@~D~u}||]}d~_~N~@I\~Ca~?~G~K~³N~9~[}N}7~[}G~S~D~E~P~d}F~7:~]}=~=~?~>~D~s}?~>~ĶJ~}1R~G~^~ķN~Q~N~K~9~=~?~@~˿N~G~´h|\|\|c|a}`~k}[}Z}{}I~J~s}}x}P~T~J~]}l}u}a}j}G~o}ъ|p}i}~}|S}ݒ|b}Ň|ڑ|ʨ{F~C~o}ڇ}р}V~V~}}y}e}CX}|?S}Q~W}|b}Y~OXRDf~n~r~o~j~o}J}~K~P~^~D~Au~ûT}7ĺQz5S~f~Q~o}A~e}O~L~H~A~<~e}w|{x|{u|G}_}[};~=~νO~X~65y1~6=~zS|т|o}}a~C~i}Ȼ`}W}D~y<~V}՟{z{Ɍ||T~S}{x|h}X}V~J~v}A~b~V~S~O~K~X~>~Ƽ\~Q}[}T}}`}`}V~\~j~M~?~B~ǷX}{շ{{{o|ût|V}ޟ|K~`~z<~ŸS~;@~R~<9Q~;~S}H}f|߄|n|{p{]|v|ļ{{h}Nd~Q~@~A~Z~C~u|{Ȗ|}v}t}H~h}|}o}D~{}V}Q}||ů|u}n~r~A~˽`}a}c}a}}R~r}x||a}}X~ʷ{ų{||r||q}]}t|u|{}}|T}s|[}I}o|g}X}d|´{{Ȭ{\}D~Q~d~G~9_~S~|||||r|Y}E~̇|p|y|w|l|7~X~Z~i}s|e|{{{{{T}O}|J}ґ|y|O}÷T~¶T~E~[~v|½|J}d}[}T}{}^}ûf}m}j}A~M~q}u}b}I~H~\}|}K~o~v~P~q}C~h}{R}x|||ۊ}L~M}j|m|g}o}q}V~Q~M~X~H~c}g}T~p||d}v}k}d}~}I~S~H~}}Z}y}j}\}Z}X}¹l}}}H~N~T~Q~}G~O~z}P~P~h~f~}o}||`}|vu||}¶|˳|n}f}||~||t}^~q~e~ۘ}û|j}b~}{{{{||||x}~}̤|j}N~Z~S~]~h~l}||b}W~ڇ}r}f}r}}R~f~}Y~}W~d}P~u~L~e~m~z~]~W~o~m~H~|}}ʀ}q}ȋ}o}k}ގ}a~}ה}u}x}}}ۊ}Ֆ}{}M~t}}z}|ٖ}}|||~e}q}~}b}}}l}k}}o}}}u}h~[~\~}M~Ӊ}W~\~d~N~}}Q~¼}U~R~W~Y~žl~W~Q~W~Y~W~}~}||w}V~}M~|J~t}V}|ް||c}ѩ|||||||||||||||qs||{||||{|||||{į{z|||{|||||Ä}D~|x}ޓ}|}n}`}p}t}ż||ˆ|]~~||˵|p}S~О|}_~K~s~P~}P~ں||||v}u~}k}Ϫ|~t}}a~}e~}|||}۝}|}||b~m}lj}ӄ}u~o~b~l}o}j}S~}}|r|h}}q}h}b~Y~X~Ľl~h}ä||g}W~~|}}~}ˍ}}e~_~|]}||y}þ}\~ٖ}}l~}|}o}[~l}Q~]~}X}|u}|T~l}}|؝}s~δ|v}n}}M~z}v~z~}O~֗||¼~}ۤ|ɾ]~|_}|N|ȭ||}Һ|t}m~d~܌}}[~m}M~J~n~d~ɽSQw5ANMW~e~j}|||Ȋ}ײ|}I~j}o}|G~|r~}{}y|i~Hk~Žq}Z}k}ûv}K~K}?J~m|\}{|||||||{||<~:~N}P}V~E~K~}[}R}M~Ef~Q~]}Y}|E~FO~{|Ԉ}`}Y}Q~}|||||||`}BK~}Ž|҇|^}ʖ||{{|}|||||^}H~G|c~{9H|i}s}|d~c~^~[~g}||m}^~b~k}U~H~^~e}|m}x}c}}|a|uE}`}{}n}÷_}|T}\}S}c}U~d}K~٠|ѳ|~{ABĺ[~v}V}f}]~pR~f|}}O~F^~څ}|@~żr}Ԡ|d}x|s}Nj|e}e}p~|[~}}Al~G~Q~s~g~Ļ\~H~d~̾s}|R~s~p}|Y}ˇ|‹||p}q}n}{}ۺ{݂}Y~m~üb~׀}j}E~I~F~P~Z~{5L~e}ƾf~`}{E~O~R~V~̂}N~@~};~^~n}BS~k}}R}V}|z=~[}\~ǽS~A~D~E~ǹI~2ôCRV~P}ĸa}¹e}q}R~yC~ùe}{>~C~öV~8L~n}D~L~e~\~M~W}Z}R}e}K~G~g~E;H~n}O~ñH~мI~=~K~z2w.O~A~I~v}o}{8~[~T}8~e}H~ǺT~B~?~9~B~Q~o}Q}k}ĴR}:~B}T}a}d}7~>~V}7~O}W}\}ρ|y|ǻ]}?~K~T~Q~m}||X}k|y|~Q}{L}O}G~G~C~v}=~Y}h}e}U~}ɿo}ϊ|h}f}Ȃ|e|r|p|j||R}V}d}}L~U}T}}}xA~H~e}|||}}h~i}}_}X~g~L~d~G~Y}l}ǽ\~O~@~<~g~C=\~y}z19z3Z~N~B~S}l|q}M}y}]}Ԓ|\}R~?~n}t}@~W}O}{®{ͣ{x8~K~;~E~S~u/N~V~Z~¶P~M~H~˿Q}q||·`}yB~I~|=~X};}d|[}X}J}|x}G~z}|t}O~G~H}N}U}ǿa~{9~W}N~F~B~p}D~P~L~M~w}|`}R}l}l}g}l}L}P~n}J~T}z|ė{|@~F}M}m|{|}Q}p}Z~]~d~w4B~v7~q}F~h}i}K}y|O}k}O}ژ|Q}`}f}|‡|\}O~Y~V~~@~S~~?~T~=~߆|z||x}`}t}y}o}M~d}e}~M}p}Y}˵||{~p|F~h~D~@~v}{I}u|x||||ſ|~T}x}Q~R}{y{{{{Φ{ʹc}||ƾ|Ȳ{{{|}f|{Ϧ{N}b}y|C~a}k|{ĥ{W}y}}3_~\~e~N~Ĺn}|||q|v|w|b}A~m}j}^}N}n}F~S~c~g}k}H~h}w|ʶ{Q}z}|>~D~F~B~B~9KHw}t|}^}e}}y}`}Ľn}i~Q~K~O~e~^~D~T}q}H~@~m|[}m}G~O~R~a~c~ɽY~g}p}||w}Y~n~Y~Y~H~ùa}}ùh~{~}F~Q~I~O~Q~d}{r||i}ļt}}}n}΃}J~O~}`}h}h}}n}||V}q}K~N~~}m}e}||o|Y}K~{B~^~L~}}|p}}|||||ݤ|r}N~K~n}X~h~p~e~u~^~Q~m~}ȓ|П|i}||||Ӊ}ûn~g~~F~J~S~P~s~PYK|~}||a~p~ۈ}r~_~Y~{}~N~L~Ɇ}q}}~yBd~ý}o}d}e}k}t}u}{}S~Ӏ}l}n}_~X~f~}}X~N~}Ų|g}c}ұ|s}|˂}y}j}{}t}}~}}}V~v}}r}|Ͻ|ȿ}}׼|||}k}خ|{}Ϟ}ƿp~W~Z~c~ߌ}\~f~ȃ}d~س||¼q~ºl~S~}R~֒}}W~{}|x}a~}||u}S~Ɗ}}}\~[~y}ۧ|}t||q}}u}n}Ƶ||||||||{|}}||{{{y||{|~|st|su||||~|{|{||{z||i}a~f}d~k}|j}~Z}||||{||~||{I~z~u|||f}Y~{e}Ń}½}j}|~}DŽ}p}¾q~|{}|U~|}j}~}T~}K~T~|z}||||{|||}||Ȳ|k}n}{}c~d}}}||{|³|В}~U~t~h}|\}|ǿd~k}||x}~֒}u}~}k~`~O~p~}}w}^~`~IGr}}||}|~@l~r~~F~|}Y}v}e}u}||ƭ|ǿ|ʑ}ݖ}}b}||Ľ|}}՝}~}k~}}ƃ}P}Ϳ|ƿ|z}^}|~_~Y~S~p~x}l}Y}Jl~||t}ޯ|`}e~ǺP@iOE~_=~@}P~|||_~d}a}HY}n|I~}7J~{o}v}{_}Q~=T~E~Q}̼{m|h|K{}@~g}z|Ɍ|{|||{{}|~|j|T}ɿa}V}o}}|}X}d}Y}L~ɿk}]~X}w|y||Q~}X}X~}c~B~^}S~T~}=~h}||||¶||o}j}V}U~_}|n|ծ{i}|yz|||}Bx}ƾt}ݗ}~}üm}f}|l}G~e~FYd~f}AH`~֗}}W~կ|}c~R~=~L~i~d~J~{@~r|Y}c}t|۸{n}D~L~T~K~v}~}f}D~G~y}A@~q~ԉ}t}u}I}}=~n}s}EZ~Va~@I}Mو}o}{}FP~N~S~|y|{P}R}S~D~y}i}ľn}b~~N~z}g~}a~[D~b~|}r}}L~{B~h}A~A~|7~S}Ɓ}N~d}JN~G~D~;~:~=~l}s}a~^~D~I~X~[~A~R~K~[}A~y}Q~R~zN}h}žm}[}r}e}x}}|}|ۿ{{}b|V}k~|D~|{V}l}|9~O~Z}{;}@}ȿw|L~I~s}R~Q~M~ʽT~b~F~G~Q~|}L~ǼX~M~R~f~O~H~^}E~R~R~c~Ľ{}f}zA~}A~m}z}v}n}v}n};~Q~G~E~S~e~|}[}D~:~~9~H~ʽT~W~ʾV~A~N~^~b~ĵL~R~I~@~S~F~L}}6~A~T~^~\}6~K}H}W}D~÷P~A~Z}f}·^}h}ǿi}t|e|{|~R}{߇|b}`}T}_}U}s|d}v}c~`|z}[}J~~}|ư{{T}A~R~r}O~{~K~}w}ŀ}P~c}i~o~{{||s}Y}\}j}}m~Č|Žm}J~T}o|D~C~P~F~<~{G}A~\~g}x}E3X~Ƽ[~ºn}ـ}y}Ԛ|a}m~s}Z}{f}݂}{}}}b}}C~W}>~ߜ||{5~нK~@~ǹK~ĸH}C~w8~r}L~{8Q~J~|}r|ڤ{g|}T}Ÿa~Żr}|g}Z}k}p|{{΃|y?~|J~R~R}J~Q~B~L}Z|^}Q}~|t}B~\}M}Q}t}^}||l|k|T}{Q}[}>~m}R}r}˾t|{Ԅ|ʺK~e}U}B~K}ή{{U}c}i}s}ʿZ~~9_~r}q|v|}|T}H}F}o}b}F}{º{k}}x}{E~P~H~]~݊}]~~N~͎}~}^~Aʿ_~߄}}~}݂}C~`}||B~F~W~B~b}D~w}|ſ|f|U}v}ƽc}|~|{{{{|||`}t}>~<~O}g|O}{E}r|΅|M}{|q|z{{e|v|{L}R}O}v}x9~ȥ|}u|{ǭ{P}S~I~Ļ]~~Aq~I~d}|Ȫ{{c|||h}t}̾`}};~t}S}Z}z}@~оQ~i}X}׎|ǎ|{Y}n}E~m|w|߃|S}h}GGf~R~y}}q}f}v}ك}K~k}V}g}ʽg}x}K~d~R~h}H~A~I}O~I~n~F~}G~Q~Ƚ^~R~j}ܧ||{||Ǿl}P~}i}o}Ž{}|}|}H~]}Z}w}V~Y~|{{||g}|}}}|}|F~H~F~ݖ|l}}ܘ|{{{{{\}w}_}}|{{Ľ|O~S~H~n~^~L~Ҍ}W~d~||||p}օ}L~P~P~r}~}H~G~a~y~d~^~}^}݅}Ⱦr}n}|ʷ||}i~U~]~n}߄}H~V~J~V~t~O]je~b~~Y}|l}ž}}Y}u}ހ}a~y~BPGùe~u~Dg~W~|ߍ}Q~Žv}ͅ}|N~p~U~X~}^~üi~|½}}ϋ}p}R~ۑ}̃}s~~}O~|~Ȅ}w}|b}P~L~]}|c}n}||}}x}½}{~~g~r~x~[~T~Z~x}Z}}|y}q~~y~}Cn~}]~}}||y}|}}ف}·p}|ڎ}}Ά}ؒ}}k}o}~DR~r}{N~}و}}||||||uw||||{vv|{||{||||||{{|wu|{z|||[~ʣ}||||}W~ͱ|׺||l}|ٷ|͢|f}T~}y}ޕ}}L~}ӄ}t}n}Ƭ|o}t}j}|M~|٥|||R~c~d}`}ą}}^~`~l}¼s}t~|{|{||||w}}z}_~_}ʐ|]}~ʥ|y||j~q}|ď|l~V~Pd~h}a~L~I~Y}z}Ļq}u|x}Nj}[~~~}_}f~~}|}i~Ȑ}k~ҏ}~z~d}{>|||}BaX~р}P~[~|`}s}|||}֟}||ww||a~f~֫|z}P~U~}J~l}҆}}ޕ}}L~d}W~Q~t~d~Ŀ}y}k}_~ȋ}|ַ|||||ͅ}j~¶O<dd~e~}3@>q~ӹ||||u}ƼW~d}[~X~}R}N~c}۰{ۀ}¸_~߂}M~o}R~[}||ֶ{|R}As}~G~}=o}P}||{{{{{{{{x|s|r~~¼x}‰||ùl}Ҙ|Q~z/H~ź\}q|q|ڙ||]}8S~U~I~|4a~E~p}]}|y||j}`}||{{{|_~||}||}||}Q~s~W~ع|H~c}W~l}u}}|~m~SX~n~^~}}V~œ|c}V~|}̿V~H~\}Ⱥa~M~n}t}֎|{{o||}|Ź|J~|G~Y}Q~W~|^}z}q~P~e~ǿh~^}Ɂ}Fx~h~I~d}}C~~}]~}c}r}öR~ƺZ~o}`}d}||Y}|e}||Žo}\}\}F~}|n}U~{I~M~n}b~T~V~Co}h~h}[}r~|;U~[}`}e}|9~g}Ŕ|^}|l}U~L~2U~J~J~G~?~^}ǼT~a~^~>Z}x}o|t|^|C~W~F~y:~}?~Q~}|a}l}}=~Ǻg}yI}zM}q|T}W}K}T}̕|m}ľ}|d}i~-3]~ѿQ~l~=~1<Ly/3S@K9W~ŷQ~^~?p~u~U~}B~O~=Bc~l~K~U~U~`~EI[~c~D~{8~?~z5~;~]~=~Q~L~ȺM~G~`}i}G~L~B~ƷP~ĶN~l}j}C~z8~i}÷Y}~9~m}A~N~U~I~b}=~D~D~=~B~I~E~s}P~d~`~[~_}|b}I~h}[}{}ɽl}z}}}ʽg}|I}||̊}S~f}M~v}c}~}[}R}r}d}O}}W}Y}K~s}|}Ȃ}J~T~ƿp}û`~}D~{{{|ͥ}}|e}u}l}V}b}L~V~]~>~w}G~s}e}U}N~ʾV~d}p}S~b}b}Q}`}||||Z}|}c||h}r}V~v}y}v}y;~z?~w|e|K~O~:~E}\|P}l}n}F~^~ŸP~[}k}{}f}|||ľ||ڪ|||{ݻ{}`|D~l~{I~O~}V~K~G~_}I}^}]}{|||ް{Ս||{{ǹ{{{|]~x}ʇ}֋}b~f~\~W}½{G}F~:~ǽX}G~3~ȿn|W}I}կ|K~[~U~f}c}Q~I~ʛ|ݜ|d}I}_|V|M}{2~n|v|ϽV}X}^}}L~g~>~@|x|c}\}w}M~e~\~M~ɽk}E~>~<~̾Y}ú]}d|k}Ⱦ[~¶V~>d~f}J~[}{||`|{{{{{ľ{|J}|k}i}[}U}`}|ޘ|ԩ||w|B~h}i|̛{K}׈|n|n|b|n|{s|m}S~]}~L}ʏ|n}q}O~}X~g~f~\~M~|||{{|~|N}|h|_}ųL~9~}=~G~u7~b}C~ٍ|{{{f|o}ŶU~B~ö^}ʿ~|m}Q~l~S~Q~W~U~b}m}}}H~U~R~a}P~w;m~V~|>~h~ɹW~E~·e}~m|e}I~J~[~H~P~e~Y~W~H~Ԟ|||`}ٛ|s}C~}}M~Ì|S|{|g|ѓ|x}~B~u|u|Y}M~S~~G~ڝ|٢|u}}}~F~r}||e}{C~b}[}b}ȓ|{{ʑ|w}Z~~D~_}{{{{{|t}÷f}b}T~l~JQ~v~}u}}z}d}s}}}G~Q~}ǁ}Q~N~E~|~}T~U~`~R~^~x@g~}Y~^~r~g~a~\}Ɠ|\}އ}|I~ۉ}ڗ}y~~b~U~~N~M~J~}}W}k}S~}X~d~Z~J~}L~~_~L~^~}¼r~ҏ}ց}u}p}`}I~}v}ſ}}|b}}͆}z}P~`}׏}t}|}{}}T~}}Z~}i}¿|ǥ|||ݩ|m}}|s}}l~^~}m}ˈ}U~d~}}f}|v}||}q~e~}Ї}W~}}\~U~c~O~^~j}҂}؆}}T~ـ}\~P~}}܅}w}j}^~L~Q~Y~ތ}U~P~}L~}}||||||||||uw|{qr||||||||{}~|{{s|»|||||ջ|}||z}f}|||d}~\~p}|y}L~}ӌ}ߌ}ڇ}}K~Ľ{}r~h~Y~]}ۺ|f}m~i}n}}|n}w}}}|j}Տ}o}Y~`~Z~݉}}}M~|{{{||̰|Ӑ}q}d~۲|\}Ľs~}`|~O~o~\~[}|V~Lw~v}a}K~µl}M~W~L~f}V~P״|݋~υ~|»p~j}x}β|}}}}`~ښ}}~~\~w}~X~||||b~JN~b~g~T}|||}ʈ}L~|}~}̄}K~q}|_}i}I~o~[}`}x@~|D~\}|}p~~K~|}K~~}}k~ֈ}Ï|ė|||}}d~l}ҋ}b~f~IIn~W{<[=V}d}|{g}y}t~I~T~k}d}M~\~f~}IF]~J~Ҏ||D~H~{|{n|m}ŸU~|J~H~Z}ŻX~>~|{|{R}{{x|||{d}Q~ނ}\~{N}s~s~|w1Q~{7~t||{w}K~o}A~ƻP~÷P~Z~D~r}`}k}L~G~S}||||{{ٮ{{|{{|u}}||ܳ|ߙ}|||H~|}ɀ}r}|m}l}Y~]~W~D[~ڀ~R~G}t]~j~f~S~D~u}\}E~r}\}úo}i|i}`}S}e}ی}s}Ɉ}|u}h}|Ш|J~\~x~y:i~k~i}֒|i||v}Oq}E~[}~||z}B~p~a~R~D~L}|ŷc}h}~M}n}I~F~}r~Y~U~Җ|Ȼe}b}}}|J~k~f}|O}f}E~o}g}q}y~d~}E~:~:O~?~>~?~_~f}@~G~E~>~e}˃|f}v}o}d~|D~||Z}I}I}||G~l}d}n}p}V~W}E~H~ƻV~F~s}n}>~A~u)y,J~X}|\~U}ĸV}A~8;H9J>@6@h~i~^~S~^~y;Z~9F|3}5;k~:Q~v}Ż[~c~b~M~U~z}V~L~O~K~p}H~A~V}Ƚ^}^~KR¹Qk~̿S~I~=8J~B~K~B~:~@~v}m}=~A~ɽ^}C}L}`}j|{Y|Y}o}p}H~a~úX~k~?>ECb}P~R~m|a}M~D~]}~}[~N~u~ƿe~R}|C~X~o~~Aɾi}{}l||p}ļp}k}F~Y}J}~Nj|}x}O~{|S}ʛ|~e|p||ͱ{г{{|ڞ}~R~b~p}F~]}x}l}f}C~U~V~=~ɻO~J~z8~R~P~P~E~l}A~B~O~>~̄|a}e}M}J}f}a}K~U~x}|~|b}w}n}F~z:~g}V}s|Ҫ{`}=~v|z|m}O}y}d}W}N~d}¼||z|ӭ|r}Ƽw}ڈ}އ}|~o||{|||K}E~z0K~H~N~Z~w}w}~}w}`}y|Ǜ|e}}͐|w|{ʼ{{{{|J~i~p}q}J~X~G~K~M~P~P}ƹ\}v}`}̾L~j}O}̼`}R}E}x=}K}J}l}|{|r}ҭ|||~|p|‚|Z~`~X~x3~{X|˙{Ϝ{|W~m~Eb~c|t|O}]}t}k}\}u}w}^}U~E~Ż]}g|X}d}r}M~]~]~|7?~c}|[}{{{|{{{||y}D}Z}p}]}|s}}˻W~|rs|{{Ĥ|{Ҫ{Ϳ{|e|{q|v}}=~|{F}d|Ջ|B~~8~]}g|Ľx}N~Q~V~x~^~R~l~i~̊}|||||||\|{i||O}`}U}U}|`}øe}Ļ{z{{û{y|^}c|ɳ{V}j~c~Y~|>~s}]}}`}||u}}}H~Q~P~¸R~j~=k~W~W~M~l}z}_}L~c~·V~N~y=\~Z~t}S}|e||W}J~^~g~M~öX~N~S~J~_~Q~E~}G~Q~S~u}T}u|}Y}|}t}E~ƻ]~b~G~G~E~U~`}y}v}p}[}|w}}W~]~V~f}{{{Q}[}R}ԋ|A~ɾb~X~k~}|ھ||e}^}`}V}q}g}ʗ||^}p}|A~T}O}|L~r}M~K~Q~͕|͗||Q}Ӏ}|K~a~c~}I~Ȥ|܂}|U~ҁ}U~f~m~w}܎}}Ƹ|Э|i}z}{I~Y~L~}I~I~^~_~N~L~{~Ԅ}[~[~|ʀ}g}Ў}׈}\}}P~_~Ǽh~ļk~}P~ć}z}ۓ}v}W~Ę|||t}}}w~U~Q~Њ}|||{}}a~p~~l~o~}{~t~~~~Y~֍}}}}j}|||||]~R~O~}W~|؝}]~}q}}]}e}v}V~f}f~}}N~]~q~i~·}ߓ}U~v}}Y~z}{}Q~J~y}ܖ}t}۰||||{~||||{qr|||st||z{|mn|{|||||Ƴ|~}|}¿|c}e}g}s}|{k}Ȋ}||||j}}}t}}}}}I~x~}t}g}v}Ѐ}x}|k}d}Y~}S~y}u}o~v}{}Fb}ш}|l}N~}|{{{{ww||~}ù|||Q~P~R~|Χ|L~}}i}٠|M~a~k}{h}ˎ}S~m~o}}}L~w}q}q}O~ǿ}z}k}V~p}|~}i~w~_~}~J~Y~h~ڏ}\~}}ۚ}t~AV»d~Ru~h}]}{N~l}}i}̍}||||||Nl}ºr}|S}||||̦|W~Z}T~Z~]~}c}c}ي}Ѓ}m}y}۟||Ҁ}}o}{}[~G;l~]~_>H~e}v}[~Z~Y~W~¼|K~>h~x}R~|;}m}\u~h}Q~ӎ}͉}c}ž^x~|n|f|{b}O~q||c}l}j}`}h}Ό|_}i||{{{||ݎ}Û|d~Q~w|Y~}T~}|`~3ƹN~h|{G~s}?~U~D~]~[~ɽQ~N~<y7~=~>~K~g}|||D~|{~|||{W}}|`}I~l}||||x}j}^~|c~Ѹ||W~މ}k}f~}~|ܥ}|s|r}t}y=~[~n~L~Q[}J~F~h}b}`}[}ۋ|W~S~K~[~|P~U~n~i~_~K~i~t~}F~h~Mݡ|SJp}P~}C~_~Ȋ|H~s}{p|^}ȼV~AU~G~L~U~I~l}P}s|f}e~}:g~W~{}`}}w}c}|}އ}~_}|||c}H~E~J~<~f}m}}S~|E~}}8W~U}r|d}|x}c~Ǔ|p|́}{Ώ|{|{||||o|V}k|q|l}E~yD~c}Ȍ|l|[}C~X~7Z~H~G~|e}|j|{l|e|{M}X}J~gk~|;D÷IøGƹINȻMND@:CGJ?IĶIDOZ}4{}Ń|b~q~]~a~Y~j~d~~}^~q~t}R}`}B~T~;Yu.s}`~ȶJ~Q~V~B~ȺP~Q~;~DzF~9~=~>~<~нW}C}Q}ǴW}U}X}}=~B~a}h}D~e}i}L~Ƽ\~d~\~ýb~t}l}Wg~k}v}P~R~A~\~y0M~O~H~c~X~d~u~b~K~g}||q}A~B~k||{{|_}[~i~t|h}ޤ|||{{Ή|y|z}ѝ}z}L~_~_~||H}ߗ|u}C~y}ȿY~Q~Y~I~9~f}µL~D~G~w6~w5~B~E~[~C~l}<~A~C~?~K}V}Q}S~E~{݊|s||I~J~Z~y=~Ô|u|߻{o|W}P}A~A~B~E~Y}||R}y;~|}i}|}O~|K~[~Æ|l||<~V}L~]~P}>~H~A~o}|Ϗ||׏|y|›||h}p~T~{{{{||Z}|c}D~|{ιV}ƴJ~V~S~?~v0D~ǷN~9~I}X}R}W}k}ڈ|Żm|U}T}e}S}|||||~|||AM~8h}Դ{{^|a|t|y;~{2P~:~x-~d|;}G}k}t}h}o}^}Û|҂}]~t~Q}Ε|T~?~=~~=~V}Y}ф}{}n}{{{|||~|wx||Ҝ}ÿt~}U~}o~[~|ϙ|`}|{{{òz{{U}t|T|zo|g|~{d|n}d}k|H~R~Q}l}M~M~t~W~~M~V~}}}e~b~څ}|w}c}|k|d|p|{{{{\}J~@W}|P}U}f|t{{{{t|wF};Y~W~G~^~s}d}ת||y|{{{Ӕ|B~[}b}S~R~T~T~D~v}Q}ܕ||d}H~N~Y~c~^~K~p}^}÷f}r||h|o|ޝ|Q~`~´W~h~H~|}}@~K~K~U}V}c}m}zA~~}݂}}¹i}u}}}g~Z~ĸ\~m~y:l~˾^~O~u}G~`}S}j}_}t|ז|}}љ||}|ޝ|`}X}b}ÿ|r|ˍ|x}q~|~k~h}k}Y}|`}W}||g}i}÷l}̻{]}t}|||{F}G~M~n~P~}H~]}џ|Y~{~C~@M~|{n}T~z}Ю|W~v~|l}Q~j}ˉ}}|n}Ƚm}E~R~X~m~T~W~}j}}n}}W~}~J~~F~}C~΁}~}j}|}c~Q~M~d~}h}||`}þq~_~h}}_~]~{}Q~y}[~_~ǿu~n~~~~~GT`WU~ǿi~O~}o~[~ō}||}~r~~S~}f~a~|`~}r}Z~э}||m}h}}^}ܕ}݊}ʴ|}Ƈ}ܤ||}|||L~M~t}|j}׸|d}w}k}k}_~t}_~~}տ||||||}|{}|z}||{|}~||~|||||||||¾||||||||{|j}||||||¿}۫|d}K~U}I}|¼||ŋ|W}c}|}d}}T~\}y}ɾ_~m~|C~ȿy}u}~e}|Ȃ}|||||||{{{{||q}|`~}a~}u}Ã}x}{~}DɌ}|}a}އ}b~˰||W~l~ɒ||||e}a}N}Y~v~V~o}r}l}|V~~M~Ұ|z}X~~}}o~~}ϋ}|}ط|}EWZ}>d~m~Jl}s|{{|a}}Ϲ||xz||~|||ҵ|u}X~~N~}}w}|m}n}»f~c~Y~q~V}H~|~_}o}|~|̊}x}||ڔ}l}â|ڂ}d~ǽWBl~?b}|@<ِ|I~R~n}٧|Ӗ|t~d~`~h}ʽ|}e~Q~I~u~[~|D}u||D}G|}}}|ٓ|a|ԗ|O~=_}H~[~r}_}|{{{|}||||||{v}v}}n}}}\}ƛ|}O~7Y~l}]}||Q}M~U~N~Q~V~4J~B=b~EƻP~J~|||P~O~|g|˘|m}{[}}{|{~||||~c~}|||||ʜ|}|u~Q~Gb~h}M~_~w}n}c~m~P~}v}`~V~I~e~H~ʍ||R~\~I~Y}w}zN}V}n}\~s}þ|i}T~Fp~sU~X~U~n}U~{^~O~h~Qf}{}B~øW~^}E~S~x}j~l~\y~ˆ|M~Ż]~M~[}K~v}}}[~žt}_}|ђ}|ǿ_~z}h|µZ}?~}8~{9~:~`}B~y:~z;~E~ƺ]}P}V~}m}ڬ|E{}{|T}}͜|aS~{{{{{{{s|T}||||w}_}T}|F}O}Y|Ϳo|V}i}P~˿[~M~y4G~A~v5~Y}D~L~q}T}ڠ|b}Җ|dž|{H}Ŷt|P}ɺ^}ʼc}|n|~_|v|p|k|vC}W}I}a~ȻR~b~U~P}ŶX}L}˽c}M~[~p~]~}܄}ƿf~CB@r~>s~x~DK`~d}r|F~;=3-5N~=~?~ƳF~]~J~L~?~;~C~x.Y~@~_}f}?~ĻX~R~I~U~e}w}W}||}N~g}|{|ܛ|J~i~q~d~a~f~W~C~Y~W~~h~Ơ}\~g}|b}V~N~H~t|p|{||||]}L~؃}{|ý|l|Ѧ|Ȃ}b}Y}i};e}p}ľv}x}Ύ|w}X~m}G~J~B~J}N}O~H~V};~8~˼M~Q~W~:~\~V~A~U}`}b}D~f|Ҹ{h}a~P~^}¼||Ã|f|l}k|Z}|u|{r||{6~M~\~?~~<~F~||~}i}}9~y0e~~|O~s~Fn~N~H}ģ{R}I~m}f}{}N~D<K~\}|{ҷ{{||U}Q~<h}{{{v||I~|̃|v}]~L~{j}Y~[~G~D}=~P~:~D~O}s|]|¼}||\}|k}i~Q~n}y|S}|[}|`}|R}P~h~S~y}{۸{G}y5}[|J}~6~K~K~U}μR}F}?}u}}x}В}}Z~u}|}}]}[~Bn}Ы||X}m~o~i}||||||~|uv|uu|{}||ߜ}~݄~y~u~_}~|s|{{{{{º{{{{|{{{{{M}a}C~<U~Z}o|S}J~\~N~G~ʿk}ɐ|ʌ|̘|l}}R~}r}~J~]~J~k}|||||Ʃ|q}^~C~|~|\}?~W}{N}{Һ{w||A~T~z|ٍ|j}[}b}T}]}z}:~g|{}{˿i}G~H~ɾd}T~C~C~|}i}Q}{u|{{g}D~C~A~~@~D~W~zA~S}[}}`}z}T~O~b~f}~n|V}z;~c~K~F~L~E~k}z}߀}G~|||||y||q|ʼn|n}j~z;w~W~]}}]}E~L~E~y}p}G~߀}y}z}G~g}|||u|l}l}|J~J~p}j}À}~}s}d}|}}|}y?~ȅ|M}P}_}w}A~J~`~d~p~n~\~S~a~Y~ƻb~ƺc~R~]~T~j}M~f~p~S~]~a~̾Y~R~}D~{F~N~]~b~W~V~U~O~X~N~ч}m~x}Z}c}Ҁ|~}}y}g}~M~f}c}e}|»w}u}^}ܥ|}Q~h~x}z}|||p}i}|m}n}ݗ}}}v~s~~J~°|͎}Ļi~Žk~p~u~~v~q~r~Z~}}_~٘}ӛ}`~t~S~b~üh~X~U~}}}}̻|z}}}R~X~þ|c}y}m}z}||Ż|ʶ|r}}_~V~^~D~}U~}}Y~r}U~̓|ш}u}|}w}ѣ|Q~||||vw||||||{~|||||||m}i~p}|||~}n}k}{|ľ|á|l}x}Xa~|Θ|þ}m}ٌ}}n}_}||p|۠|||n|c}}n}y}|}ˏ}h}i}}b}||{{||w|||||{{{~|Ļ||{j}ׅ}[~׆}|]~ûd~E}f~ͅ}i}ƾ|X}]~Z~i}||{z|||ۖ}o}ˠ||ձ|z}||ј}}_~ڂ~PL~R~}X~Ko~r~a~}u~|`}X~[~~~¹\u~e}}k}}|z|j||{|п{{{{{{{||K~Q~}{S}m~}׵|w}~}M~X~{}j}ʗ|FU~v}|}|~^~܌}|}}|y|O~p}ŹP{5|r}Z}>=AN?~|||R~n~Pk}||a}Rc~|~}f}|d~f}Kݰ|[}|{}{|_~v~~}u}Z~CV~Y}|~|{{{{{||y||v|W~z}||o}Ȑ}~e}|ğ|P~F~V~M~U~Y}W}<~G~A~L~^~A~~?~J~C~Y~?7GD~{||i}ũ|s|~|n}|{{||vw||{|||°|Ⱦ|}|||||~~||[}W~N~W}V}o~F|f}ݫ|b}|s}҄}}|i}Ԍ|܂}c}|y~Ԥ|a}L~K~x}\~K~J~q~k~\}|k}R~ژ|}E~u}E̜|Ľ|L}~}S}v}f}x|c}U~D~q}i~}z}{~J~^~d~}E~m}R~d~|¹k}r}I~r}g}žn}}F~}|Ԃ}q}ן|u|ɿc}g}o}};~}9~ɻ[}=~O}h}d}_}Q}|J~\~`~j~|?g~l~C}x}{{~||y|X}e}=~X}\~9h~t}ҿj}}F~Z}Ͼl}L~O~c}\}j|{}J~M~t}a~y439R~D~}i}||||}K~I~}z}h}q}o}`}W}ƵY}c~N~8~}@~\}ʽi}R}=~=~ͼJ~[~Q~P}j~g~~>Bo~t~k~p~T~t~PNZɻP~|ي|H~>Y~u}V};~W~2w,L~x/T~P~T~X~Q~+2;~8~G~K~J~S~F~P~G~~}[}Z}ӑ|n}T~R~||b}z}X~L~V}N}i}Z}Z}p}M~{}}f~ʍ~\~_~}g~N~l}o||||F~F~T~I~q~üa~w~`}}n}m}||X}׈|`}g}W}}}~J~u|g~O~?~y}»g}ޅ|a}x}l~j}:~@~a}]~V~@~<~}?~i}l}X}{ƺd}O}{|¾|M~O~t}a}}|N~zI}|\}i}w|»{˳{d}A~\~_}V}ĴY}t5~N~n}H~E~O~|4@~~}C~U~g~G~@~O~X~U~{A~\~S~k}J}D~L~D~c}P}a}|{|ӓ|X}>~T~R~@~I}_}|ۑ|[}|m}h}µ[}f~K~D~b}Q}B~B~f}̺N~p}~<~y9~n}{H}ŏ|{=~{}M~C~d~_~J~O}b}B~C~}?~F~Z}b}b}u}h}n}q|{A}ȭ@}h{9}T};~<~O}I~i|U|t|}ؒ}}s~X~y}Ž||`~S~}|}~|z{|||ԝ}}ڂ~}~g~}͐}ϗ}}|n}n}ǐ}}}{}i~Y~T~Y}{_|j}{{{{{w|{{عz{|{{{ǽ|zB~zM}y<~?~>~|R}m||||\}Z}G~A~Q~i~b~x~~j~_~b~ހ}|ͥ|ҋ|||{|w}K~N~}D~m}|h}[}F~źZ~_}b}~@~F~L}zO}}d}h}ļ|}|}w|H}0~?}z|D~y;~`}n~n~B~j}g}\}[}k}m}m|ψ|R}m}B~K~P~B~j|||h}x}v}l}õS~D~||h||L~S~G~}t}ڂ}]}a}ۅ}z|{{{{{{{j|m|a}W~|~t~}>Z~J~R~F~ƻh}T~F~ʽi}\}R}w}p}{{Ի{i|T}G~[}u|f}|{{||{s|{v|`}N}ɶ[}U}W}Y}K~{A~\~JTTSU~փ}M~e}{F~[~U~Z~N~O~q}Ľ{}Ȁ}R~_~U~T~x~EDA7e~B~u}T~S~||i|Q~Á|z|\}Ө|h}҆}}m~[~Y~x}|~]~r~y~a~K~G~ɽr}}m}r}Ⱥ||}_~ԍ}}~}П}|}ϑ}k}T~M~~M~Љ}z}}[~c~V~M~U~Y~}}}}y}|ϖ}}}}r}Ć}z}}Z~r}d~֏}Ґ}}|}ʕ|\}}t}S~|{||||w}h}c}i}P~b~c~q~i~}|e}|||f|V~Ӈ|}Y~f}z}}||{||||{{|||s}}|n}ޕ}||}a~l~n}ޒ}J~Z}[}v}V}ݓ}|{ي|K~~}o}e}p}o}|X}ܭ|Λ|[}{}ݚ||e}i}|||٣|Ֆ}|c}|f}ӽ|}}ǫ|z}||||pp|{e}}Ƚ|{b~W~q}d~~[}d~y}}}ڊ}z|d}s|]~To~s}}|||||l}h}y}}s~q}ǖ}v~e~Ɨ}\~QT~k~n~p}|҂}}z}o}A~Y}|e}¹i}~P}i}k~q~H~}o}\}||z||ý||||||{{{{||դ|g~P~ҥ|}U~~}ƽg~V~U~͙|i~{}|a}W~}R~l}^]}F{L}u||x|[}ʐ|_}@=~b}H~t~EP~X~d}Ց||||»a~EK~G~}m|}|}T~˽W}RT~y|}Aþ}|z|{~}x}9C~f}}9]~Y}˵{{{z|˔|v|{{{||i}c~}|W~}|||p}¢||r}S~´J~z7~e}i}_}h}?~b}_}j}ǽh}F~L~;n~S~|{{{||٪|~|Z}~`}||wy|{|||||||}{}{||||`}źY~O~W}f~FƝ|¾|Щ|Pľz}ٍ}{|||||t}d}t}|ٷ|ݔ|P~Z~z~Ƚm}P~}E~d~i~m}|I~||H~G̔|˰|||r|m~S~U}~N}j}~;D~]}x}zT}à|ʿX~n}|}||t|||_|܊|k}͋|||V}Y}ۄ}g}|ʳ{d}|ӌ|X}i}s}o}f}^}x|l|||\~w~Q~e~[}[~x}|8o~Å|||\}>~^~@=X~{6ȹR~J~D~D~O}C~ʽ[~e~Z~R~p}||wF}l}|>~F~ƺT~{?~O~M~|j}}v}}}v}O~l}N~O~}_}]}i}L~S~N~M~L~ʺM~_}|?~E~z}z:~R~<ɺKHABw-I~p}NEm~u~|@@r~d~>ĻS>K}ɋ|f}o}c}M~b}C~I~|,A5[~W~ԽD~v-I~X~L~M~/8K~F~[~f~T~N~D~C~M~F~B~}x}_~j~h~}R}ُ|o|n}G~ӆ|x|j}b}s||O}u}}d~f~~}|K~V~~HH~l}۩|R~Q~|k}s}Z~b~}}||{{{{|`}X}G~x}{}h}[}m|O~}D~~}E~M~K~H~y8~^}I~S~N~\~N~m}}=~}X}Գ|a~޳||y}F~e}]}|S~V~[}|ȓ|{|e}ٖ|v|{q|d}R~{5~:~^}Q}i}G~·L~c}Y}X}Q~H~N~G~[}?~ʻR~I~R~ƹK~<~}=~?~M~Z}}|{`}w}q}K}}Q}p}Ω|{{Ϗ|~|X~ƵS~?Ch~úb~|\}{|}|M}j}~<~M~~|ݵ{<~I~K~B7{n|֐|ӛ|||a}N~n~Ff~D~H~}D~T~O~F~@~`}V}{j}v}Ջ|~|{{|f}=~2~T}`}T~}½t}^}X}P~ڀ}ÿ}}}}ˇ}s}e}w}|{{{xy||­|v}M~c~c~z~f~}|֘}}}]~}}}P~O[~H~l}X}{|{{{]}|ɜ||{˒|{^}|O}q}L~G~T}P}|{p|{|s}x}|~f|f|Ǵ{g|v}V~h~¶OCu~i~Y~O~{}X}x}|x}~E~U~I~O~R~j}d}`}}e||b|^}a}v}B~m}Y}|n}s}D~}B~R~ٛ|{N|\||ȡ|µ_}Żh}X}T~Z~F~G~{}f}a}c}t}֋|Q}b}ڍ|d}G~@~c~i}[}y|{}s|v|U}z;~C~w}C~Y}u|_}Q~T~H~H~}F~e}||{{||{{{{{{|v||N~Q~f~G~m~L~U}S~˿[~y>~h}ߖ|{չ{{{{Ŭ{̀|Y~k~\}đ|٦|||||{{{{|||ϸ{v||ͷ{{zR}ֈ}ۍ}o~zCf~Q~Q~`~[~X~j~s~s~f~v~\~}}}r}`~K~Q~|CN~x~k~h~k~h~]~Q~P~|خ|||u}n}έ|r}l}|Ӽ|}c}|ʆ}r}b~w}ܓ}_~՞|k}|o}h}w}|e}Ҽ||}ؔ}}S~w}}S}ڈ}X~̏}}g}}}}~S~n}z}i}}t}|}Ȍ}W~}ʐ}}ˆ}p~}А}||y}ϑ}q}}}n}||j}|}|M~f}]~Ċ}|}s}|}||||||q}~H~^}}X}|k}N~w}{}j~a}S|b}u}ā}}k}v}ʱ||{{{{{||͹|x}[~T~O~އ}T~ӊ}‰}ď}p~J~|z}J~r}kk~^}|ˤ|S~~~ƍ}c}b}f}}V}ºv}k}Ϗ}ʆ}փ}||[}||||||n}¹||z}m}ב}]~|Q}}}|}^~o}|ȍ}|{}}e}Ն}»}z}[}|m}b}g~{D~d~|he|{|xx||||}f}~S~l}w|}ͩ|U~|P~}}T~Ƈ|c}{|½|}Q}o}|\}X}|a}|M}^}|R~_}ҏ|{|~||r|k|Ľ|Ƚ|y}v|y}Ɣ||˩||n}U~Ӱ|W~ú_~{~d}w~k}d}}üx}Ľv~V~ڀ~O~`~|_}C~F~Z~u}HP~}|\}zL}|R}|r}8Y~S}b~f}ʽT~@~4|9~F~T}||~N~P~i~f~|~}k}]~}p|d~W~|Q~N|M~b||Ng~[~f~||Gg}N~`}{{|||x|x|{m||Cs}r~Y~||‘|g|m|\~M~żX~zH}o}D~V~>~N}M}X}ž|L~F~|||T~;S~d}|{Ÿ{{{||{y|ئ|{{||{|||||||t}{|||c}||}ƽ`}O~WG~C~V}|Ӣ|ɔ}o}|{|||}w}||||Y}`}r}V~V~t~v~}[~Y~Q~T}j}|G~ºYJ|>?v~x|W~k~w}^~}c~}}R}I}Ջ|߹{{̠|k}y}W~F>}{`~P~^}֝|Y}i}\}wG}||{{{{s||ȋ|֒|S}Ĺd}i}S}ˇ|y||I}R}u||׊}S}]~~}Ӌ|?~yA~Z}{R}{C~Q~N~^~~9`~I~t}R}E}d~f~ǽX~p~n~G~L~ƃ}N~ǽd}ȵb}q}}?~C~b~k~T~O~ʹ]}v7~C~H~m}V}]}h~}D~}H~u}|W}p}a}O~D~A~O~F~Gh~D~v1Q~M~\~J~V~z9};b~W~I~b}<[I{=[~W~GEm}º|n}|ͦ|ƞ|֮|ج|]}x:~ɸK~W~G~9~²J~M~N~ͺI~`~7~4S~ϻK~M~z3`~i~V~S~=~L~B~g}{}\~m~d~o~}?{N}|_}?~e}h}>~y}X}Ž|O}U}~}}j}|v}f~|~|?d~S}|ɧ|||{{|o}̠|vw|{{{{{{{{|p}v}I~{}|H~_~}d|ƿp}~B~J~E~|>~l}r|Q}n}>~T~I~Y~@~G~A~i}|z}}s}|I~F~|¼{|\}v}_}`}Ū{ݵ{b}j}T}{{{t|n}K~T}8~>~ͽM~=~ȼ[}X~8~M~{<~>~F~9~|2~<~P|G}L~l~^~l}u|u|u|~|z||D~>~S}{Z}}|t|T}<~y9~|\}S~y5T~B~zB~˽b}O~k}f}|{c}I~q};~Y}P~fǾZ?}|{°{{|||w}P~W~W~{=Mm~y}}C~}}ɼ|}r~@x9~R}|||`|{q}Y}@~<~ƺ]}Z|R||V}˿k}d}P|`}ǿk||||vv||}}e}d}z}}V~r}ނ}a~F~f}x}v}ۚ}^~w}~|{||y}S~}ſU~q~~9Fc~̿[~U~7H~ɽb}|Շ|t|}||||{{~T}j}\}||}~@~I~m}}|z|{{{||||d}`}s|h|k|r|x?~P}O~m~MHn~P~O~}`}c}l}ۈ}Q~j~t}i}ºt}s}w>~S}R}~||f|t|f}E~c}y|g}>~F~j}d}}|ӕ|}Q}\}Z}S}ø_}[}|}{A~^~[~f}~}}}V}Т||o}\}Y}I~[}g}N~E~՞|H~V~H~Ƹ[~v}O~C~`}Y}M}X}W}^}O~Y~L~|}|{{{{{{{{{{{|z|{{̺{{{T}L~|}}A~E~|?~F~K~Q~G~f}U}}L}\}c|T}y;n~|||||{{{{{{Ⱦ{{{t|{˗|t}\~|>o~r~f~p~~f~^~~z~l~m~h~\~f~ſk~}M~q}t}}}T~_~`~~u~\~j~z~r~h~Z~ه}|~}ң|Ɇ}}|^}t||||t}ӥ|a}}ߎ}X~~}|s}h}n}~}N~x}R~ۃ}ٌ}Ƽr}Z}y}ݚ|ȅ}|ܻ|o}b~j~y}~}̎}a~~}Ђ}P~L~|}ۏ}U~ɂ}}^~ٍ}}^~v}e}^}֍}Ђ}k}W~q}s}¼||||||e}|n}֏}||||o}y}||||||||||}]}{}X~t}T~Y~P~V}e}~^~||S~i~|||{|||||ʌ|~}`~W~T~R~`~}}}K~}|ɬ||z|a}|ó{{|}Y}}|؍|u}M~V~O~y}j}c}g}X}||Ŀ|f}|||̇}}}V}ĺs}ˊ}g}}e}|Ӻ|e}ܿ|Ľz}]~k~b~~R~|}||}b}zz||؛}||KӐ}`~]~q~}|`}ߙ}ɍ}}}|s}}}ɮ|Y~H~f~}|{{||¡|^~ɜ|o~m}}O~}|}ż}}}`~T}f}_~w~{}\}ǻd}@~W}\}|~|ƃ|N}x}p}t}|{m|ʱ{{tu|vw|^}p}{|z}`~z}Q~~}p~}||~||z}~}|[}X}~d}}|R}Y}Z}c}E~ހ}|K}A~O~{H~M~>~n}:y.b}ȿi}~B~D~X~F~>~ȺN~C~O}|Ƞ|{{|T~R~S~V~l}p|F}͗|_}d~]~~}|g~S~C~Cy~|b}||n}d}|:~}<~y|{T}Æ|O}ʅ|||||}n}x}M~}Â}Ķ{f}ȹM~h}_~\~S~y9~X~5g}l}>~\}\}S}j}z:~H~A~t|k|Ս||||{{|z||{|{||qq|{||||||e~m}||~`}{}g~_}u}v~^}y}\}H~h}P~{}S}p}d~i}|||ׁ}g~|||{|j~zD~\~»k~|Ƅ|u~;f~`~~@~a}|k}]~Oy<~A|<|Y~~}|g~D~a~p~ނ}|C~||||I~h}O~@Il}»]~l~v~x}r||G~U}p}|{{||j|x|o|||υ||b}>~C~v4~a}a}||Q}Q~t~Y}c}8~Q}I~O~z4V~a~`~U~}}j}c~R~|ɒ|Z}|c}H~{=q~ĸ[~W~L~Q~R~p}j}P~J~^}c}O~a~@;a~ɾ]~r}b}J~ƻg}N~{@~}^}]}R}ȼ[~L~ĸV~^~U~`~O~;Gh~z}V~F~a}A~w}|B~Ⱦ[~k~I~p}ɾ`}]~IBAS\~~d|d}W~Q~u}q}i}V}o}t}k~T~q}k}G~ɻ^}r}E~P~?~y:~D~[~ʹN~@~D~C~J~E~D~>~?~^}Z}w}R~L~~N~|X}g}X}A~I~M~>~7~v2~C~V~^~M~F~}j}e}ƚ|g}~}@_}b}|{{{{|||||{{{{{{{{u|ɾ{||y}U~N~d~a}K}_~M~p}<~C~{<~E~Q~y1P~H~C~;~L~J~}?~ý|~V}}}~L~l~}҂||}]}ҍ|ȅ|||x|`}x}U~d}T~n|||]}?~t}_}x<~K}N}Ⱦc}D~8~:~:~P~d}F}źZ}οY}P|ǽj|Y}^|Y}{~|{{Ů{x||ƾt}Ǿp}t}Y}L~ɹR~}6~zH}B~|{ǹW}^~N}k|t{N}}O}|D}c}\}ľ|{m|F}t}k}}7~S~}8{7k}p|˺{{||||ǁ}a}u}}i~p~FAi~p}\}z}z}f~Z~O~ý||٩{|k}H}e|q|v|v|}S|{l{o|g}Y}{{{{{{{||||N~\~U~r~c~b~}h}b}|||ɒ}̉}R~ۇ}I}Y}z}M~^~7T=D~x}[~G~i}~`|t|z||||{|V}u}Z~F~K}Y}~=~ԉ|t|{{{{{|||g}Źh}X}||[}s7~U}N}G~^~{~Im~I~t}j|o}s}|}_~_~J~p}w}{R}՜||N}{Y|\|L|I|V|{|X}J}N}\}:~<~Ƚ^}\}a|j}F~k}[}f}J~ɾ_~^~S~}}v}}ǽf}H}w|||k|m}ù]~X~a}ϙ|[}x}l}|x}V~U~ĸb}z@~s}o}{8~d}q}q}u}}D~F~|}||{{{{{{{{{{{{{{{¸{{{{a}|U}E~B~V}k}F~U~C~H~z}s|ۆ|v}Oi~|J}c}||||||~|zz|{{||Ρ|ƿ}y}ۃ}}k~Z~p}Z~q~_~}W~Z~i~}`~f~|e}׈}n}~I~w}}Ր}ˇ}{}i}|m}m}}T~p~~I`~j}}Lj}~||S}ӌ|Z}|Ȥ|Ĩ|k}ʄ}|}}ݭ|P~x~Ë}|ф|m}l}j}q}}j~̃}O~H~]}܌}̈}|H~X~u}Z}|t}\~a~W~N~}X}|}}~Q~l}ÿ}}}p}k}p}T~Žt~׍}m}|o}S~|o}}{}|}n}j~DM~V~}|||||Ө}o}u}{}޷|v}}||ɵ|e}}\}p}d}|{ҩ|s}|S}|{i}}||||{||޲||k}o}Y~_~`~V~~~Y~o}w}}ߨ|}||S~d}|ľ|zO}||s}}|C~}a}ٌ|]}̃}]~U}O}|g}|||||k}У|l}i}|}Y}}|_~n~r}a~}_~]~}~}}c}S~}||n}||a}\~נ|U~\~~}܌}x~N~z}h}x|r}}{}{|}W~ˉ}ӆ}m}v}A~B~|||͞|ف}a~g}\~W~{}z}ը|a}}n}{}|g~F~y}I~ȼb}z|Q}B~G~_}o}B~R}{p|{|||{||{Ƭ||{j}T~}||Z~O}|}}|}f}̏}|}̈}Y}`}޵|u||ܖ|d}G~F~t}\~ĹW~ƾq}ķ\}E~Q~0X~A~S}^~c}ʼO~o}ߒ|Y~ƻ_}U}P}{|||q|d~]}q}Y}ԗ|~|\}úY~}~|~~Gn}i||x~L~@@}|{F~=E~{{{ȿ{|ɿe}@i}e}\}||l|{{|{{{|{|O}<~C~C~¶N~K~[}Z~ŶP~G~÷a}d}@~w|z|\}y58;y:~|ݎ|ˆ||||u|T}p||||h}|||||{|||[~}ؙ||U}X}y|ʗ|ߪ|}}}}T~D~|Z~{}b}g}h}W}s|{|{{||||{{{a}L~}k}|Q~l~f~l~f~c~E~Q}|ڛ|`~I~J~}`}|úk}||p}u}>_Ϙ|{B~}\}{|{֜|?K~~R}I~Q}p~|ڃ}b~^}|Վ|l~Ĺl}ٲ{x}j}}|ͼ~|k|U}W}U}ĶY}öZ}y8~;~h}b};~L~U}C~W}w4~?~߉||X}U~h~|9f~Z~Q~n~}=o~|}u}J~I~E~~@~ź_~X~źZ~S~T~L~A~O~q}N~S~_~L~E~G~x>~K~G~G~T}χ||y>~r}w}I~I~F~s}ݟ||}B~X~H~[}d}^}ݖ|Џ|w||j}j};~D~=~J~H~G~?~r}n~`Zq}º|~J~X~P~`~r~c~W~b~f~g}c}<~U~H~M~K~ŴL~V~b~T~?~@~K~A~k}d}k}?~I~ǻO~S~I~B~A~ŸP~^~~5T~ğ{߲{M}g}i}b}˽Y}|U}}5~X~A;`~b~m~Z~z}^}_}V~h~\~}k}|{{{{ż||u}||r}X~т}g|{{|k|x|o|P}}E~U~J~R~^~P~X}i}ʽM~\~]~M~S~D~s}}<~E~ĺ`}=~Q~[}{|||c}Z~}{r|Ƚ^}|<~P}{||z}[}]}l}o|w|r}|}ſ|r|I~ƽ\~p}r}V}U}N}X~T~E}?}u|Ú{ڢ{J}~B}f|Ɩ{{{`|ł|̪{{{{{|n}C~ɹM~>~;H~L~I~ŴS~m}X}W}A~j}^}^}9~[}|||~v|wI}ѻd}J}\}H}J}X}o}o}ͿX~]~L~l}{{|||b}؆}z}`~v~\~Y~U~_~M}c}a}e}o}U}Z}O~Ђ}¼|||c}ȹR~L~ƻ\}i|]}`}ց||`|j}f}{{{v{{Ĭ{|||ºy}ͅ}}}n~j~Z~Y~}||tv|rt|yy|{R}n~z~I~]~W}f}оV~x;~`~9[~P~ŹR~?~ڏ|{{м{]}O~q|Ϸ{t|xE}E~E~M~e}żf}s}u|{|{|{{|;{{|||f}j}z}ƹh}ɼn}N}}L}Q~I|?u~k~?~S}P~}}Z}f|Ș{[}m}E~G~Y}z@~Ź_}z|[|h|j|F}E}ܐ{{P|Z|J}/~E}k|d|N}u}F~M~n}x}|^}C~L~<|:F~˽g}n}ĸe}e}m|{{ҏ|q}U~h~f~I}x|Ζ|i}Q}օ|Ā|x?~e~k}|@~u}F~:~M~µZ~G~D~w}o}}|||{{{{{{{{{{{{|||؍}»p}żj}\}C~ܳ{{Ġ{l|{{]}i}L~G~S~f|{ŭ{÷n}LGS~||||{z{|{|wx||{{~|||}Q~{}|{||b}g~x~K~}}M~^~X}٘|Y}n}P~ц}n}p}||j}d}ݡ||t}l}g}}\~z~}u}}E~|V~j~Q~O~o~J~l~}m}h}}m}͆}˄}b}ܑ}a}Z}ӆ}W~\~}[~N~}e}R~n}[~m~t}V~x}{K~Q~]~H~}}|}}ʋ}d~Z~ƽr}ԋ}Z~P~X~k}~}Ć}|ի|ҟ|}j~W~S~i~}Ey~`~^~}v}~}p}i}|f}ێ}~O~}}[~̂}\}q}»||||||j}h}||}V}֞|{|u}}|||||{||{x|{|d}z}_~i}۸||}—}{}}t|Ņ}Ї}|||׭|ߣ|\}S}ɾm}a}x}h}_}f}T}}}܅}}}|~c|i|½}|{|e}b}||a~_~f}Z~|e}i}Ջ}h}|[~[~}MO~s}…}o}؁}}S~|||Ȓ}|v}n}p}|}X}PN~|S~Y}U}w}X~E~D~c}^}øj}Ϝ|}}V~E~D~M~U~K~ǁ|R}V}F~|i~i~Ž~}|_}~}||A~S~}`~k~|n}Q}X}b}c~R}m}x:~C~>~r}}|uu|~||||||ɧ|}e~`}R~t|ֵ|a~…}t}p~}}q~~X}r|X}||x}R}F~Df~Е}F~X~m}K~L~E~N}>~G~X~ξL~S~|0N}k}J~ʾS~ד|_}j}V}X}}|||d}t|ܤ|e}}r|h}e~e~́}M~Ľt}}α{c~s~w}g~b[}{a}=BƆ|{||||{>~ͿJ7_~8K}t|{|ȹ{~p|||~|{|U}h}j}i}k}j}i}s}?~X~=~O~>~Z~G~?~F~A~H~9b~_~5J~N}Ʌ|a}ȭ|{||{||w}||xy|{||z|||a}c}}G~|}b}u}Ǿf}i}e}̄}D~w}z|~}x}}}|D~c}f}c}|||}||||Ƹ|||ʜ||||v~Z~x~_~`~S~V~z}I~^~U~C[~V~j}H~D~|}y}|E~}}|~{Cm}{}۟|̚}|||n}t|X~}H~i}|e}R~|b}h~`~Y}ϔ|}S}|O~O~J~O~l}|}p}r|~e|ӄ|÷[}k}8~:~ƶE~C~P}R}|c~h}j|w|]}||Ӻ|ˁ}Q~H~Z~\~S~X~[~^~h~ûZ~G~N~Q~˾[~ôN~F~X~zE~K~J~]~N~B~\~:P~D~C~w}F~F~]~<^~:T~<~B~W~B~L~y=~{A~D~E~u}z=~z?~Y}ʎ|x?~E~`~8~F~b~D~W~K~J~i~ļ\~a~<>G~j~N~X~v~`~U~}S~p~d~ǻW~L~z}H~J~D~>~E~>~}:~j}G~G~ƻd}I~C~8~@~w7~y4~c}9~~6~?~B~l}Q}C~c~x4U~R~h}և|^}z<~a}b}a}p}F~ȼT~ȼT~U~=~G~N~P~y}|}u}f~e~>O~|?}||v|{w|`}E~|g}J~»i~}I~z=~m}W}||P}ν\}|>~R~K~Y}}P}}p}C~Q~z1C~@~F~A~>~N~M~E~H~źS~U~^}|o|ѧ||||޴||{̎||~m|{p|\}||X}q}m}х}l}}J}y}M~W~r}q}L}<~l}X~|{м{ݾ{d|A~a}|úY}K}V}\}<~V}S}͝{||W~|=~?~K~ŴH~E~S~_}|X}o|\}[}d}?~5~:~·S}k|{{[|~|C~e}I}T}J}V}K}ڃ|x|R~n}|?~b~|}F~q}s||]}R~[~ûg~k}^~||M~N|{{{|}T~n}_}ɑ|_}F~DLM~K}^}g}||Z}D~i}{v{|{{{f|y||j}o}N~m~z~~Mz~|||||vw|{{n~t~7~*4K~Y|ԕ{~I}q|{<~s}E~?~[}{I}O}¼|^}u}r|ī{z|j}b~[~=~·W}O}źi}{{{{{{{{{}|y|||W}m}ȼp}}n}Q~n~@@R~;ͿT~Z}j}P~N~ɿa~p}a}|9~}>~|r|}}Y};}K}\}[}S}P}_|Ő{T|}L|H}T|c|o|{{y|w}N}V}u}{`|{_}v~r~C>ź^~F~z@~ʾf}|{Թ{l}G~S~ƻ]~V~_~U}~}F~v}d}\}p}^~J~k}p}H~Q~D~x<~Z~Y~d~x9E~`}v}y}v}Ƽg}͒|{{{{{{{c}y}n|˄|~N}f}O~C~{{ӯ{ǿ}ұ{c}P~L~r}X}c}ǻz|ѵ{o||b}V~`~||||xy|||{|{{{{||p}ς}S~T~Ƃ}ٯ|~|b|y}Z~T~ф}]}p}R~ۃ}b}e}]}||l}m}g}||{|z||}|f}`}t}c}~A~L~W~\~c}}}~}L~Y~q~L~Žp}S~{E~_}H~a~T~g~}W~X~g}r}N~Z~H~žz}|n}_~{|ޘ||z}՞||}p}½}||y}[~ΐ}|||||‹}v}؀}P~|Ŵ|Ԛ}|t}T~}_}|P~{~žm~U~b~L~͇}ٍ}p}zM~N~ƾ}}K~݇}|\}|}}M~݇}}Y~Ǝ}̵||q}|ު||||e}||||}]}|p}||z}}{z|}t|}||t}ŀ}|c}x~U~||P~ƺm}{||Ơ|q}L~}U~|}}D~z}|}U}|q}ɿo}}f}||ߐ}c}{|t|ѡ|f}؛|||\}f}g}p~}||}B~c}}`}\~ſ}ɫ|j~̉}s}t}|}e}ٛ}g}{}}|{|s}¼y}c}_}\}}H~r}c}\}~H~e~t}f}f}L}zI}o}ۇ|h}i}͗|U~[~Z~4ƹ`}׋|s|Ԛ||T}j}X}{>~e~N}||n}b~b~o}>~ч|F~|[}b}R}ɽd}|t}k~B~|x|{||||||«|_}V~t}Z~}x}}|K~e~He}r~|l|v}z~R~a~||p}h~c~ξ|w||zA~P~[~B~P}b}Z~X~v8~u}Z}ǼR~W~F~ƽa}Ћ|x|||Y}s}נ|ƿ|e}Y~R}|}z|r|q||R|~l~n|~Y}a}{~s~l~f|R~}Z~z|r~FŽ]~c~`~R~ŸR~>z1ɼQ~>G~u|w}s|{{ӥ|u||q}J~V~;~F~=~ĸ_}[}{|||||v}|r|E~ȿ]~F<a~W~?}3\~ſ|T}S~ĸa}{{{|{|t}|||{|{||U~|~}|r}Z}H~N~R~}B~|<N~J~͊|ԕ|[}b}y}]}J~Β|x||||||j}X~~a~|z}I~g}o}||F~j}T~_~p~ù\~h}h}M~p~[~^~n~L~Z}Ή}R~i}||͝|ÿ||ޭ||||n}g~C~j}|I~ƿ|||ӡ|f}}s}Y}u}}V~N~K~}r}w}c}|ˉ|^}W}M}ƷW}j}N~K}>~m}܌||]~^}e~6T~N~}yA~p}r}J~T~^~p~n~^~b~W~S~I~Ż]~g~Z~ɻU~:~U~z}||E~_~ͼP~a}}O~ʻV~I~\~=9X~_~{}_}I~˾V~W~\~J~F~6S~G~źY~G~b}{|p||ͺ]}Ӆ|L}D~S}h}{<~P~L~G~R~9|9V}ľ||x|z|d~JFy~\~F~L~F~E~G~I~W~[~ŷN~E~K~N~I~s}m}K}U}W}`}̺X}ѺV}R}\}B~F~L~[~B~z4~Z~59_~}R~_~ɽX~j~C~ɻ`}w}G~X~]~d~W~Y~d~_~W~e~P~m}}o~n~M~|`~}}n}|u~»i~n}i}|}}K~}~I~]~u~S}̿|h||e}c}}S~a~J~^}P}\~[~@~p}O}ʅ|[}T}g}p}o}H~y;~q}G~I~q}Ǧ|̮|||`}Q~I|||V}u}y9~D~h~W~l}W}g}a}{}ľu}Fz~э|d}L~O~z9~g}ղ{{|{|ǁ|@~κI~b}>~L~L}{<}5~D}|{P}H}:}z0~:~:~?~G~T}S~i~Ʒs}}t}t|Դ{i}ο[};~F~:~?~z2~k|T|?}V}9~||ºv|z4~N}Q}ێ|K}x9~{<~|J}i~L~^}|b}||d~Q~K~B~`}||}|||{{{|f~ޕ}`}|q}|C~l~ZGT~[}U}e}n}|W}|݄}d}t|պ{{g|r{n{{|р}U~~~~RMh~}}{}||||~}[}R}H~K~¹`~~J~R~F~żk|O|٬{R~p}T}z|{{֋|N}1~E}O};~J~@~V~p}q|||~w|{{{{{{{{{{ѣ|b}|T}|m}S~^~~:_~t}W~L~z=~W}u}}H~h~o~=<g}ȃ|d}X}>~Q~ǿg}o}f}T}Q}F}R}@~F~_}ހ|n}d}i}|h}|o}Ƚk}m|;{w|m}M~Z~e~j~w~ǽ[~J~Ǿh}|N}Ȋ||`}a~c~m~a~R}t}D~q}i~j~C~L~\~l}v}l}x}p}u}Ǽ\~ĸ\~V~G~C~l}g}H~ɽ^~b~G~^}Z}|W}[}|{Մ|x>~Y}o|{f|Y}^}w=~\}X}\}p}ŵ_}f}u}@~s|r|c}_}||{Ӿ{|||||z|||xy|vv||{zz|{{{|}t}ʁ}o}{}w}}І}ӏ}R~N~Žs}p}{J~t}\}n}ƿ{}z}p}~}e~o}b}f}|||ݱ|{|Z}f}y}B~L~U~[~I~ߔ}ǁ}o}{|k}}|_}K~w}z}}R~G~C~G}c}}u}x}}`}b}ʿq}}e}u}}}h}Α}w}}P~q}ԉ}v}|ÿ}{}ýz}\~_~}}v}Z~`~֍}|o}q}|ݎ}ބ}Y}y}}̸|}_~V~CFt~]~ĉ}_}s~u~[~O~~}}T~K~w}}}ɂ}~R~}Ք}}[~Ա|ȶ|||}]}b}r}|}_}o}z}m}V}v|{y|{|~}M~L~K~f}^}Z~·}|T}X~Z}u}و}}~K~L~P~c}}}j~O~}N~w}||R}m}b}l}Ʃ||[}}|z|r}ȿ|}|k}z}}V}L~W~h}c}||K~b~{}ŀ}ߟ}}}U~~T}׃}i}z}üd~W~Dy}|||l}{a}f}}}}~}}`}X~g}H~W~^}Z}p|{<~>~<~E~^}`}x~m~^~ƺR~x}l}?~A~J~}:~A~ķP~D~I~t||l}}j~;|Q}{|׏|x|x|o||L~}8~>~X~A~n|w|{|}|ն|||Q~g~T~Dž}}ž|[}s}e}K~z~W~v}ڛ}Ͻ|||I~U}|ŋ|¹g}Y}h}T~P~r}b~;{4H~V~~2w1@~x|ĉ|=~D~˽O~g}s|{{n||N~F~L~~F~y}|||¼q}{|{|||{{|e}r~|c}d~m}k|~K~e}N~S~g~P}p}DGH~w}F~U}?~M~?~ޜ|{z|m|v|b}I~Q~S~W~[~9Z~:~R}||֨|||Y}{v|[}Q~\~O~7I~^}ǹP~K}}_|R}m}N}{{{{|T}Ч||{|{{||\}|||}|y|zB~ǾY~BO~O~m}b}b}[}Y}`}o|{|v}}ڞ|߮|w}|̕}i~h~}}v}w}H~N~͔||||x}~}}v}i}a}Y~H~V~ĹX~;:U~k}|^}`}n}߄}~}d}Ŀm}X}Q}|c}W~j}|_}_}Ð|ٙ||[}øi}D~M~T~}T~º_~e~]~F~K~`~|}i}]}d}u}w}{|v|~.M}u|j}Z}||]}F~S~e~õS~X}Q~÷T~P~S~~}~}I~K~h~`~X~N~n}t}W~n~<N~?~U}Ы{{ʹ{{|s}H~`~|:Fo~k~ȻW~I~k}q}`~U~ƸS~Q~H~Q~I~q}W}y>~q}B~s}8~q}l}߅|@~Y~@~F}N}N}N}_}F~B~ļa~Q}B~I~q}k}}}}e}^~\IGAK~G~ƹd}~}L~{@~B~_}[}T}[}m}:~{7~R}]}y8~v5~e}6~?~O~@~9~y7~w8~i}T~>}4H~}I~Y~c}{U}X~|}B~e~q~R~^~R~=~R~d~C~|M~[~;~W~F~\~R~h}ބ|ƍ|U~u}w}d}z~O~E~P~M~H~J~t~p~}{{{v|V}k|a}X}d}Ń|k|U};~I~¾|{{{{{||q}t||К|Ђ}p}||||o}s}d~o|ڻ{f}o}v6~i}ŸU~ŹR~[}q||U}x|u|u|L~ſ|E~[~e}|P}}}M~{{{r|]}\}z}X}h|l|W}S}B}O~>~6~4~x+~9}9}6~:~^}`}j}B~M~c}V~ʾp}a}X}ҿY}Z}f}8~L~<~5~ĹV}z6~:~B~B~e}j}x|u|X}_}g}E_~͡|e}|X}Փ|T}ݢ|}H~ø[~ɼV~y=~@Fj}||{{|||v}Ŀ|ž|Z}v}?~N~j~GICw3A~I}M}V}b}M}r||{{{c|Y{{Ɍ|ºa}G~>~U~ڒ}z~ۄ~r~h~e~f~|C~n}l}ϳ||ҡ||O}֎}ԗ}}Ԙ}g~\~|2N~J}F~f|{{{{|[|k}Q~:~C~8/b}f}ݩ|ƒ|W}e|{¿{{{{{{{v|ܐ|Y}|Z}c}E~I~C~I~y6M~]}M~V}b}Ľn}{}~L~a~}~h~o~c~\~O~yC~ȓ|i}A~L}U}O}Y}J}L}U~Y~S~@~8~>~}B~r~[~x=~}a}q|{{U}Q}b}|¹p}J~½_~e~O~I~w}U}|Բ|~|l}U~@X~l~?l~S~a~c~b~U~}>~O~`}k}t}Y}]}J}@~@~]}|}q}k}_}]~}E~p}Żl}]}Z}||||W}]}Y}{r|ω|d}p}}\}d|Q}t}V~`}e}οj}|P}ܿ{z|n|Q}w}m}}|{|||{||{}|tu||||{{{{{׬|P~J~}g}_}ؒ}}S~Z~T~}X~b~}}}݌}`}g}g}Ę|n}^}}T~ӊ}|}||e}}{}}]~N~E~O~g}|||ԋ}ڼ||{{{||\}ÿ|Z}}W~û_~^~m}z}žl~W~|٠|i}Y~R~^~d}Ќ}h}źa~R~z~ы}`}T~l}m}~}p}j}φ}}y}f~N~h~[~]~~}|Y}|V~P~}Ƣ||u~e~|}JA`~W~\~Y~{I~ӕ}Ǔ}||`}X~U~[~}|J~W~S~Â}ÿ}ێ}׍}c}h}Y~|T~|b~ѥ||^~Ɉ}U}O~½|»|]}Z~g}ܟ|ڌ|F~}}t}u}}V~g}~N~ߕ}}}yG~s}l~H~^~l}l~q~X~j}z}R~žw}|`}}}W~}`~}V}g}L~|d}m}|r|ك}p~~b}y|х}J~}U}l}||l}||\~}||`}}K~U}ߘ||K~}y|}x~V~|||ƅ}W~c~F~S~}A~e}@g~~}ϗ|Z~]}C~<~E~@~̾O~D~Q}T}[~X~?~U}=~v}l}^}>~n}D~I~{:~?~û|p}u}s}l}Y~X}V}p|{||Ӹ|||_~N~L~t}|n}Ƽn}{||j}ر||r}t~߉}Y~K~w~}g}t}N~W}q~Ų|v}t}y|y~Px}|u|ӄ||M}l}M~o}D~i~L~P~ǹP~O~e~y:~b}|:~n|u}A~l}O}l|r|}|l}G~}:~C~X}|א|{K~G~l|{{vw||||{||r|x|Z~|>a}k}|i~}>y~N~k}F}m~FW~E~Q}yF}Żk||M}u|{{{{|G~P~H~6Y~A~}0:ͽQ~Y}C~[}l}K~k}~|̕||^}ƾk}C~H~S~l}l}ɻK~>~r|W}P}t|y|{||v||[}{|||}|||l}t}q||ڢ||[}Q~y}`~y@~l}H~I~p}k}b}Ѝ|i|U}х|g}t}Ā}߮|}}}π}|||:l~¹[~O~r~J~`}¼t}|_}F_~gS~c}}S~r}H~n}r}U~Z}|||_}\}{}|s|m}i}}|f}||V}R}R}Y}¹n}|}t}S~s}u}Q~F~s}L~I~l~a~n~]~I~]~C~t}p}DV~;~J~|}Ǽ~|LG~||v|i}A~ķI~P~G~W~6a~:~ðH~J~|}X~w}a}j}߅}_}||}T~}H~E~ñJ~y4~}|Ş{{|n}D~A~S~j~]~¸k}¼|[}ü||G~F~F~q}L~Z~G~i}R}b}ǹ_}R}Ƃ||U}m}θS}˄|W}w1~J~c~{7P~T}Y}x}Że}t}|ϊ|Y}U}T}{@~t}M~PU?m~l~b~b~F~F~{}D~D~t}?~=~8~;~=~B~>~x9~U}}F}U}J}\}X}b}<~F~u}^}}a~V~||\}X}g}A~|@~h~HΈ|]}u|^}FG~i}|w|^}|x|~R}ˈ|}}d}|=e~~R}~aP~C~G~l~B^~ȿc~y}|ő|˺{{{{{{{{{В|r}z;~Q~b}{{{ƺ{{{|{{{{{{}߆}{{{{||s|b}L~A~k|r}y=~}E}[}V}z}z<~f|`|m}q}N}d~Q~F~o}x|j}<r|{{{”{չ{{|i}^}b}v}I~>6E~9~[}N}?}W}e}º|ۊ|I}w|R}J}|F}|H~͹]}a}{4~B~Y~P~C~M~B~k}|=~K~{=~Ɣ|y;~E~\}|V}ʸb}|||¿||ȹ^}n}K}ƹ~|?~W~P~R~f~z5D}p|l|{{{z|Y}K~m}П|P}g}9~F~;J??Ld~W~y|J}ĺb}v|{{{{{||h}Ļb~B~>~Z}V}|}w~7i~¹^~D~ƽi}e}f}[}^}{F}a|zD}޵||^~c~O}2~S~J~|{{{{{{f|z7~g}|?~|6W~f}I~\}|ۊ|x||{{{{ǿ{{п{{L}|ͯ{{|Y}P~N~ѾV~ĺ^~~9M~X~Q~}S~f}W~h~Cn~c~`~E~Q~u}Z}Z}R}R}|׋|n|{D}q}L~|>`?˽R~ǻY}O}Ӥ|}\~F~U~J~z|}P~}C~||}{|e}F~B~k}ǽh}M~K~|Ė|}T}u}Q~q}O~V~v~R~O~r~ǽ]~̿]~R~o}R~÷[~M~H~{C~d}q}T}j}r}I~s}ǽg}{}{}|}F~r}ɼk}{{{Ͻ{{x|ۙ|F~~}ݽ{||{ɾ{ҍ|ƍ|r|T}Y}}}V}[}|X}zE~{~^~}}}~}|||wx|{z{|{|||߹|u}ڭ|̛|||}]~R~}L~s}q}׃}h}x}Y~W~i~R~}s}l}|s}҄}Q~`~ڈ}}ûz}||Х|i}c}}}}}b}O}N~DS~Y}ю|||{{_}|úw}U~|||{|m}]}^}|^}¿||X}|||Û||K~e}`~q}U~r}||ԋ}|˳||||||R~g~HSPb~}LA|~}|̘}\~d~|~o~T~Ë}č}|Ŷ||||q~U~a}s}̇}|}Z~o}\~k}v}}ÿ}Y~Ս}o}|U}Ɉ}|ƀ}e}||{|}å|ǣ|||T}d|d}U~S~q}f}|x}o}¸h}q}|ɟ|}o}R~|}C~~E~W~z}R}o||l|}u~v~c}e}I~]}P~b}|||X~|p}ި|i}}Ն}{Š|||~}}Ӊ}Y}d}L~o}ι|d}}|}u}ބ~f~á|n}i}͒}V}U}߉}|{|q}w}l~]}a}J}U}܄}ͳ|o|ȩ|f~G~̿S~G~S~|8~B~w8~V}>~t|E~C~ɼ\}M}b|t||||ߕ|Ӈ|b}[}|Z~y=~A~{9~j|m|ƿ|öP~Ź]}l|{||ҳ|Y~J~t}C~Y}Ĺi}\}|||||N~l~a~|Bf~_}[~Ӷ|ێ}z~O~T}Ŀ~}|ʘ|||}{}{~k|g}}x}d}{|ȻW~N~|>~s}^}J~a~z6~~7~X}ȿ||`}̿T~^}q|nj|j~W}f}ý|b}l}M}]}ی|W~}||{{{{{{||}S}R~i}r|i}΀}a~|zU}P}i}Žx}~F~±V}:QAZ~H~e}s|{{{{{|ƹP~`}M~=O~P~5z.x4~I}D~ɻM~|U}I~:Q~M~ƻT~^~b~~6\~E~K~W~N~?~v|g|ƃ|}|{}|{{o}||e}|{|w~W~||w}ř|¾||U}`}}U}I~E~v}H~¼Z~B~Q}J~P}Y}T}U}}a}W~|}V}a~s||ب|K~u~He~Ph~\~l}ȿ|@~ľe~~x~݋}M~w~¸]~P}K~I~w~z|p}{e}ԡ||\}U}ſ{|ڊ|]}=~\}e}y}W}L}Q}i}Z~H~R~\~x3K~ƿm}ׄ}Ӂ}ۃ}߆}M~P~V~{}j}t}g}M~Z~9j~ۇ}L}ZNt}E~d}A~}U}r}źP~31P~D~x}7<6c~J~=~E~B~G~j~e~A~|_}u}_}d~9>~;~7~t|i}[J~Y}w}n}Λ|þ|d}Λ|||g}F~Ƚk}Q}[}N~`~[~|<~yN}i|ɀ|tE}̶v|Ʋt|V}ưS}G~ŶJ~5[~\}~f|ڠ||̇|ȼ`}?~?~w}Y~}V}B~E~F~\~\~BǽXDm~Y~]~ɻR~@~t}w}~?~E~|=~G~J~Y~N~:~v6~?~B~I~I~ʼP~X~d~A~p}x|{|||ʟ||v|i}T}ľ||l}D~i}|W~}h}Ob}f~l~Kއ|H~u}ƻf}e}K~}B~L~R~r|E~d}X}΅}}|||{~|||n}V}{{{ء||{{{|d}K~v||S}ո{{{{{{{{{ʾ{ù{{y|{{{{{{|a}W}u|{E}\}X}[}r}Ƽa}N~d}y|_}}s~Z~ƻc}|M~r}v}|{|X}Z}~|{{t|}|{{|ف|H}űR}Y~F@[}X|G}`}@~H~U~x}T}W}Ӊ|{j|O}H}>~_}U~;B8}1ȼA~:~zH}{=~ŷQ~ȻW~s}\}{P}k}^}|{{߫{|Ш|Χ|||c}ɿ[}N}Y}O~M~[}}S~Z~H~I~T~I~̀}Q~D~N~g}ºg}`}|e~Y~@HǸN~j}?~f}=~P}π|{j||u|{{̟{к{{{i}h~|1~>}zF}{d}L~x8@[~e}}{ý{`}C~÷q|{u|]}|c}Z~S~q}e}w2~9~q|Q}O}k|{{{{|m|W}X}m}B~};~o}Y}f}x<~Ђ|{{{{{{{{{{ò{ʂ|=~Q}||E~ѺQ~:S~}d}բ|z||b~s}ְ|s}x~Ni~w~JGD~||K}~||{{{{v|||{}EnKW~r}|{۳{n||a}^~z}Z}|{O}T}{u}þh}|L}b}||Q~V~T~|I~H~w}r}\}_}L~G~yC~F~W~@n~`~S~X~U~E~a}˾[~H~X}R}c}p}}>~G~N~n~Q~J~V~S~˺h}U}ӑ|{{m|{¹{x|{x|{{|a}r|m|q|ʼc}q}a~m}Q~m}}J~G~|A~\~R~ǿj~Y~z}u}|||||||{|ܺ|ć}P~}|S~\~h~S~i}`}h}`}||o}À}m}v}L~\~|K~G~}|M~|u}t}||ҧ||Y}c}|ݤ|U}|l}u}n}ۅ}e}|||Ӈ}O~y}g}c}}||||߭|}|{|v}||Ŷ|a}ʂ}r}r}}}^}|Ґ}o}}}b~d~t}}`~s~k~||h~_~݇~l~w~NPOX~||ǜ}~~~>p~[~P~g~_~W~||||p}Ű|}}~c}͢|e}؝}p}z}{}V~ݕ}}k}O~K~ï||||||Ϟ||ƻ||ƈ|[}V~_~}|}a}Q}߇}X}]~W~ђ}މ}}}I~o~e}Z}]}{J}m}]}i}r}J~g}ي}͊}ҍ|e}ݮ|z}n}~}я|u|k|w|{|}_}|}g}a|M|O~~Ҏ}|}}g~̠||n}|||Т|O~M~ƾz}m~w~Ϲ|v}y}s}q~M~|}ƿ}}_~}l~x|[~m}˃}R~e}{||m}v~p~F~S}Ľ|~[|ު{e|ʿd}ߕ|w|R}9~L}|s|h}u|||}g}|}p~VV~j}u|k|n||N}[}e|f|||||`}F~Q~÷T~|}_}}}o}r}|X~JN~Nl~k}T~k~|[}}A~m~E[}x|||x}t}Y~Y~e}k~{}|{|F}x3;ʻP~|b}d}W~@~D~A~@~@~y}j}||k|t~^}̲{s|R}a}u}U}i|i}֓|{|{||yy|{z{R~~R~{DDn}{|{||{y||٫|}n}H~}A~f~>=L~`}K}G}R}D}p|s|{{p|B~763W~5x-`}|H}{;~Z}j|v|\}T~f~Z~}4gDj}W~B~ȻP~D~:~O~U}h}ǹN~j}Z}f|v|Ɠ|u|z}|~|[}{i}ɿ|u||||ľd~۟|{y|j~|I~J~O~Z}Q}|Y}\~l}Y}y}r}\~q}||ڙ|ƽ|b}a}n}r}Y~^~u}||||N}]~W~_~܇}}h}|X}}M~t}|{j|a}T}͏|||Ѕ|||h|]}L}G~]}R}j}l}K~]~a~x3c~_~J~L~x}j}m}J~B~L~M~L~?~L~ʾd~l~G~Ba~m~|<@P~=DO~M~u}P}ɿ^}||O}\}s|x:~U}_}:~<~ɺN~G~M~A~t}~F~L~^~¸r}}U~N~`}}@~ʼe}õQ~T}~g||{Q}A~\~\~|7@9\~?~H~y?~L~\~6GE~E~J}E~X}p|V}T}V}J}v;~ϼT~\~=~̾`}¸y|d}Z}Z}=~{9~C~x9~j}I~S~P~V~>~@~?~a}a}o}c}{=Nd~˿U~ɻP~B~Y~Y~X~S~I~Y~C~w7~o}7~<~@~S~`~a~ɾZ~F~O~|ΐ|΍|S}|E}M}||{r|`}i}F~B~L~A~J}p}t~FW~Nx~E~v}K~øk}}^}[}E~}}~}}}}|g|J}t}w}_}`}|{{{||X}t|L}ܠ{{}|{_|{|d}|Z}M~T~||n|q|P}ýz|ȷ{ŷ{{{{{{p|b|Һ{{{{v|}O}D}J~k}V}?~h}j}y}ӟ|ɏ||r}l}_~BGz7zE~|X}Y~r|͢|դ|q}|{¾{}g|ʑ|n}:~>~j|D}P~5H87~^}E};~9~B~}?}S}||׉|m|Կ{߹{Ʌ|ºd}c}e}8~2w+3w0̾H~=}I}S~[~@~zA~zO}l|O}m|j}F~Ļ\}‘{{ò{{~o|{p|Y}C~Z}z|º|9~m}W}}V~e~j~<Y~z}z}u}M~E~U~Ǿ\~}<a~d~=~W}j|zD}c}y:~O}r|{{ސ|\}ƭ{ڲ{ˈ||{{|L~Z~d}h}N}Î|}f~o~g~]~S}k||l|{{q|c}T}L}u|i}M~˹P~z5};~?~S~[~>~Q}{߿{m|{g|S}j}l}R}{{E~Z}{ֻ{i|ؿ{{{~n|{{{{g|Ɛ|޷{d|s|m}E~P~h~B~}֋|{{||M~؈}ѳ||p}_~w~}c~y7y4;~m}i|{{{{{{|||}U\b~M~|p|{t|x||e}]~~C~O~I~]}|T}|~V}|_}|{t||I~r~m~`~U~P~O~L~H~R~P~Z~p~{=¹]~i~{C~F~U~Z~~}H~K~f~ƷX~U~}?~E~P~=]~O~K~T~Ź]~L~I~P~ɿj}|˽g}Z}{{e|N}||M}F~;V~I~X}Ӏ|O}|}E~_}Y}]}p}E~F~M~I~L~W~ƽa~o}˞|||||m}|{{||L~~}}b~ۄ}|}}~}p}ʟ|{|{|_}߄}I~X~U~R~]~L~p}|||||M~р}s}|||||||l}e}}||c}r}ݹ|ݳ|c}|||||ɐ}ڤ||l}|||f}||e}{}|}r}}ü}ͫ|h}Ǎ}}}܅}}a~}f}·}}ޜ}|}}X~~Iԗ}||}}c~~~Cy~l~j~{=z<?m~žm~l~i~~l~–}k~]~ǐ}l}x}}P~΀}ƚ|||w|m}R~҉}إ|||||||t}|a}}{F~{~X}u}͹|ε|||[}~i~e}Z~X~\~ſt~W~_}U}d}|}t}B~U~F~U~Y~}c~X~\~`~Q~^}[}i}r}Z}P}҉||m|}||R~h}I~|{q|Y}s~}ʩ|||}||[}o}҂}|թ|ń}{{{}j}e}e}x}|Ł}{||}S~|٨|j}j}s~Q~y}¼h~~}]~s~M~S~E~N~}T}r|L}Z}|ќ|O~F~D}d|u||Z}{{DŽ|a}\}ľx}j~\~ſ_~¹^}ʾ]}f|||{ں{{{}|||j}N~`}|}|a}|s}}}p}Jz~Hh}g~?|u}T~q|އ}M~BR~|||{|~}~|m~|{{{ѳ{K}D~|H}J}k}S}~9C~żY~Q~µL~B~[}|{|^~u}p~H~M~V~r}|||D~C~^|{ƫ{}|||Y}|ͥ{~w~||x}t||||}}||{|y|yD}eźq}r}V~[~S~A~B~T}=~N~|/n}||u|{e}q}H~8R~47<~ˍ|z}q|h|}|[}P~ȻR~Z~N~F;G5V~@~g}ŹO~^~J~D~X~F~M~a|{i|l|]}u}{|w|{i}4~J}Ի{|`}X~u}||{|r}?K\~u}|N~S~X}u}n}x}|{|~||a}r}}}G~F~M~RVh~Ф|o||u|IGE~EɽY~j~p}f}L~F~a}Y}|r|y}Ɍ|||{||a}o|}|\}Y}]}¹c}X~ȽX~`~R~u}f}V~R~G~w}g}w}}}B~S~|>~W~ǽ]~W~|D~?Oo~|~Q~^~T~<CRQ]~L}[~|>~F~>~K}S}D~G~x*>=_~e~e~ʽY~O~}K~~L~M~_}W}Ü||^}X}b}L}{{{c|ȸG~O~ƻY~k~Gz9?W~N~A~A~^~F~h}|e|o|ͽl}E~Z}I~V}ʼe}T}P}_}D~R}L}z=~s}m||T}m}O~I~`~Y~~276P~D~s}~=~>~p}I~R~{}|=IF~E~>~I~÷R~F~Y~|7~f}y:~|=~S~C~x}J}r}K~`~q}I}3~:~u/~Ư=~ƴA~@~U}k}ą|O}b}T}}F~f}T~y|]~y}t~s|^~c}S}R~`}[}q}˿g}k}|}r}ȋ|{|[}`}G~GS}[}V~{{{{{|ٖ|t|{}|{{{̍|Q}d}G~06]~L~d~b~c~M~Ք|{{{|a}c}a}źn}y9~a}d}V}K~HO}S}ف|c|r|f|{{{{ǚ|m}z;~4=HBL~k}Ê|Z}w|{a}\}ĥ|zz|{w|T}b}|o}P}Ӏ|N}A~715~z4~|5~N}a}i}|E}|է{{֩{{|||g|]}U~E~x0y2F~L~J~I~y=~v}õQ~E~C~A~J}N}S|}{[}k}{{{{{~q|{s||x|q||N}{ɍ|b~^~F~T~f~C~j}t}y}g}N~o~Q~ȾX~]~K~ǻ\~I~E~xG}u}k}O~{;~L}S}g|ė{{{Ĝ{ٌ{}{{|Ϩ|J~·[~N~ȿn}R~Q~L~^~]~\~W~S}l|n|^|o||u|S}]|{{S}ҽP~J~6M~Q}[}h}t}?~q}v||ݓ|y}@~W}[}|п{{|u}V}|yP}~R}z<~P}[}M}ݿ{{{ڳ{̸{ù{{o||J~~9>B~~}f}|N~T~N~I~V}ع{ӹ{|||z}F~Q~ĺ\~O}{{{t{{{{{||t}R~r~}}Z}U}_}Z}W}s|y|||؝|{C~|Щ|Ҡ|T}||w||{{|~}Q~ºy}}}d}p}P~ǻ\~Ab~F~J~j~k~o~>b~h~Z~D~h}p}r}~D~r}x}>~n}A~ȿV~i~ùT~G~S~f~a~d~Y~F~r}P~c}|ŷ|U}M~V}u}h~J~H~z@~H~|\}zA~Z}q}b}}N~}C~F~R~p}}|}¸a~Z~}t}r}ļz}^}}||||í|ζ|}q}v}||h}|Y}ؖ|~||`}Ҡ|{}}Ȇ}h}y|ͱ||ې}ھ||||r}|||||e}||||||||}|}||||||||̫|||Ǹ|ɱ|h}|||}V~|f}|{|{}K~o}}ʂ}}]~e~l~|{}r~g~f~S~H~~~LOREk~Žm~H~?HJAf~`~h~V~W~ýq~_~i~U~}z}|i}ü}d}ƛ|q}}||΍}Q~||¼|ѓ|f}|k}|`}|ӌ}c~P~}ô{d}f}{h|Ʃ{||||Ȧ||t|٣|{}}m}т|b|y|̊|U}X}|}U}o}֊|E~Z}N~L~r}żh}\}y}`}|_}z;~w}P~G~`}X}b}V}H~ޡ||o|¦{{|z|||ܧ|||Ė}|χ}ړ}p}|||}s}k}|{|ߑ|}}}o~}D׈}`}U}d~`~{L~`~a~m~x}ϸ||Z}\}o||u|j|Ԃ|m}T~9~ʼ^}k~}}k}S}Z}U~}^~~}_}f~}}~g~|z|{{t|l|{{|C~s}~|||{m}}}Q~}j}\~v~}y~Z~م}H~g~q}s}f~|~R~{٣{}§|x}}}_}{{z~^~~|{{{{|a}X}E~\~D~J}j}H~U}U~a}||f|M~|}j|Y}`~K~h}h}|{|K~Ѐ|xN}r}{C~a}||||{¾|}c}}U~||t}z}½}Ջ}Ի|ǿ{Ɯ{c|ؔ||^}P~T~=~;~Y~N~>~Z~\~F~x|||||t}D~U}G~GY~N~B~`}Q}Z}b}Q}L~|;D}>~^}c}e~}6`~96[}v}V~_}U}Y~I~P~u|d|||h|v|ԑ|b}W}w|_~x|n|U}{~l~m}|¼|ޜ||P}žo~Ӌ}{||L~J~R~f|p~\~|ǽ{|K}ˉ|{V}˘|O~~B~L~L~P~m~I~^}||ք}o}W~S~X~]~{@~f}ξq}Y}S}żj}U~}H~~}{c|}|Ӝ|þ{}}}|`}}×|h}x}Q~T~~}H~D~P~È|{e}}B~O}X}S}q}ڀ}}}b}j}v}S~P~i~f~F}}O~žb~l~v~Lw}h}z|E~Aj~CƾU~P~I~̾`}i|O}f}q}X~:3~3^~N~K~W}||ӌ|||ӥ|z}z}i}]}n}s|{{|пJ~X~W~K~I~ȻT~ƸN~m}N}M}A~A~f}d|y|]}P~x}{}c}g}@~u3~z:~g}m}B~j}˺c}ͺW}T~@~4D?Y~N~A~>~C~I~I~J~]~Y~Y~Z~M~A~}G~Q~JJ7ʾQ~I~H~d}x7~ȷL~Q~_}b~=~Q}5~6~̻W}B}P}d|v4~?~N~R~X~R~B~i}|>~¹g}ȽR~~49Y~P~M~N~N\~}~K}T}||Żq}^}{J}I~ǻ_~|C~d}}}z}_}n}I~m}}}}{Y}{{{{{||||{{Ŀ{{׏|a}t2~R~V~z1Y~`~B~R~C~A~m|Ų{{{q|W}Y}y:~A~o}@~z}~>~}>~X~Z~v}{{{{k||e}v}e}k}ļh~µP~T~P~X~q}źe}ۀ}l~|}F~z|Q~W}ƹ{{{q||@~p}|{}|`};~:~z/>~:~öV}N}~E}||N}f}|~u|{{{{|ޝ|O}g}B~N~?~g}˽V~z}ƹU~H~>~ȻT~Q~̻P~_~g}o|M}b}{{q|X}π|į{{{{{{{u|t||s|{p}]~=~h}C~u}X~z}~X}Q~A~Z~m~}C~}{}M~n~øg}]}M~n~W~K~B~¹_}L}U|Q}y|R}Ǎ|{Ц{|`}i}s}}Ƅ}s}D~_~|9]~F~w}p}\}K}]|w{;{{v|H}_}F}{e}]~I~6w1e}m}C~E~]~Fp|y|U}C~W~d}\}i}ŀ|j|_}{{}|N}u||Źg}\}w8~|{{{~}{|}q|~t||b}z}{=K\~~M~d~݋}f~GBR}J}ޑ|p|{ſ|~Q}]|{{{{{ĺ{{{{{{{b}P}G}b}|ǐ|U}|}ot}c}a}R}j||}|c}{ã|}n}|~|{|Ф|y}F~zB~}B~Y}{z|Ç|y}P~U~V~X~U~u~m~<n~\~{+~F}s}R~J~v}@~~}zA~C~{}y}G~@~r}|D~W~I~ɾk}A~B~ʾd}w|{{ȵ{|k}{|L~^~}@~A~i}E~ƿj}Q~c~M~k}e}ׁ}}V~Q~o}d}|k}I~V~i~Ԁ}˜|̈}و}{W}d}߻|{{||||{{|Z}ɽj}{@~C~J~o}e}|||u}||||||||}}o}~|}\}b}}ݵ||||||Z}|ȵ||}||||||||||||t|||޵|}{||½}k}k}|p}ޚ}}|¾}}}l}}|||}f}|e~üaC}?\~\~e~|I~}}p}Z~l~o~d~R~}C~O~d~c~u~s~T~̐}v}v}g~Eƾu}݀}Ԏ}||z}w}‚}|}}h}d}ə||h}|l}i}S~X~j}Ⱦ|{|e}\~{}{|u|א}}k}Q~w:~W~l}x}|{k|{ۿ{{{{]|{b|\|{T|p|{{|||X}s}K~R~N~y?~|v}||||ã|Ŝ||Ҿ{X~ٛ|||||||y}Ģ|i}t}U~܎}|Ђ}|}||x4H~ڧ|x}g}|^}}^}Q}m}G~[}q|`~т}j}L~|dz|{n|q|q}d}DD~D~G}S}΄}ĝ|Ƞ{j||Љ||m}g}l}Fb}|ɥ|t}||b}S~E~~:~]}r|i}P}}v}П||}}|e}d}Ѐ}~ͅ}a~S~R~g~t}|E~P~Y~΀}[~}{}u}~}c}LJ}\~J~V~{}t|^~v~|^}r~d}||>~T}Q}k|Ђ|ͿO~m}D~õN~U}~|c}W}[}I~K}l|?f~?~~?~H_}s|~o|{{{U}w|`}ϣ|m|T~|{}|V~Z~l}||i}z}o|[~K~[}||ۖ|o}e}W}b}@~ŴV~g~J~I~Y~b~~>~y}T}W}z|W}r}Q~L~\~N~}59S~Z}ȼT~L~ǔ||Žt}Ho~V~Ƚa}\~|}T~i~Y~I~K~>~=~:~]}|˽M~N~8~|u|{ă||P}|Ĺb}P~c~{w||}~ܬ|b}S}|g}z~m~d}K~R~O~j}Os}Q}j}?m}|}i}q}R~֧|w}^~d~Q~Q~I~=~M~Ͻo}T~N~q}}yC~L~J~v}p}wH}S}j}y}e}j}}|}|P}p}~S}֜||~^}Œ||ޮ|~||{||¼r}O~d}S}Zg~ʙ{ۂ}}K}z?~}t}P~Že~I~f~{~T~n~E~Ļe~\~W~a~\~p~j~a~a}x|r|M}~J}B~N~9;<{4@~ā|x|y|}|ú||L}{|d}r}?~E~u}D~I~ĸY~ȻW~R~F~g}t}D~d}k|x@}|m|\|A~ɺ\}>~U~M~R~W~~0<~z<~B~}d}s}q}r}n}I~u}v}H~͸]}d}<~ŵN~±H~R~]~{1?Gy2D~l}n}öP~c~V~ɽT~N~z<~K~D~D~A~C~K~B~B~~>~G~n}y}I]He~E~||o|E~P~=~4~6~;~`}Ű?~9~ǴO}{1~>~P~K}Ļu|\|}|c}^}O~ƻV~S~|@~I~W~\~H~}|ޑ|a}˿g}|p|I}ɼb}B~_~Y}p}p}w}q}f~έ|}}]}ۭ|^}]~||{||{|||om|{{j|{|~|?~?~Y~S~C~C~>~m~h~=~|{{{Z}C~`}s}q}o};~~<~r}h}}?~O~6[~X}b}|}}?~s}{}J~w}q}J~E~?~g}òF~H~C~zA~|{{|~|}}{{{|l}\}j}[}[}f}:~},K~I~D~ɺZ}~@}R}[}W}Z}|||||d}N}y|~|~8~b}=~1~X}m}j}v}=~Z~4O~:~|:~̼P~A~v7~G}t|]|{u~U}}|u|b}<}p|{{{zz{í{ܸ{f|]}i}Źh}L~]}C~z?~z>~R}G~u}n}R~E~~}ƻX~|ю||u}`}=J:L}r|b||G}~}v}i}|f}\}y}L~|p|||]}R~¶RM]~Z}|{{}{{z|{{Ǵ{έ{Q}`}s}J}ȶL~J~F~`~]}b}|]}I~w2f|{]}f}~B~D~w}z=~c|||`}\}{|]}{J}Ʒb}Z}S}_}|r|ɼh}W}h|{|{{{{v||^}h~CVp~h~j~~9Ah}H~B~H}{{{{{{۹{{{{{{{ż{{{͸{a}`~>~ׄ|]}{{{q}}E~Ǿc~R~H~}k}ż\~J~z||Ơ|{|{{{{{d||Y}e}Y}V}j}Q~O~}Y~ǽa~r~s~p~r~Ax~i~ƽ\~j}v}D~D~r}Y}m}R~L~m}C~@~B~D~W~M~{?~u}s}s|h|{w|\}[}U}Y}}?~M~G~Ⱦ`~J~L~e}_}}}}a}Ɨ|||ܢ|p}~[}||}t}r}U~|S~S~}Q~y~d~||{{||||r}H~A~z}a}|΢|ŗ|||{||{||„}ԑ}~}|}w}^}||f}ÿ|Њ}ޟ}}|ȧ|G~E~f}^~}||m}q}}||||¿||||||ǧ|b}}|||Ч||d}p}ݐ}O~}}Ԉ}R~ُ}ż||r}_~|Q~z}}s~k~}}Y~W~}Q~~NZIs~b~S~K~F~]~K~q}s}[~a~øa~R~s}||ʟ|x}m}{}}g}U~U~f~ō}h}ͦ|i}y|Z}x~v}G~ɿv}\}w}|K~||~L~O~`}|]}}}|m|d}U}Ԟ|Ů||{||ŭ|w|||{{|{{{{{{¾{ǹ{{{{۳{ة{f||||{{{|m}{}q}~~||¿|\~}r}g~ϖ}r}{{{~~|Y}}~h}}f}m}V~Z~ȡ|k~ʿk}z||ǿv}||`~k~ר|]}Y~s~»b~M~x}^~g}|{{{{k}P~~>N~Lz>~\}||||[}b}|{|||||q~j~|||ý|x}V~Z}}_}v||||}|||l}||J~O~b~t~}}yA~¿z}d}˗|m}a}`~I~c~j}}|t~Ü|̹{{|d}T~|}^}d|n|T}k~_~õX}q|j|¶Z}Q}L}f}a}M~b}k|l|Y}K}|ƌ|i|u~_~ŽZ~n}|}|{L||{{¼{W}l}l}R~M~w|{]}d~i}|y}i}||||R}y~x}X~}q|||P}x}P~K~K~I~H~a}[}z}e~B~j}||||T~e~S~6S~|8d~6h~|O}||X~]~Dc~:I~ƽi}@Z~Y~d}ǹZ}ŷL~3Z~R~O}S~X~i}|{S~}t||t}Q~Y~|Ƽ_~}{|||}u|˞|`}|v|}ƀ}n}~|b~x}\~]~b~x~N~}}Q~{L~~}ǿ~}o}}s}|}_~a~̀}x}h}=~=~@~<~A~u|}g|u||}V}|_}d}k}Z}U}~|{||||{|d}h}Ǖ|c}a}Z~I~g~Df}}^~h}~}|J~a}Ԃ}p~U~I~DQ~}P~ƾg~ހ}`}|96l}E~|,B~7~A~8~h}c}|Z~x}l}H@6[~J~J~_~b~V~I~U~}6@RQĶ@NNE846`~R~D~C~h}|M}y=~o}˼W}?~Q~ʺI~x6~öX}K}O}Y}]}W}S}r|̎|T}Ļ{|s}B~n}ʽV~}8\~p}b}V}B~y;~U}|||c}ùc}zM}||ʏ|o}B~p}y<~u}]}P}k}t}C~b}[}d}}}}}d}Q~L~V}S}f}L}S}B~F~Q}v.~8~T}_}3~8~;~öW}F~;~@~ȽR~G~A~Q~ƻY~{@~|}`}Q}ɽe}X~|{Y}܄}_}Q~^m~I~q}|~|w}`}j}}s|R~]}}[}؀}o}ڶ|T}|v}{|ٝ||}~|{{{{|g|ۓ|z<~J~ǹK~H~I~ͿS~K~V~K~b}s|{{ɣ{̓|¸a}9~{8~^}p}};~a}Ƽa}_}q}B~|J}d}7~e}e}_}Z}c}}}D~A~9~9~G~J~B~p}x|{{Z}e}f}X}{s|g|b}_};~U}i|˹W}a}:~s,ƱD~Q~b}¯D~@~<~K~;~b}7~9~V}\}l|_}E~=~K}z2~a|J}>~E~C~]~V~ɶG~S}l}F~J~>~?~b}x|Д{\}έ{{P}̿]}f}d}[|k|q|u|{zz{Ž{{{|zC~~}L~V~E~d}}_}||_}I~Y~|||m}^}O~G~}c}|q}V~j~ſy}|ì|b}Ō|W}k}J}[}{;~:~A~Ƽ^~DFG~D~M}{{{{{{{e|ͽs|K}d}P}{|=~W~óG~c}~9~Q}_}b}ʻP~ɺW}{Ҷ{c}g}M~<~B~s}Z}J}s|R}w|{|l}p}[}܈|ӷ{n|u|n|yJ}[}w|{{{{{||R~@B~d~ɾ]~|>9w0~Ⱥ\}T~I~~<~ɽc}ȼg}ݖ|a}^}|M~úl}m}[}DŽ|||||{{{~l|}}g~CX~x}{C}|Z}~|U}h}y}v}Q~d~c~ſo}|{||{||{|{{{{v|Ҍ|{u||z}Ԇ}|y}|ǿz}=9h~`~g~^~t}e}x>~@~N~l}`}q}A~A~i}j}N~B~{>~b~a~G~i}؉|}|R~U~Ȼe}P~A~T}~M}}m}~B~U~F~[}||{{{{m}|||o}ф}Ř|}k}ӝ|I~n}|p~|||^~r}|{|}|}||ܘ|Z}h}Ǣ||||||||{}Ϲ|n}p}\~o}}}~K~O~j}a~̉}|}}c~v}}[~Ö}|||۹|}}|{}z}h~}||||³||t}}}M~R~j}|΄}e}}b}s}Ą}~}͋}}^}s}k}_~Y~_~W~~}ڎ}o}г|||~_}p}ޗ}Z~n~z~h~j~QȾXPY[VøSl~üp}|z}{}ʶ||̶|||a}k}c}}r}|||h}l}|Z}Q~}T~}l}Н|g}a}{{}||]~e~¹b~۔|CL~{}n~|y|}}~}|l}i}||xy||{{|{{|ss|{{{{{{{{{{{{{{{||{{|u}{|}}߫|Ŀ|||ϱ|}}b}k}{||yy||||||^~}||~}W}T~٬|h}̿i}ۀ}T~X~}R}b~CZe~ަ}Y}r}||{{|y}V~O~r~o~j}Y}|\}M~؅}P~~E~a}ق}݂}Ľq}d}}}|||||ɀ}]~X~|}e}_}ڍ|l}||z|||u}T}R~ی|p~@X}ž|`}Ļk}j}Ȏ|||Z~k~ڨ|̂}e~u}s|m}х}s}^~R~B~z}{{y}I~l}[}E~T~T}j}ˁ|l}d}>~V}M}M~s}ɀ}`}R\~v|}}||X}b~X~S}{A~l}X}b~K}{{{ۚ|v}ѳ|O~}}~|||{}a~`}|W}ǻd}{r||{x|zG}\~a}ė|b}M}˸i}N~G~z}S}}}s}͐||w}J~e}l}|||U~l~z~b~D~9?Ku}|||ݤ|_~_~Lc~˾V~E~F~^~Z~K}P}`}P~K~8>~D~H~D~w|{}}|}{ûj}P}}R~q}|_}v~}[}ҙ|t}X}|}Mcľ|x|B|F~}m}_}ܵ|{|||o}b~}l}||||Źh}Y}^~Y~K~d~O~K~C~D~S}q|{þ{dz{{{o|]}`}l}¡||ʪ|||{{}|}؃}}l}||գ|]~Ż^~K{}M]}i}a}|I~P~Z}ʁ}l}}j~z<q~|:|}{ɵ{k}B~D~=~D~ȷI~?~@~B~\~F~B~ͿOJi}~:KIF:a~~:~@~<~e~k~g~e~O~E~F~E~K~<JS\C:8-I~Ⱦc}ò{s|ĸR~M~N~S~z.M~K~<~E~J~~C~S}u|d||T}Ƚg}A~E~Y~E~ƵX}F}˺Z}w3~8~>~E~y;~r}V}|^}]}Z}N}[}?~k}k}i}]}R}żf}=~o}\}X}a}@~L~N~ýr}p}¼}R~[}=~:~ǻ]}|s}L~B~B~M~N~_}b}6~9~J}Z}R}}<~^~W~y;~[~J~K~~@~w}}|܅|{V}q}݇}`~R~a}q~i~s|֍}b~||V~z|||ͷ|þu}q}v}l}t}~J~e~t~i~`~~}{{{{{||O~}z}\}i}[}Ⱦg}I~R}N}q|q|{Ͽ{{{o|i}B~|{|\}H~A~D~V~E~};}A~X}[}s2~ѺX}W}O~z:~X}b}Q~ξJ~F~K~ʹG~нK~K~9~q}|~||h|}Z}}^}U}{|b}=~j}K}{^|?}Z}A~?~y.˹K~N~:~`}A~A~U}c}i}|K}n}>~P}̺W}R}7~Q}|R}W}E~w0L~>~ҿE~E~>~D~7~{5~ĴS}O}T}x?}{n||ŶV}j}m}U}^|u|f}X}}|A}X|{̪{{{{`}D~M~H}~i|v|o|{|{{w||q}b}v}f~M~L~}}w}d}q}v}_~[~~W}e}Z}H~T}z;}a|€|úe}B~{5żr|x}A~O~A~]~h~z|zz{{||N}óY}ˍ{Ϳn|I}U|K}D~2~{0~X}n|y|J}{:~F~n{z^|;~ŵN~μ`}W}@~~>~p}h}f}P}{~L}N}\}T}T}¹w|}N}R}{}r|ȼ{{{{z{ƽ{s||ο[~E}4Z~>~}<~ƵJ~_}S}E~H~{<~k}~@~~||c}o}g}ļt}~}y}T}r|{{{{{Ÿ{|y}E~b}N~ƺa}T}|ý|u|p|{NJ|ɼh}z=~e~N~h}|||~|||{{{{{{{f|̆|Y}Y}{{{{U~a~S~T~I~g~5ŽS~G~\}s}f}Ⱦ|j}s}A~J~L~P~ƺY~]~R~E~I~|8~{`}G~f~M~I~D~F~V}\}u}c}g}Ǐ||r|||Ӿ|{{{||o}}žp}[}|||m}l}ϭ||{|||||ѭ||{||||||||||||||~}o}}~T}|_~g~ը|}}|۱|j}P~d~}k}|ҵ|~||Ί}Ѓ}}è|o}|}}O~Y~T~{~x~|||̼|Z~Z~r~żh~v~[~}S~}s}ۇ}Ս}}ʓ}}{}}ƾs}]~}[}r}ӟ|Ą}z}O~a}|e}q}}r~}N~ϑ}ŋ}]~h~h~n~p~z:r~H~ؙ|||ȭ|||||Ŷ|ž|||j}|j}||_}Ѩ|`}T}Q}ч}i}a}״|}V~f}j}y}}O~j}ă}L}T}|V}k|Ŀ{}\}t}Rf~m}~z||||||{|{|{||tt|{{tu||{{op|rt|{qr|{|{|}~|||Я|}|{rr|wx|{|{c}۽|{{|k}ڮ|¼l~k}|}̜}Ϊ|||ǿ||^}^~ۀ~||Ҥ|O~O~а|ّ}V~|x}k}K~L~}|W}T~Dʀ}տ{{||{Q~}]}v}p~ց}܃}c}Q~jXr~g}|ӽ||q}|C~f}U~ƿj~Ա||||||}}h}a}J~Q~f}u}x}l}j|b~ƾt}Җ|o}L~K}@C~\}T}|||v}|{}g~l~v}a~~|ُ|S~o}ɨ||g}h}Ћ}O~d~K~¾{ٮ{o|_}˿_}H~\~y9~I}J}|R}G}m|u}Z~8\~Y}|||ײ|s|s}V}7Ij|{{|||˖||{{œ||v|X~G~A~a}}M}|u}V}|}[}m}|o|@»a~s}ّ|٢|||{F~a}M}G}u|Ӏ|j}a}]}Z}ӟ||||k}Q~y~i~L}b~b~<@~^}}||{||F}Q}G~^~Z~E~F~D~Y}{=~ǺW~5U~Y~N~=~=~U}{j}V~ºe}`|R~H~V}n~DS~^q}u|{Q}`~Ƚb~U~|ж|Y~D~]~Ο|}l}|||{{{|||j}V~v~{F~~r||i}u~u}j|V}|M}P}p}z<~C~F~]}h|{{{ij{r||w|Ɍ||x|]}ɾ|}y}а||a}Ȟ|]~v~j}ʗ|}܎|F~|n}H}d}DW[~i~BT~}Ź`~`~`~O~V~i~PO~W}L}H~L~P~ǵJ~<~;~A~ĴG~I~6N~\~7óH~j}˽c}f~R~R}C~F~^~h~D~ρ|n|W}ƵS}F}M}ȹS}?}j|||\}N~L~F~S~R~ŷN~J~D~x7~e}Y~S~W~X~K~T~U~a~R~L~a}T}[}E~s}?~ɺW~S~D~G~j~>~E~C~H~ŸP~Z}{|w|\}[}y|̌|X}f}?~<~a}W}v}t}@~E~>~Z~6O~S~P~˿U~L~H~g}b}}}|F~R~8g~[~Y~=~P}S}u}֟|||U}Y}A~o}|9~C~~=~]}Z}Z}c}j}e}̈́}~K~|{v|Î|~B~CZ~||~ſf~s|y|M~¼_~½q}||t||ޟ}|ƨ|W}^}Ϛ|~|Y}o||e|V}ȫ{{{{{{|ˋ|S}n||R}ø[}L}ºj}U}{}||Ȥ{Ի{®{{{Z}G~P~~@~r}_~\~C~`~`~09]~|g}w7~n}f}Q~}»h}z}I~?~9~;~4~A~^}b}Z}?~i}a}U}|Q~U}{{{l|]}{{{\|=~4~ųE~D~M~x6~S}|7~A~5~|3~A}Y|x|u|d}L~v4~K}F}ôV}_}I}{{_}J~I~e~T~ųJ~C~>~\}ƷX}a}P}~C}}C}p|t|]}~B}I}n|||{{{H}b}b}i}}8~Z}\}{{_|{ֵ{|_}~Q}[}|{{{{{{{N}}e~I~k}zD~}B~q}ʽk}]~J~C}R}R}Ŀy|z>~o}L~B~`|j|k|w=~j~Y~S~G~j|l}`~}{{|||l|g}`|o{R}=~ݽN}x|X}:~L}[}7~9~<~s1~b}ź{љ{\|T}g}|Ķ[}K~]}k}|J}Ÿc}Q}S}b}~Y|ͥ{R}Q}{{{þ{{{{{{{ñ{~n||w}ȹU~Y~b~C>~I}r}D~H~>~p}g}f}f}w@~y>~c}^}{}_}D}{{{{{{{d}D~W}O}[}v}n}V}w|Ր|{O}|{w|`}w}M~~@~p}{||z|||||{{{{{{e||{{ۼ{{`}n}]~m~W~E~F~P~O~Q~ĶS~;~K~u}`}X}i}Ǿi}\}w<~=9~J~U~͹S~m}zX|zI}q}ш||G~µY~I~z}u}ĺn}`}̓|{{{b}i}||t|ӣ||ܟ|ߚ||||{`}s}s}||{{{|||{||{|߸|ֵ|||ϫ|}||||||||֊}N~v}y}}}j}|n}l}|||||}|||̴|||_}}ˇ}Z~Z~x~i~x~g~̆}||_~x~|B[~}~}H~}]~R~~}}͂}}ݳ|T~}Y}r}ɶ|f}{}S~}}}t}}ſv~Ғ}d}{}ә}Ό}}b~W~ɾp}}||ݾ|g}δ||||||ټ|p}e}ó|Ƽ|¿||i}f}|߾|||||݅}}K~]~}g}g}|^~P~o}Y~q}g~j~c~_~}s}]~üj~H~º|}f~}|Q~||d}V}p}[~d}{{}||||||||~}|xy||pp|{||{vw|||ƒ}U~}\}|{{t|~||y|}|||j}_}|}d}z}\}{||v}Y}T}M~׊}||y|{|ؑ|||ܸ|Ǿz}Ӈ}׊}ƣ||r}}f}S~Fu~d}I~T}׫{{{{|}Ȓ|r}W~j~a~T~n~J]Rc~X}m}n}Ȩ|j}ˁ}~D͇}ܙ}h}|~|||h}`~ߟ|}[}`~X~G~v}F~C~:y}m}d}[}b~I~J~@~~E}T}Ǎ||Ī{{{m}M~t}dž}w}U}y~}p}u}|ó|~}n}}d}a}i}}ڃ||}F}I}V}I~w2~|Ǣ{X}?~A~j}р|\|`~JB[}||`}m}u|n}|m~L}R~h~|}΄|w|R}V}||P~w~{|{{}r}||ǐ|p}q||x};X}|[}G`~p}{F}z}{A~b}O}F}Q}k}G~k}q}^}|}|||ʛ|J~~}r}bY~[~J~H~~}o~KQ}Н|f}|E~Z~S}|X}@~<^~K}Ѓ|E~=~C~´J~V~;~O~?~||žk}d}A~J~?~D~v||f}^~}O}ź]~F|}|~\}ǿ|`}݂}Ж||w|ʾO~L~\}|m|{ҋ|R}|||X}|چ|t|||||p|{A~V~?~_}c}i~ʵS~E~B~{G}h}ش{{{{u|Z}[}z}}}V~b~Mf}H~}q}]}m}_}D~b}c}~Q}ΰ{X}u|||~|||]~|\~`}{Q}]}}~@~k}s}D~Q~=~°R}w4~L~<~x2~@~ȴC~F~k}x}n}m}c}}6~=~Y}O}l|O}`}?~Q}{F}F~C~|6~̹Q}3~z?}D}L}4~<~;~H}u|P}P}Žy|i|E~a~d~]~y}Q~i~J~H~D~j}_~V~L}J}d}8~}6~=~T~O~f}||z|H~Q}k|o|w||[}p}C~B~;~S}x|ü|Y}~;~O}Œ|]}^}Q}ә||_}E~X}֍|@~C~>~r}H~}>~a}]}y}|}_}h}{}y<~h~^~W}z||||{|{||v||l}J~U~r}v}_}\~_}q}T~Ľ}}}W}}W}V}u}_}؈}X~g~r}m~y~Q~W}ĺv|{|X}|^}k}[}}Z~e}\}=~~;~v}{9~{{{{{|}N~V~[~\~2M~x|F}Y}|͸{ʪ{Κ{o||V}f}J~K~E~T~O~\~T~U~k~o~|6E~{Ĥ{vA};~9~}/~5~F~u1~A~P~y6~d}{7~>~v4~|ƴX}ѿR~_}[}h}|}F~Ц{O~u}{{{s|v|t|W}E}R}H~,~<}m|Y}S~A~Z}\}e}|R|ܐ{Q}f}P}O}T}>~öt|o|y|i|_}~9~R~J~C~|:~G~c}D}̿i|E~|H}S}K}}V|P}_||D}Ɩ{u{|n}R}yD}E~ża}G~{{f~F~|y}z|z{{|||{{{{{{{{Ñ|w||g}G~Q~:h~Hx5?}Q}e}O}{C}Z}}{I}R}j||=~ĶL~ȻT~?~@~P}ԋ|{{{{{{O}S}ɫ{º{]}θI~Y}Y}h}b}|Z|N}H~A~>~yB}{l|b}y8~_}M}Ŷ[}S}ŵY}h}<~i}^}r}̽^}q||k||Z}{{{{{{{{{||||;e}m}|=~b~y6l}g}u8~j}Q~{=~~>~>~v=~Y}Źh}q}s}E~w}u}K~?~t}p}d}p}`}N}~}V~ȻR~H~f}ʅ|y}U@A~u}\}}p}X}a}G~N~A~a}_}\}E~h}|{{{{{{{{{{{{ȷ{{{{{|H~U~R~}Z}R~R~o}t}|C~r}̾j}m}|i|W|U|N}F}x<~E~E~ּ{s|zL}i|O}|u}g~}~F~U~`~n||||g}j}ſ~}Dz|l}`}d}Ø|R~}}}o}y}||»||||||||||||||||||||||||_~R~}d}d}ް||x}ֻ|h}|l}ċ}}||}}x}r}ݬ||i}}a}}̓}Z~½}}|l}r}l}}t}}}׋}U~g~|~EQ~ľ}p}ӎ}S~e~ɂ}p}}M~Y~g~Nú_~|}R~`~r}߄}U~U~o~g~}}X~e}n}Ҹ||Ȯ|Þ||­|v}||||ǹ|н|͹|||ĭ|x}ρ}l}||v}a}}n}m}d}|ÿ}}K~Y~f}_~ȗ}}\}}_~Ռ}}܀}O}]}w}{}}{{}|v}||}ͨ|}|}l|S}|||{n}||{{|||ִ|Ƙ|r~L~}k}||~~|zz||||g}||v~ޟ}{{_}z=~h}D~J~y@~}D~M~L~r}y:~N~L~p}{}|ų||Z}y@~xW||U}||||ɂ}U}W~|||q}{}}]~b~]~}}l}۠|v|j|ƻh}|G~B~[~H~ǽ\~R~9ȻR~9H6|3g~Z~>~d}R}u|̝|||q}ڊ}||||}{}X~[}ſ|T~ɟ|}Q~i~]~y}i}]~X~A~u}N~}}w}x}x9~_}|x}_}ݖ||{{^}]}ż|؝|w}|R}{~u~}|||Yp}|}}~}{@~~}P}l}N}?~7~q|{|A~C~l}_}x4~j}|n|}ƿ|V|]~}}|c}c}j~a~i}͇}}~C~s}{l|}J~]~b}ȋ|͈|ʿh}J}T~Pߴ||{|b}{Z}}||}}؅}}}ɿd}~]|k|Á|s~V~^}S}k}_}W}L~>~}>s~K~d}u}ӕ|K~x}J~|ܓ}_}u~|8|5G~o}J~`~X}Ɠ|b~}M}j~M~Z~Y~^~I~M}|o}I~<~B~:~B~d}w}P}a||}M~|G~[~:~||{|]}G~A~N~r}i}|{h|y~c|ɂ|>~@µE6оJ~D~R}֔||W}||{|||X}|:~}}F~w}]}d~?~|x|]}p~h~X~C~h}~:~||o|l|Ӏ|Q}Ƕ`}|Ⱥc}D~N}{c}_}}z}W}o}T}]}m|o|X}|T}|~\|k||||IJ|}Y~|½||F~P~G~}B~²Z}|S}:~J~H~[}f}P}X}e}R~>~´J~<~^}H}8~w0|0W~ñF~W}s|Ľx|G~~2Q~2G9I~R~ѺF~0~u=}v|M}V}L}l|j|;~>~c}̽]}P}`}g}c}D~E~>~N~L~Z}A~?~~A~K~G}F}s|c|L}C~S~R~F~;~ʶE~;~9~=~D~>~J~B~@~B~U~G~O}z|U}_}j};~<~E~N~P~{P}^}q}S}@~D~^~E~@~w}@~\}]}|X}b}||x}P~ȺW~E~||||r}s}V~W~8a~b}|}Ƞ|e}|}||m}T~J~i}f}~L}|A~N~ł}g}b}|ƿ||||||{|_}Y}o}}]}Ʌ|l}>~_}_}i}i}h|{{{zؒ|}F~@~J~=~ɽL~Q~~5~`};~~9~E~O}|}N~D~K~Ľ^~T~Z~}<{;l~p~q~`~[~`~^~F}{O}b}9~ɻO~M~7~D~L~;~;~`}x8~V}G~zE}M}9~c}I~Փ|”|P}E~O~M}|g}{z{|U}y|~||}S~A~M}C}F}U~e}a}J~Q}Y}n}V}N}e}U}O}~|t|y|J}Ļp|u2~;~;~l}G~=~M~J~G~A~A~x9~݇||p}|9~||{{{|}R}n}C~?~;~L}u|c|{{|{{~||{|{{{{{{{{{{{{{ѷ{}|{x|P~JB7ͼP~G~ŶN~Ȼ^}f}c}q}G~L~C~p}|ùx|N}};~i}A~f}P}r|q|{~~|{v|}||¸|u|}|C~ζF~U}l}F~ȹh|]|n|h}=~^~݌|̩{ͫ{||J}\}<~µ]}}|f}h}ƴJ~R}V}<~{֮{T}yI}}|t||{{{n|s|{{|||}`~e~Z~N~g~}c~Z~[~V~E~B~N~G~I~U}c}h}Z}w|¸f}o}E~A~C~ûo}|}s}|>~H~}}X~Z~8M~֊|{{x}W~ȺW~V~W~:f~x}C~d~O~I~H~y:~p}J~I~M~M~o}{{{{{{{{{{{{|{k|z||i}b}x}^}n}e}g}a}|f}H~|ˍ|r|f}Z~`~T}Հ{[|IJ[}b}s|||̽c}r}D~l~q~L~w}Y}ۘ|||p}i}|D~}҉}t}|}բ||}||j}|I~{}د||||||||||||||||m}k}|m}}|}ʁ}}||Q~Ѐ}v}f}l}Z~g~Z}x}a}}p}||b}ٵ|ʹ|{¿}g}þ}}M~w}k}Դ|m}Ć}R~ݿ|¼|ʩ|o}}}{G~a~_~}h}Я||t}~}}t~}m~ˆ}}i~W~||~}v}g}a}b}}}}T~|}}R~h}Q~e}l}g~b~||}||||||||ƿ|ķ|~}{}r}d}k}p}N~L~ɿx}º|ݴ|\}f}a}`~Ƅ}|}x}‰}k~}}x}}T~~=^~}}{|{|}||||||t}|ц}}f}||h}|f}‚}f}|S~O~M~l}|N~ڇ}||zE~ˆ}|~y}e}Ս}N~d}p}<5N~P~_~F~R~ĸj}}]}a}H~¹d}lj|Ċ|Że}Q~u}f}e}ƽ|ۭ||g}s}||e}p~_~j}|ƫ|`~]~V~X~|L~P~}|j}u}K~k}A~F~[~3r}z4J~T~W~;@b~L~G}P~y9~|||{||ɪ|Ԇ}c~Z}|ܻ|ȏ}Q~y}|Q~ļr}d}M~Q}s~h}٠{܃|m}L~G~?~Q}G}X}|K}P}||}s|t|ȿ|||k|W}j}||ܖ|g~Oۉ}|{||~}}|۷|ܭ|J~ǿ_~Y~M~}{N}Ձ||^~W~E~7~Z}O~x2~M}M}p|v|x|v||ƿg}DM~c}}||^~G~y}||\~}}_}z}ûu}x;V}L~k}||{|]}b}||{{s||||ſ|č|x|_}{f~X~w|Ľ||QR~n~|}Ǿc~L~@~ɾ`~z}|^~c}|K~}x}}8~@~T~G~T}Z~|ڲ|½|a~ơ|||||O~U~I~}9~C~W}k}S}|]~P~S}i|Z}q}C~=~Q}@~R~<~_}n|n||I}<~N~R~O}W}q||b|{x|V~Ĺ`}D~;~h}C~O~[~T~L~C~T~{0[~a~ѾH~=~|h}пJ~O~ǹM~:~=~|K}W~3ȺR~S}[}u}R~̻W~˹R~̹Q~N~A~j}o}j}o}G~m}O~r}|d|W}I~|n}|X}g}y}t}l|e|x|m|m|ԁ|~||||||{||j}z}o}V~;~w7~O~M~B~||i||@~:~@~8~Y~?~?~I~Z}ɷJ~;2y9~>~B~=Au.\}O}H}~}o}ǼX~t~7?R~t)M~s&ԼB~5~~0~H~D~κB~9~k}T}̿[}W}ց|t|¼|{q}G~}z}R~D~v}\~S}F}q/~7~ɶS}~:~B~K~?~H~E~1x-R~;~k}µN~?~=~;~S~{/T~Y~O~ʿQ~K~R~[~Z~÷O~C~n}^}W}f}@~^}h}^}Z}U}Ǿk}w}C~D~F~T~F~X}@~w}m}|P~Y~ùP~=~k}|C~T~E~m}|Ğ|q}O~~P~||||||||ؐ|U}||s}p}l}w}x}p}`}`}Ԇ}|K}O~}~a}Ⱦg}f}ȿl}v}A~i}R}[}q}|H}l|{{u{{|S}z}N~D~Q}||d}9~=~=~;~>~ŷI~S~_~H~@~B~m}E~B~B~R}Y~Z~A~G~C~g}øZ}ĸ[}8~||ŰR}ñH~S~}8~Z}X~;~9~9~R}I}ĺx||H~{}d}Y}_}zJ}U}n}G~K~|}^}{{{~{{{{Y}o}I~H~M}}@}Q}F}N~=~a}w}y8~W}S}l|H}P}I}}^|{zF}]}<~@~y6~E~|R}A~T~H~ú\~a~W~p}g}]}U}n||{}U}j|z{{{p|Ʌ|{6~\}]}ǸR}t|V|{{{{{{{{{{{{{{{{{{{zz{޵{ѷ{|n}[~<|q|R}R~b}CIDH<O~z;~_}F~p}}?~?~>~^}|v|x||ݕ|d|{Β|Á|I}O}C~H}zɵzԪ{I}Ջ|u|S}Q}G~>~L}|ʛ{{K}l}D~`}L}_}ȼT~óL~b}b}f||у|W}|R}>~Y}O}¹|{|{{|||e}F~~@~N~u}|T}A~P~V~M~J~=~N~X~5Q}xE}k}h}_||Ϥ|I~Q~~=~~M}W}d}|||}T~r~N~~?~|ƺh}p}w>~B~ȶS~J~ҽU~a~i}n|;~L~H~N~|6x1|6P~~W}P~>ǻ\~~|{{{{{{{{{|||r|؜|}W~|s|v|P}_}q}b}}S}z|{|{~n|ƽ{ȸ{{Ɲ{xH}@~l}|g|Ƚ~|C~J~{8~{;~Q~W~c~m~M~U~^}l~Q~L~Q~P~Z~d~h~څ}ֈ}|||T~|||t}|||||}s}|||||||x}˨|c~U~f}r}t}c}||y|||y}}s~a~q~W~w}P~|J~|}ۊ}ƒ}j}z}_}И||ƒ||}b}ť|r}\~X~|ü|ؽ|y}l}½}Á}u}}Й}|j}Ŏ}|||s}W~~}n}|t}|||Ŧ||ο||j}||||„}n}،}||Z}Y}}]~Ȍ}ߎ}i}||}‘}g~x}||||||||˱||Ӳ|p}}}{}||}Ո}s}Ʉ}}|Ω|k}U~Dz~j}օ}|}|y}}̓}ʥ|V~m}|J~ºx}h}[~m~~M~}i}||¯||||Ę|s}|g}V~R~G~p}f}Y~P~|}e~_}M~~F~v}q}\~x}΁}|{@~P~K~:;8d~ɾZ~h~M~Z}ý|n}}K~H~Žp}Ǿp}T}Q~[}t}}~A~{}}Ѐ}|ͧ|}^~y}{~Ս}u}t}V~b}H|L~ƽv}|l~}|d}}[|}J~x~{}J~|}M~L~U~X~E~ǸL~5Hd~Y}F~9~E~=~|||{{|||||ܥ||m}J~»q}V~U~Ѐ}Ӂ}}\a~]}p}}˾[~ôW~K~c}óW}X}µe}]}Z}ǜ|ة|{{|{|{y|V}[}|a}\}F}^~ſ|}|||{{]|Q}||~|L~|r|Ԝ|}|Dz|l}z}D~<~O}`}9~[}h|`}}J}t|{||R~z}@t~~M~ɀ}n~O~n}}p}o}b~}Y~a}c}NN~g|Ѧ{{|{||}a}|c}a~j~Y~ݷ{z|{|e}S}v|||ȿ|˺Y}X}P~i~c}}V~j~^}ž^~u~t}|İ||’|g}N}V~u}_~]}ѯ|ġ|Ǥ|u}l~_~w||s}s}[}U}|F~e~e}˧|}ԉ}t}X~|P~||q}\}S~:~||D~I~T}H}<~S~T~Y}L}}|{{|i|{{¶_}B~z6X}ˀ|G~N~Q~I~h~?a~]~ǵE~A~M~C~A~F~X~5:~G}G~?~X~ŹM~V~P}K}J~h}v;~C~M~y|J~<L~R~}4<G~̔|c}m~ُ}||ܑ|F~~}E~U}k|s|{{|||||||{x|Q}s||o||{~m}}F~I~Y}^}Ͽ]}d}z5~l}F~F~[}n}Y~T~W~S~`}P}J~9^~|m}ڃ}I~9~}7~:~<~V}n}W~E~{8H2y/R~I~y*H~ŲC~̷B~I~D~нF~V~P}||{f|T}y;~^|r}R~}܎}P~Ќ|̾v|[}A~˸G~J~y-ɺN~?~b}{8~n}s}|O}R}Ƚa}C~U}m}i}B~Y~F~Y~8:N~C~W~K~K~|;~E~M~Y~G~|Ñ||]}|||Z}͖|ɾi}z}N~ƽV~T~M~ȽR~D~~B~|`}x}M~H~M~f}k}f}p}{|a~x}|ڱ|Բ|͟||a}i}||¼|d}|{|c}{}g}Y~zG~K~|u}z}|{i}C~\}{N}˽M~E~k}D~ʾS~µX}b|{ғ|~{~|g}[}b}M~Z}?~H~3|-B~c}Q}G}ʂ|H}k}i}T}W}o}A~S~N~A~S~M~{}k|_}һD~8~@~>~ѻW}3~ֶ>~<~U}?}T}̸S}e}Q}ֆ|K}E}ƺa}a}|j}G~D~C~q}b}l}H~{x|c}u|{||{{{]}_~˼F~f}g}O}]}J}:~z3~<~f}?~P}k}y|x|e|~\|{s|u|xI}}9~c}j}O}{s|Q}K~ǼR~R~H~x}C~i}͕||n|µ{{{{{{V}M}m}P~;~D}Z}p|˻U}4~w|f|x>}d|{ę{{{{{{{{{{{{{{{{˻{{Ӊ|E~W}o}õf}T}P}F~x1±F;ƵLKJAT~H~V}Y}Ǿ|Z}E~F~X~_~ľ}x}}]}I}O}h}=~K~l}||^|{ϲzY|Y}T}W|Z|g|M}H}|W}D~@~¹|P}ĵ]}±U}V}D~N~U}|ξ^}i}W}zN}|||p}]}{{{{||Ľ|d}}{A~}|ſz|c}|ĴS~]}H}?~@~>~L~Y~I~L~`~:~B~b}||ƒ|[}C~v}l}d}Ƚb}K}}|||z||n}D~~X}G~||k|ҵ{{p|R}W}b}w>~|O}P}^}μ`}ȴO~|6}@~z||[|N}G~J~ө|{{{}}|{z|ޝ|ƿ|}a}|{{{|G~c}|w}¶g}V}~||r||{{Ǵ{s|{{_|}g|{µ`}A~I~~?~e}l}}]}|}}́}X}|Ȋ|l|ʾm}J~N~߀}N~w}K~p~}p}ē|~}ܫ||}[~T~u}|{{||ֵ||Y}||y}|ŀ}ۊ}t}z||Z}ł}||ѹ|}b~{}}c~ʿl}Ŀ|m}ҵ|ٙ|O~|C~}e~W~΅}}Q~]}}}z}f}y}üz}}}R~}w};|;|f}c}u}||}j}}އ}ҍ}||j}|t}ſ|}h}}c}e}|||m}~}}}|e}}}U~||۱|a~|GR~|ū|z}x}ʀ}„}|z}}||y}|}ױ||||||||||||_}r}a}|||||v}̀}{}u}h}n~X~u}|~||Ɂ}v~t}K~V~}}P~~M~}U~P~b}|ݽ|j}V}i}g}|ۭ|Y~˶|||~}߂}X~}c}}F~Y~N~P~̉}b}|W~ك}|U}|||r|O~^~E~a}@~8f~M~J~L~R}žn}|u|{||}|||E~{6D~m}x}k}e}{||o}_}~}[~u~}ƽt}ߎ}~~U~}}f}û|}|s}u}e}|}R~`}p~|u}ĹV~F~N~̾Q~>BS~k}r}w}O~k~G~d~}v||j~|ɝ|||||u}}݊|žn}l}_}q}CCD~i}}<~F~S~Z~W~>~J}J}N}O}|{g}ǿ|k|{{{q||N}c|g|Lj}|}|ʑ{ψ}w|u}t|{٦|y}u}q|{ة{{]}}m}y}k}}9]~]}L}y|?}u|k}W}Λ|||~`}KX~|Z}¼a\~ä|~^}m}d}a}U~d}}X~d}]}d}X~|[}q}Z}̲|{j}˾b}O~u~f}M~F~C?|ľ|ώ|{b}õW}o}ƻ{||u|@~|G~}~J~BȾm}yA~}R~»e~K}}<l}t|`}[~^}t|||l~t~ˀ}_}|h~ź\}T~i}{~}_}|||Z~{}Ž|k|H~µS~[~R}|y|{{|t|F~d}{|{<~h}b}X~W~`}G~G}g|o|j|v|s||E~j}]~Z}H}i|||n}A~˼Y}J~A~;~S~˾[}}9~A~ϿL~W~~.6}6T5{6~I}g}f}v|w}Ƽ[~Q~s}j|}|a}i~CQ~X{:m~|H~}|t~½}U~o~a~D~[~څ|ɾt|ƶZ}Q}x|ǿ|t|||U}|||x|z9~~N}xJ}u|N~<P~j~@~g}P~P}a}|i|k|ۂ}s|c}}O}@~X~J~~3\~L~@~LC~s}k}̍|ȼz|9~A~:~@~A~}A}IJ{߿{U}|{|ý|r|οZ}@~ĸM~s}<~C~D~9~;~y3~G}r|Ĺs||ƽh|z>~|V}g}<~<~L~@~W}ҿR}y3~;~҇|˽`}g}S}o}q}@~@~F~>~Ź`}V}]}|N}zM}>~K~g}?~1@5K~x;~N~>~o}Y}e}p}t}?~A~K~[~O~u}A~ɾT~`~V~F~K~K~F~P~P~v}T~U~k}|n|k|k|ڐ|Ⱦs}c}||u||||||f}|}}^}ûr}t}w}}m|{{g|d}r}P~c}Ƽk}d}|[}}B~t}>~q}U~BƸK~h}|7~n}?~h|ô{Ľ{{{]}|Ⱦ{|[}M~=~4H~9~T}j|{{|Ñ||{|{|—||ƹc}T}|n}I~}q|m}=~W};~v.5A~<~ۼ@~׹@~Y}y/~ȮH}дK}B}{u|M}R}b}O}T}n}^~O~=~7~j}y}}e|{{]}|{w{v{ۑ|F~}8~;~¶[}e|ƶV}y2~A~`}\}οY}}|W}ބ|u|{{p|{|R}?~]|~H}V}O}M}|{I}9~B~A~>~q}a}x}g}\}g}G}X|{{{{ؒ|b}O}^}k}K~@~8~L}=~4~b|F}I}\}{{{{{{{{{{{{{{{zzz{z{||Ә|Ƽi}^}ɸQ~;z5NI¹Y~U~[~Q~J~N~}H}X|P}~}U~V}{D~þ}́}I~m}T}P}h}T}|8~q}N}l|ł|I}Q}L}z@}p|M}ſ{|̾`}|}^|{|ғ|l}};~@~C~Q~{K}O}zI}T}\}ƹe}>~u|{ȸ{}|{{{{{{{||g}U}z`|h}}V~l}R}G~_~A~I~h}{|u6~{:~o}E~\~G~V}{Ή|y}B~z}^}x}A~Y~n}s|ã{|U}|||{{~|{i|ߥ{|y|s}I~}7~b}K~n}G~X~f~b~?~I}d}T~N~}q}ǽr}p}|x}H~|||x|{{||̙|w||~N}֋|ƻ|{{{{Ǿ{{{{g||ʾ{|{˪{J}m}g}}d}|q}||}}]}ˢ|\}M~Z~P~|D~Y}_}j}Q~|u}c~a~h}|{}s}U~}|||z{||U~G~|}}҆}}ܐ}|[}n}|e}S~̄}|||||}Φ||l}|||c}}П|v}o}W~^~}Q~}Q~Ҏ}|Ë}v}|n}}}Å}l}}P~}|g}||}s}r}|||g}b}ٍ}`}|ʡ|ӧ||Ԩ|e}}K~N~x}k}h}r}|g}ȑ}y}ǀ}t}|j}P~R~j}u}}Ξ|X~o~}~}e~L~h}¹|||||}||||‡}X~a~w~[~ƾq}`}{}g}р}Z~}{D~V~g}̃}||L~i}r}{?~Ľw}Ľv}N~I~}v}w~b~N~Žd~{}~p}Ck~|ӄ}͇}k}zI~M~g}m}Y~g~c~K~{A~J~Q~}W~́}ن}Ǿ|}S~C~z>~`~H~z=~u~=r~I~R~g~==U~}M}P}|{u||{||Q~}}i}`}|{|\}O~]~_}X}||||}E~O~~L~F}؅}^}ޓ|s}}؁|¸_}{}w}f}T}Ǿ]~ߓ|‡|N~ծ|g|K~Ah~Ja~V}U}t}}B<f}e}r|y|{||d~||[}Ŕ||U}]~ʻO~;~?~H~@~}N}P~I~L~L~D~ź_}J}^}M}E}Ǻo|V}N}[}||{{e|{o|˃}Y}ü|{{{{{x|~|]}Mg|p||||Å}f||w}_~ƿ_~|¼x|d|R}>~z.9~v0~̽X}q|P}|{o|u|}||}ÿ}|U}k~~;U~ƕ|{}|e}I~U~u}k}q}s}|c}|]}J}a|]}|a|I}zF}G~~@M~x~Q~M}@I\Y~|г{I^Ӡ|}^|v||c}Ӡ|}¼^~T~t~p~p|j|Q~V~S}l~ʡ|?d~T~|R~V~e~q}{}|v}r}Ƙ|^~s}^~s}K\|J~}D~[~|[~||y}P~X~6̾Y}{κ{{s||;~\}U}h}=~X~63D~p|{{ϻ{{h}c}÷Q~V~?~>~7~n|f||k}X~_~O}}Y}w|{|_~n~Ba~BaL~EEEn}I~U~ĸf}N~Ct~\}|a}}|ݎ|\}Y~ijE\}V~Z~[}{r}ȿZ~;~8~w4~ǷV}ŲC~M}6~a}ξ]}P}9~R}L}|Ć|u4~7~@~X}^}ȼb}̾z|Q~Ǿs}>~N~ʸZ}O}Ӏ|c|z|[~X}G~ŶS~O~Kl~J~F~|>~J~G~P~[~Y}g|n|@~D~F~g}b}f}Q}ʀ|ϒ|||||{S}J}b};~Z}m}{3X~D~M~E~B~G}H}P}x}M~=~b}`}x3~_}Q}a}´Q}h|H}V~Y~=~X}R}`}V}n}>~D~;~}M}|||G~T~ǻR~P~}>~\}c}E~ĸP~V~V~V~V~X~@~v}s}j}J~ǻQ~J~V~÷O~X~L~K~ǻM~U~J~N~X~L~AʽO~^}F~s}_}n}x:~}=~N~`}i~z~}|||y|ӧ||||V}e}|||Y}Ƚr}}V}N}B~Y}q|x|X}M}W}B~C~^~7Z~c~A~g}B~=~<~b|{{{í{|c}Ќ|z|=~ŸM~>~ͺD~L~M}a|{IJ{ʦ{ո{ԍ||n}H}P}i}Q}{N}b}@~R~s}~T}N~C~K~J~t*Z~³X};~E~G~L~įB~\}@}u:}{[|I}|1~|/~t-~6~J}B~:~<~?~;~?~J}M}Z}}Z|K~¸T~Bi|w{{σ|m}R}|g}ξV}O}@~q}h|J}a}x;~w}|l|{݂|K}r|F}ח{{ý|o}X}T}m}ƲP}s|z|@~g}>~?~K~]|m{U|׋|e}Z}O}~J}N}c|_}\}F~z}O~b}`}g}w2~ʹP}j|c}N}S}\|й{{{{{{{|{{{{{{{{|{{|X}w}}z5c~k}ƺX~_~C<ȿZ~M}w}}}{?~=~T}^}f}t}r}U~N~»l}J~w}Źd}Z}{>~F~j}B~T}|}p}Z}|{k|ɸ{|X}o}v}ԕ|^}L}>~C~B~ïK~x2ƴX}p|yA}[|ӟ{U}C~d}Ʈ{{{{{{{{z{{}x||d}j|j|u|p|N}{D}|Y}j}=~a~B~M}x}l}<~=~h}g}N}y}O~[}R}P~f}ܿ{{´{o}m~|[|{L}Ƽg}r}W}y|{{|{|vG}y|{̵{{}J}˺a}K~[}d|~|Ⱥc}_}@~C~D~L~G~M~\}V}i}E~n}I}|ù~|{{{{{{ؾ{e|p}c}{|{{{{{{{x|`}A~V}p|yB}N}ؒ|Ҟ||||||~||v|r}||_}}e}}R~R~F~u}~E~[~|||z}}i}n}||Θ}|||||˵|d}|}}w}|r}ݎ}_~|q}g}d}h}Ɂ}}Ū|z}Պ}[}||Ј}x}||||h}|ƿv}Z~{G~}w~]~d~Ÿ|}m}m}e}|~c}}}r}w}w}Z}ġ|_}||ű|Ԁ}}~_}ƥ|`}W~c~`~F~R~_~U~S~I~„}r}т}i}}P~ʀ}P~T~x}Ű||||j}}o}}a}P~q}V~u~o~R~O~}v}|~_}т}}{T}s~b~r}]}m~y}}}{}|I~P~X~[~I~c~߄}W~R~X~|}́}x}c}q}h}[}|U}P~W~}|^~}E~^~U~o}h}ҙ|n}a}ͷ|x|^}`}M~j~Q~L~C~F>O~q}x}t}z}I~t~v~܂}AW~`~89}7`~d~P~L~T~X~T~ŹT~T~o}L~J~J~М|||||h}Ӗ}ǒ}w}Q~E~ǽf}c}f}T}\}o}c}k|z|k}V}N~~}ͤ|b~ǿ|j}ۤ|X}x@~f}b}Ē|p}p}h}X}f}{ߐ}Q~z|ч|g~f~I~Ll~|F~r}`~T~k}R}|˂}v|Ľk}z}q~X}}[~~|g}||^}R}{|Ӏ|y|J}B~k}Y~ŷI~·_}Y}M}P}||s}E~ǎ|d|D~e~A~\}S}}J}V}_}|Ӹ|||uv||vw|޸{O}|ӊ|k|ΐ|q|i|ڃ|y~~}@~>҉|M~p}o}P~;~x|I}U}J~0B~C}|޷{{{|{{{|||g}û|u}~}üw}||}||P~o~d~~E|a}NJ|x|l}i}]}Z~م}J~I~d}}||J~U}l|}Z~ż`~zB~P~f~]~|ȇ}p|e}|y|M}F~I~y}L~x~ýi~}h}M~FAY~Q~[}{x||̀}ڽ{n|H~e}~Ew|||}V}X}Pz}{u|ذ|}X}g~T~|ӫ|s||~^}T~T~S}|}V~c~f~Y~||{k|y8~μH~C~6X~z;~||{|[}l}6N~<~b}ôH~<~N}o|T}J}\G?}F|l|E~{=~o|v||u|O}b}Ԁ|l|}t}L~H?H~Ez9s~|}H~U~G~p~J~Q~r~P~UNP~Au}Ch~`}d}˺F~:~?~5~I~?~D~<~K~I~w6~}|Z}58H~X}|~R}ż|Az<z}<;3űH~d}\}P~G~{G~Z~]}z}ՙ||}J~l}Ʒ]}ǹ\}hi~||I~g}}J}|}H}˽||a|}9~=~\}]}Ƚg}ȿd}Ր|u|v|~|Lj|S}~|Ļv|}|\~ĹU~{:~9~v}W}{µ{f}E~`}̽]}s}`}M}^}8~9~<~X}Ź{|ތ||v|[}N}|Q}s}q|{r|r||x|||̔||z|t||||Q}B~J~B~˾O~X~V~R~ǻO~@~H~I~P~E~?~||c}m}v}@~~|v|U}l}H~m}s}x>~G~M~n}Ȋ|||{|n}X}a}g}V}|ߕ|٢||{|{y|S}_}o}h}Q}`}_}h}@~@~P~=l~r}L~M~K~m}z|w|{{{{{a}e}Z}h}z5~ɷJ~u+ɷG~`}{Ħ{Ӿ{{ţ{{h|||d}k}>~e}}<~F~E~w}G~H~C~<~ʽY}A~z2T[~C~M~Q~ԿK~V}{{W|{E}ìH}B}z/~4~c}N}7~ĽO~ƺ`}@~\}S}űP}ȳP}O}݂|m}x}<~žh}M~ûy}|οzʀ|h|{M}b}F~8~w|_}b}R}L}b}t}f|{G}_}`}d}g}t|{{|{|i|Ƽc}¸^}a}Ż`}Y}t}>~w}Z}q}|{|{L}a}´V}y3~r|_|ôV}Q}s|F}Y|\}d}ɿa}J~z8~{E}o|{{{{{{{{{{{{{{{{{|uw|{{{|{|}g}f}`}V}8~6EV\~q}g}r}T~`~\~I~}|{}k}b}R}P};~M~I~H}i|q|k}K~~7~J}g}r}}7~M}ӱ{|Z}֟|ӟ|D~Ĺg}b}A~[}f}N~M~?~:~z7~<~<~}8~9~|ܸ{Ѹ{{{{{{{zzz{{|{v|V}i}|M}Ѕ|F}҂|I}I}wF}N}^}b}:~=~d}y:~c}D~N}{{}{g|u}{}h|{`|̽c}S}Q}[}|W}Y}~@~źW~s}y}|{|ƾh}{|{|{{l}x:~ƒ|T}Ǿu|`|s|h}f}ûq}ˆ|n}r}H}W}|x|Q}ۊ|݌|r|||xz|{{|{ΐ|`}m}||~|{{{z|||o}d}݉|Ǽp}M~ƿw}|z||||v|{d}|~|||`}n} |_}|}z}݉}k}e}u}|{}||{|||n}|||p}|£|~T~}s|^}}||m}||}Ǫ|l~}}r}k~{}֟|}Y}¦||o}Y~c~}|l}߾|v}|m}|f}_}||||`~||W~a~n~Ċ}|ߩ|v}||||r}n}|t}͙|]}ƀ}}þ}~}Ļl}x}}N~p~l~u~^W]~}P}y}|}}Ր}b~̬|o}|ĥ|||þ}r~ƿj~ӈ}z}}}j}MQ{DGe~P~ùc~O~g}||\}Q}||}P~ýx}~}l}ԭ|ө|x}W~R~K~G~»o}}e}`}a~]~n}m~}q}g}V~}d}}b}Q~Q~O~߆}}u}|}V~Y~I~f}Y}Ƚa}N~{6d~6R~S~S~I~[}۟|||G~ACg~q}Y~b~J~f~}?~r}T~H~Z}Y~p~B~S}O}ºg}Y~@~d}z}t}{}||n}ɿ|||}_}D~l}e}¸w|p|Ə}^}x|{||}c}|}{~j}Ί}\~y}߷|}~M~}t}_}{|k}{e}c}q}S}|D~[~h~}}}q~d}|l|ـ|R~U~|t|l~E|z}|U~R~a}o}|}}l}{l|u|c}6ȼQ~?~ȻP~C~S~:~t||}|_|R}S~w}x||K~A~N~H~O}:~żc}υ|{|}|||{|r|~|ŻX}|g}z}|g~o~К|P~P~p~b|–{Ĺi|~F}<~?~J}G}q}ǺO~ξH~ɹR}e|{{{||||||||v|f}{`}}t}x~^~_~{}|K}|||J~Q~߆}Ȗ|\~}|h}{{k~\~a~W~Θ|B:X~ʬ{i}~O~|žbW~n|Y}b}j}~O}y~`}g~\~O}RYJ~i}N}>t~||E}S}S~\~A~Ļh}N~|ܥ|q}|[}ň||z}||{{|ڝ|Т|{E~Ĉ|y~ǂ}T}f|ҽ{o|x}Q}r||i}|ϼ{q|||E}ч|l|x|u|{z||c}O}y:~;~X}K}H}k}t|o||J}I}9DKh~T}Q}f}Z~S~`}S}>~e}m}Q}X}f}A~ƹb}X}ߚ|q}{|j|h}y}Y}n~^}ƻm}C~\~|}g}H`~x}K}n}=7e}J}|3~x3~@~~3~˻J~d}6~c}k}8~S~`}z|ڪ|b}k}u}ؔ|s|k}y|p|}W~T}C~`~_}\}ި|m}Ǡ|||ɺP~ȵO~|t||z5C6ǶG~^Iz|O}Y~74V~;~´[}N}J}ñC~P~|*K~t}?~v2~J}Շ|p|{]|b|NBv|{|||zG}N}{||{{Ҿ{{l|{|}}g}g}H~V~T~Q~I~L~p}\}|N};]}g}=~<~]}@~?~P}||P}g}g}`}A~@~A~U~\~2211źI~J~i}z}B~f}}X}f}ƿl}Ľl}m}_}ؔ|`}|>~;~;D~l~k~N~W}a}Q}t}|||}^}b}ĺh}|}}A~C~s}|t|u|q||_};~@~}:~>~_~X~´H~P~_~6V~A~I~C~U}U}|{{{{{z|r}]}J}s|c}E~c}{{ؼ{ۻ{ӷ{{{{{p|j}|:~ɽ`}F~ŻJ~>~W}?~Y~R~?~B~>~e}ɸP~B}:D@~>~g|ݪ{g|w|жK}~0~3~4~y<}ϺQ}8~E~>~}+/X}r.~M}q-~7~\}z|e|}3~t}|9~_}s||e}y|͆|h|O}J}j}C~E~C~C~R}{|ȸW}J}M}x|[}ża}m}m}s|{zȪ{zI}M}{{l|·_}ĺc}_}u}~8~D}|p||}=~~<~X}\}R}S}e}Z}|C}P}`}I}j|x|[}L~ƷL~Ծ{||y}{||{{{{{{{{{ļ{{{||~|{{{{|{{|u|]}u}e}^}ً|=~<~b}:Y~}?~U~F~J~X~U~}`}m|q|3~|O}@~L~L~q}g}_}T}i||<~h}ȺP~C~\}g}|ú{ƪ{t}Y}ĵ^};~S}I}f}e}~=~Y}U}U}ɷP~N~9F~|{|{{{{{{zz{{||k}l}Q}S}a}=~`}̴{ų{ʧ{u|g|xI}b}h}|:~=~|a|{{{{{p|L}v|ʔ{Z|{{ȿ{i|s|i}O~O~I~F~]}k}}ļ~|[}h}}|Ⱥy|e}d}ɷ`}s|ί{Ռ|؍|~f|{]}x=~|ӕ|u}O~Ƽ`}K}c}o}d}|||||{{z{{{{{{||~|||||||ڠ|||Ę|z|t|t|{|||ש|o}|j}}}i~úa~e}M~h}r}}T~a~l~}|£|N~b}ω}ڑ}}O~X}þ}}}r}b}{}|Z}j}O~}o}\}b}p~}g}{}g~T}Ŀ|i}|ʚ|}}O~Y~O~||||و}~}Ô|ј}|׋}f}t}|ҹ|Ӿ|~j}|||}|||ǹ||||||ɴ|Ӷ||j}|||p}||||||Ǫ|}O~Ђ}j}|N~e~Q~l}~}l}}}^~s~v}e~JOFf~GNGTAGJL~V~[~}ݏ}|Β}ƾz}|m}g~}X~úu}U~|||¯|}Z~_~|x|]~ˁ}Z~zK~i}n}X~l}P~y}g}҆}i}I~b}]}}Ǿl}^}}h~V~|A~}}Ļ`~}ǼY~?~<~C~r}b}@~yB~E~P}s}_~i~~:l~H~B~ߑ|L~\~`~O~{=~ƻV~M~Y}|g~f}|y|֕||x}T}||o|{{{{|˯||h}|ֻ{c|{q|p}|~~|{{||]}Ո}[}Z}X}}r~|i}|X}j}||{Դ|Ҙ|R~||r}b~q}y|L~z6C݅}|\~Pj~J~j}L~}J~h}i}CFI~{}Iޓ|ކ}|}}~_~||[~‹}|{b|~O}C\~̾R~T}Ջ|b~b~Z}A~l}B~u|h}h}b}V}P}R~B~h}a}T}l}z:~ž|{|~||}I~F~S~O}d}p}a~|V~׏}ߎ}c}{b|~?}Q}>~U~~9~v0~W}҉|||}¼z}{||~|{|{||{}|L~W~r}¹Y~|>܇}||||j||||l~V~}t|΋}i}|{{~z}|~|L|\~N~||i}AM~q|r~Y~i}h}L~ǿt}_}R}|Z}`}S~Rp~{|S}W~̀}|u}V[}c~}5n}»a~<ɾV~IŸM~94l}BՆ}E~]}[}{{^}ڸ||{{~|Y~R|n~p}{P~J~b~V~l}M}t|v||s|m|~|{s|o|{{|P}Z}Ԅ|T}U}Źt|g};~Y}J}`}[}d}j}s|@~U~n}c}O~IGZ~ƸL~J~y/J}h}a}E~R~c}p|m||g}c}L~Z}|^}\~~G~۝|}}ό|ܘ|V~\~Ն|T~EN~B~d}}0ϾJ~;~K}T}r|d}{5~:~f}j}Q}݌|Ȕ|~|·|B~d}F}˻W}`}[}|f}}S}R~z~f}||p|Ҋ|B~l}[}l}7~}6~u7~|c}|@~x14w,9~4~G~W~^~;A{,<~7~ǹZ}8~7~X~6z0I~@~8~]}ӾU}b|x|ȹT}|6~^}v|q|{|{|~}r}|{|||e|{{r|z|y|y|||T}a}|Q}zM}…|w|p|³X}8~J~D~>~F~X}V}i}I~I~E~@~ŻR~K~?~i}A~M~H~W~b~n}G~F~K~Q~B~c}ȿd}A~e}o}b~}3586?S~|˚|ûq}t}ǽg}]}^}p}|W}u||Õ|Ο|u}{}G~Ļl}Ɂ|ڎ|T}S~W~w/}.7:;X~q}J~a~d~T~_~V~I~U~o}ʋ|u|{¸{{|{{{Y}\}c|l||t}c}{x|{{{{ѯ{{|U}K}W}Ȃ|Y}A~q}H~LJ|v}E~A~}:~ʽT~¸P~ùo|P}˿G~37c~k~c}d}|:~9~V}D}P}|W|[|`}S~~.9~=~^}g|A}m|p,~6~5~<~F}y=}X}|8~||`|r|o|u|o}{{ǿy|>~]~ŹT~B~P~I}ϕ{Y|N}k}|}ο_}?~@~q}I}{Y}||Q}_}ºc}K}t|V}W}P}@~{:~N}|R}~F}~I}Y}R}d}6~@}P}T}=~|8~8~ɿm|֩{X}C~ǶG~V~x|zz|{{w|{{{{{{{{{{{|{{|{|{{{|{{}||V}|{}[}x|Y~R~I~g~BU~E~w}w}ǽ`~ĺ_~R~r}X}w|F}w|F}n}\}@~>~V~H~m|ɸ{|S}Źc}K~ƷW~Z}̼R~I}{¾{e||||G}K}x7~M~˾}|Ϳ]}p}R~Z~}=~|w|j|{{{{{{{{{{{z̴zz{ƿ{O}|Ʀ{q|g|[}n|۬{|{z{{{j|b}q|{{{n|{{{q||}P~k}ܖ{{|T|Ӣ{|I~³T~]~^~I~O}{>~z;~։|ߴ{Ĭ{Q}L}Ѿ^}־P~Z~q}||{{Ƿ{||||O~V~}E~`}J~M~w}g}d}ǰ|||yz|{{{||||||||||{{{{{|׻{{{t|}||}y}»u}ˁ}\~M~K~|v}s~m}q}N~X~`~^~p~l~k~}S~e}a~Y~M~f}_~P~q}O~e}х}܉}[~m}h}e}T~o}||s}e}¾}ĩ|||{||ӱ|}f}t}l}|r}X~}]~i~}o}V~]~Ѳ|w}T~¸o}]}Ń}|}N~ٛ}}~}ܖ}|||||z|i}|޲||u}Þ|d}|||||}U~}c}ߩ|x}||{}MNk}|}n}}Hz~~UYVOQK[`u~s~f~N~g}Ǥ|Ŭ||w}t}L~m}ʨ||}R~}y}}X~g~g~̏}|||ſm~~}n}P~ǿo}s}N~ǿv}Z~w}P~}d}|u}ԃ}v}S~m~Q}J~K~G~\}h}~Lg~r}n}}Q~U~?~y:~A~=~Ս|}d~Q~P~s}Z}^}|t|{|K~Q~y}V}u|{ʢ{{{||}~|{||{{|x|k|{u|~|y|l|||h}o}њ|r}||Ƕ|||a}i}|d}a}|}N~Y~h~|{{ۯ{||W}Źl}Ў|{y}^}y}M~m~^~c|}_~}T}߄}l}Ak}|HMb}f~=||}S}_}|\};n}̂|R}R}R}=~T~ʽT~<S~K~r|u|||r~W~V}G~U}l|~S}x}b~E~O}O}c|ܲ{Ӯ{ڳ{o|V}@~X~Z}{}ո|g|c}h}}~x}{Y~j~̯||}d}d}A`}l|l|m|n|_}E~h}k|{{j}}p}p}||||{||x|Q~pD~ى||ש|u|G~|l}|{{{r}|}M~BË}}a|ߟ|Mu}̊}T~s~k}|w}]}|I}k}r~¼o}Ր|}^C~|F}`}R}N~c}|{{`}l|X~T}L}|}|||N~i~y|F~7g~ƾ\~}J~_~K~`~`~X~?<~P~c}d}l}|{{f|{{̱{q||p|t|t|˹{{|x~ONN~D~F~N}|I}w|q|j|r|M}|M}M}g|{{|o}z5~ξ]}|v|Y}l~X}{q|֕|m}L~˿e}ý|_}g~M~M~^~S~F~B~[}yH}:~D~D~>~S}ͽ{}i}F~Y~\}˓|`}W}G~i~Z}]~=o}w}@WƻK}-=~?~S~S~4~:~=~?~^}l}}|||²J~k}L}|8~?~D~R~7@A~s|S}|d}}B~R~u}ɷN~y9~T}e}C~n}Ļ||h|ߎ|m}o}|Л|U}Y}H~N~=~}d}yB~l}r}75{,ĴC~L~M~L~9~/8~\}4~¯A~H~F~~|{\};~@~F~k}|s}a}||k}k}{{h|q|s|{{p||p}P}X}O}ȍ|l|y|O}|8~c}g}Z}ʇ|݉|]}W}~K}T}V}͉||Q}U}đ||`}U}|s|Ĺa}F~=?14<==76?~ȝ|C~}37C<Y~S}N~F~C~|դ|T}új}~}u}˿o}Źn}g}}]}|\}y;~;~E~@~F~`~7GA64D~|N~K~H~ǿf}J~}S~B~\}|v|{ϼ{{|{{{t|j}Ȍ|^}S}h}n}X}~M}z@~i}|ç{{V}X}W}øe}u}D~B~T~E~ŵQ~I~J~_~Y~O~H~E~n|ڀ|k}|1DI~Q~V}l}{7~H}{2~9~6~E~O}>~ؾP}7~εC~^}e|{e}W~R}Q}U}6~B}мW}a}5~v4~x>}̓|a}~}g~K}zˆ|P}k}o}>~|}9~K}N}J}s|B~G~:~`}@~b}]}|C}S}̾[}Z}`}h}p}N}S}r}K~n}̿]}T}|Y}l}L}¸\}b}8~\}f|I}y?}6~H}N}=~{4~d}^}=~@~I}p|ȼ{{{{{|{{{{{{{{{y||{|{{{{||{{֞|s}~A~b}b}>~úd}v}}H~i~<<`~f~x}D}c}Ռ||T}Z}}|j|Y}?~Y}<~Y}l}^~g~Y}z{P}\}ɺK~P}^}a}d}b}o}H}H}T}b}|>~L~V~B~I~:~e}{q|ɢ||{{{||||{{{{{zz{{ٮ{³{T}q}ùb}o}c}Y}[}j|{{~b|ż~|u|܊|Dž|o|{{||a|{|{{ئ|r}X|}^|װ{θV}N}ɼg}µd}l~W~W~z?~H~g~C~l}{{j|{{O}D~?~d~m}{{y|{{{|k}o}d}w}e}ڊ}q}|||{||{{{}|{||{||{||w|{||||{{{{{|||\}ބ}M~K~O~^}Ӂ}}|{\}z}}K~}}€}֊}N~Q~Q~}|}k}}Q~}y}}j}~}}¼o~g}»x}ܐ}؍}||}|l}|ȥ||ý~}օ}t}d}i}|Ӑ}g}m}|||}`~Z~ʍ}t}žd~G~~{}„}j}|؛}a}}}h}}}؛}}y}̮|{}}v}|||Ы|b}ӆ}}g}d}\}e~y}q}½n~k}z}_~ށ~l}|||x|a}}ݒ}|׊}y~GITcƿa_\RS[O?S~̀}{}}V~Y~~h}|^~d~R~}}}^~F~`~ۯ|\}g}ܦ||||Ԉ}~}||ֆ}T~[~<~}σ}s}\}c}σ||W~c}Q~C~_~R~J~O~k~F~j}K}Y}O~֓}۷||B~j}ôZ}k}O}|\}d}|ñ|}}}z}{~|@r}|„}ܑ}{{{{~||||||||||||||T}|e}y|c|[|~X|u|f~x}|^}ˏ}s}u}}}~||̲|t}t}n}Ԃ}J~`~ɱ|_~}}|O~o~i}~|q|]}|}N~|~y}n~`}|~Žt}J~}h}[}Y}{C}r}x~a~Q~|}Z~Sd~g}|Z}b}n}|z|;~E~N~z4L~a~F~j}j}S}\}E~R~t}Z~^~S}s}z9D~M}|||};~ۊ|{}N}J}S}X~X~O~t|f}K~{D~I~ߋ||}w}~H~|}y@~`}h~~}o}|{{ǡ{Ξ{}[|Nj|~|{{|}{|{]}ޙ}|||{|ӣ|}}V}|M~K~v~|s}YR~|}|Ǽ|{}g~x}Ĝ|Z~h}i|s}g~΂}o}n}||ĩ{j{M~׆}]~b}Ԅ|T}|U}C~MMw}L}g|Ԏ|Y}W}|m}m}]}{~U}r|t{j}L~`}T~|O}q}ݓ|O~C<y}t}E~I~f}j}}T~H~Z}b}V}˄|{|_|m}ɾf}|h|p||{n|{}|N~PIƼp|O̿L30^}B~=~8~c|{L~Z~;~9~<~~7~S}l|||E~>~c}G~P}A~Žu}{|Z}Ǖ|͋|˙|~|q}?~I~D~_}P~6Q~H~?~V~V~@~V}|_}s}d}a~O~Q~|q}W}m~Oa~n~p~U~A~_~{8O~ŹP~I~c}=U~p|S}U}u||L}r|k}ѿJ~\}k|Q}P}|H}Q~N~ü\h~T}_~z=~ѣ|||_~>~\}o|i}|;~q|{̅|>~D~j}=~F~<~R~|=~C~B~f}׎|{|||z};~Z}d};~I~D~]}}_|ݳ{~_|ȹG~H~V}c}J}e|h}|;~T}{2~z0~G}w>}n|w||{s}a}N}~|m|q|j|i|{}|P}J}`}g}X}C~f}||y|T}i}y4~x6~x|u||P}^}>~G~v}b}_}a}h}~;~J~N~?~|b}L~\~s}}>~V~]~7{2~|y|U~z}ť|d~}J~t}|V}|~|d}Y}V}~}K~G~z}H~M~[~l~ȼT~t}Y}R}O~9ɾH?<{0˻J~̿O~`~9L~R~B~t}||{x|{{{{{|xz{{|Ң|~S}Z}m}||V}e}|||p||{o}D~F~w}B~A~s}|;~=~p}~B~O~H~{:~B~݃|\}:~x4~H~3?~Z}@~ƵJ~:~H~>~˽H~6~ƯK}K}i|v:}Q}p-~8~<~7~V|źX~O?~V|i|s|k|J}|7~8~̻Y}y@}}O}{}K~]~r|}{|||ȼ[}D~l}_}[}z5~x1~;}x5~W}F~;~y6~6~\}^}I}}D}΃|]}g}c}l}U}q}V}}3A~}S|{|O}K}R}[}P}G}ʾi||w|{|U}<~H}z5~D}ƞ{{z}p|^u~k|ѷ{{{{vv||||||uw|wy|{|z||||{}~{{ww|{{|||]}K~t}A~D~b}L~?~Q~E~/^~M~B~e~M~P~{}||W}U}|h|||N};~¹A~C}O};~G~@~g}x}A~}}~=~Y}|Y}k}X}ǽg}Ќ|g}ĺc}¶P~D~H~f~`~Y|{j||e}l}^}|{|{|||ɚ|{{{{||{{ο{X}~T}x|g}T}e}|A~=~p}z|Y}m}s|{S~\~[}ԋ|ܫ{ٽ{̹{ɷ{{˩{^|{׵{{{{{{w{V}\}O}D~S}Żh}\}ƹd}H~^~Q~N~F~o}_}k|{x|j}ĺe}\}|}d}[}h}|}~|{{|||{{{{{{{{~|||{{{{|{{{{{{|{|{{{{|||˙|||i}P~x~s~m~H~|||n}|}ʥ|ʪ||ٲ||`}o}|G~S~m~ſw}m}k~}P}w}}R~T~ǿx}d}}ׁ}T~~L~r}h}b}N~U~S~R~[~}|؅}¾}s~N~y}|||ӑ}r~n~a~`~c~ºf~}X~]~l~ߍ}O~Z~^}u||_}Ԉ}ږ}}b~[~s}]~~L~}||ÿ|Ā}߄}ߋ}d}h}̡|~}s}|}m}c}Ԃ}~}½|ǿ|Z}f}ۊ}~k}|||}|EHDPG~y~|~~Gs~[~że~R~]~P~||u}w}ý}|}ԟ}u~p~M~Ƚ^~Ź[~V~||P~~V}~J~}v}~M~P~\~~}}}lj}i~_~w}ޚ|c~P~h~{}}Dܕ}h}^~Z}}u}C~l}~E~f}d}[}o}e}g}{{|o|f|}V|{||{|`}x}d}[~||կ|څ}||ݗ}n~|{|||||^}Ϛ|v|~R}_}||||{|Q}z}F~f}[^~||d~}ާ|||p}\~\}|֜|է||}}|||~[}֪|e}{>P~||||_}`}v|s|{|{}N~}e}T~S}P~R~ƺe~e~R}g~\}y|f}J~Q@¹[~\~m~Q<|w||ގ||Y}x}W~T~Q~X~F}S}|zM}z<~}?~ɻKB~B~m}ֆ|}=~B~[}s}=~Z}U}ƅ|ۓ|V}}:~D~[}g}N}N}r}x2R~:~Y}q}i}D~\}W}ؒ|p}k}m}B[}y~j}{{|ɢ||Q}j}S~g}c}|ȿ{{}|ͥ|}u}}{||{q}R~P~||ܚ|x|a}V~]}U~g}}}}I~d}r~g~Y}۽{k|V~d}Mh~F~{|]~||s}d~l~||˥|K~J~|||Y~^}Z}b}|E~`~{}a}T}}}f}e}ؑ|ĕ{c|n{s}P~PV~`}K~¹d~m}_}X|v}w~]~I~e}K~h}ʆ|E~_}r}b~ȿw|ց|˄|{|w|[}e}h}|{g||Ė{{|||ȼb~u~[}v}>ѿJ~_}w2~|4~F}b|{j}m}c}q}E~F~7>~Q}Y}d}l}|g~@~`}n~N~^}K~r}|ҏ|;|[}S}G}?~H~r}ɿ|܌|e}Ϳ_}ƿ|ߡ|f}||T}E~@~}9~N}{||q|g|}e|o|L}~E}v|`~f|6aD~8G~|3~K}v|ͼU}9~:~U}H}G}q|ܱ{r|{{d}@~o}7~E~6~8~D~:~l}ʼ\}X}R}J}y6~|K}_}{9~q}i}f}f}Z}m}=~H~D~A~l};~E~P~X~G~>~B~X}X}z}p}o}G~E~Z~M~{}C~@~I}{}G~||s|~\}f}[}l}j~Cý||a~U~q}_}żc}zE}C~g}n}||Q};~\~99I~z7~ż^}K}q}L~||{{{{{{{}|{{{{{{{{|҂}o}f}˿b}||ܤ|Ǿh}]}m|x|]}o}A~]|q}h}`|h|Q}i}_}Ļe}j}ǼQ~D~G~N~z}x=~p}S~\~C~R~I~6~9~?~˻H~8~@~ðA~B~ļR}տR}:~5~z0~;~H~>~@~f}K}O}7~ӽP}m|{L}m}B~8~T}Y}_}Q}}b~l}b}[|c|Q}Žz|{G}[}k}i}q}R}b|a|_}B~J~B~s|q|I}}D}h|ہ|R}]}N}]}`}k}A~{5]|ƫ{λ{z||S}}|y|{k|߂|N}{4~:~=~c};~9~؜{{}J~O~U}{{{{{{|||||||{|{|z||{{{{{||~A~H~s}c~F~c}S}I}E~M~F~=~]~ɽY~Źc}N}L}|j}|V}ؕ|O}S~v1K~~||S|O}K}y|Y}[}V~ĸR~R~ûf}P}Ґ||`}Z}X}|j}L~ǺT~S~E~:=X~ʿa}o|||W}`}ȿl}Z~T}S}||_}O~a~h}|z|z|{{c}Z}|{J}R}|r|p}K~ʽV~S~;^}xC}q|{ۛ|?j~|~|{{{m|p|w|Ɇ|n|s|{|P}_}~I}|Ƿ]}vB}u|^}{ڿ{i|p|øc}R~M~G~J~b}_}{||I}N}ʾd}e}b}|V}|{{||{{{{{{º{{{{||{{{{{{}||{{|||{{||||{|l}v}]~R~O~[~[~݉}P~ʰ|³|||ۇ}i}Ӏ}փ}p}l}R~U~R~ޟ|p}||͕|P~P~Պ}ڇ}R~Y~٪|Ԉ}t}|w}J~|r}ބ}\}g~a~i~އ}e}}|m}}o}^~Ɔ}W~\~S~X~ǃ}|dž}X~X~¿||h}U~|}f}}}J~k~|~]~};|ֽ|j}}d}̭|i}||}u}L~_~[~X~f~Y~^~Q~Z~r~Ľg~O~_}l}}x}w}}}ʼn}v}}x}|\~~V~T~MLBEFJƽg~X~\~M~͈}}Ə}b~m~}{}ؐ}}|}Ҝ}љ}}}i}t}]}ܬ|ۣ|n}K~Ԉ}}s}`}}G~_}}}}Ƕ|}}_~Ї}|}q}͍}}¹w}ڕ}}f}}כ||v||t||||||{|{{m|||||[}֭|n}|||||{{||||||s}z9G~[}e}j~P~|y|}r~}||`}z}G~]}|x|~e|c}|n}||I~Ԅ}ס||׽|_}[}|x}k}||T~H^~^}ۏ}֮||||{f|V}a}}Ĺs}}Ɏ|r}\~l~s}J~Y}M~V};~d~s}c~c}:EJr~U~L~~I}|m}_}V}»l}{}M~w}e};~_}>~A~ɾa}Ⱦ[~u}l~ه}΂}z}_}I~=~d}c}پ{͑|Z}H}J}l}e}b~_}ʿu|e|]}L~E~b}e}l}h}]}|ݎ|W}g}{K~Ǐ|ߦ|}q||}{{z|~b}q|؏|o|ZFR~|z||{}V~|||{}p}p}j~\~^}||l~ÿ|z}٤|||||}|<~^}}x}|{|h}n}F~L~X~p}|Q~ʮ||߆}ׁ}|F\~O~ƽ||{ߗ|||žd~ľz}R~z|w}d}ǀ|X}~|g}~S}|~K}k}n~^}҈}N~}j~ۃ}]~Fj~K~l~Y~I~K~F~٥|p}P}A~O~P}Q}ч|Ϗ{d|Ĺh|N}z@~^|{^|TV}ք}^~N~U~h}zC}J~T~ȹK~1V~i}=~b}w|{O}9~|||]}q}C~i}J}y||o|R~I~|e}}n}u|ý|ȶ|{|Z~D~_}8M~c}5`~x5~y5~C~b}p|`}G~L~V}^}g}f}G~||||ڸ||||x}||Ҝ||D~E~@~T}{|_}D~E~>~KDQ}G~Z}:~W~V~?~ƷI~:~g}}|g}{Ɍ|o||M}g}J}³T~i}6~d}9~b}Ј|ć|o}x8~ǶX}Y}L}w}|{|{{|ܛ|h}>~E~j}Z}[}8~b}^}Z}O}F}Ǻm|[}F~s}|V}F~}\|Ƞ{`|]}³R}G}J}ͺN}{|u|ԿR}5~O}||V~B~N~>~G~@~=~P}s|޼{{{}|L}ŏ||w|¼|||~|t||W}_}l};~<~@~B~A~ƹP~U~S~L~G~D~ǾS~A~\}ݐ|p}w}Ļv}\~\~Z~D~K~FZ~:‰|~z~h}Ş|x|||}|n}c}|½|b{b|ŻZ}U}r|g|d}=~|<~Z~3:F~:~I}L}z|f|øx|}9~^}͸{{{{{{{{{{ƻ{{{|؅}y6~I~x8~||P}b}_}g}ȹX}E~>~h}ɾk}}?~}|׻{{Ŋ|X}E~\~I~̿Q~Q~L~I~r}u}G~O~83~E}J}Y}9~b}2~8~T}ǴE~ӽD~:~=~I}5~E~>~L~E~9~:~L}x|a|2~x0~x>}H}`}E~5~Y|O}F~E~H~<~I~l}m}^}ܭ{{]|d|l|h|p||D}xE}|ϱ{b}8~8~ȸd|c|y@}e|I}W}ƻ`}N}`}z9~D~6~a|?}<~e}s|{{{߸{t||Չ|V}Ǽ`}F~C~m}z|n|s|e|i|e}[~|~\~z|^|s|Ͻ{{ư{r}||||||{|{{{{{{|||||G~<~p}\~P~G~h}ϑ|p}=~n}R~I~o}|h}||||A~D~89νP~O~`}_|W|g|4~d}H}1~9~ƹM~q}B~|l|Ŀ{{{|u}I~S~E~^~7`~;X}M~o}c}Θ|^}]}}}d~M~y}N~t|V}[}Ƚf}L~~E~||ȹ{{X}W}|Վ|k|u|}L}a}E~F~Ͼc}s}w9~r|{{h}½w}{{{{{{{z{{{{{|A~||J}L~X~O~J~zA~q}@~]}W}f}_}V}S}j|֏|ɽd}|}f}r}~E~H~|B~h}f}|{{|ҳ|{{{|{{{{z||{{|||{{||||||||||{{|||||t}Ѥ|׃}݅}l}߲||ظ|k}Q~v~h~Ļt}f~ā}X}}||Ƹ||~}r}԰|K~W}l}G}~q~V~b}w}o}ԉ}À}X}}ۅ}|}t}^}g}}Y~z}N~|Р|}O~b~P~x}_~܌}ۊ}R~}}R~]~q~T~t}c~O~}w}{B~y}m}|ʐ|t}؁}}L~{D~»u}\}_~h}Ǽq}x}DŽ}}i}O~y}W}y}M~N~d}“|{|W}}l~f~ÿ|c}}t}}}}k}|Y~{~y~Is~w~y~r~u~k~`~}~|J~}}|||o}v}n}}k}u}}}}|||ʹ||a}z}e}ۂ}}}Z}j}|ڮ|~X}t}l}Žn}||p}o~ü}{j}}d}Y~}}}|A~Y~|~}i}`}|||{|{|}|ʣ||ț|]~|}|{|~~|ѥ||{||||||޸|||v||w||S}I~:7I~S}_}a}||||w}W~X~l}ļ||||g}x|{Q~}{o~ˇ}|Ȱ|l}Z~}x}¾|Ұ||r}y}m}^}R~Ȫ|h}l}k}g}|ď|n||zR}\}}]}܀}}Ǻi}]}j}ѿe}J}I}8~[}|a}e}7~L~P}g}x0I~T}|a}x|w|M~_~h}A~a};J~m}r}|i~P~y}JI~y}B~<~L}P}|O~>~`|{n||B~N~u}M}|>~L}E}k}d}˼[}[}ļ||ȿ||_}|p|Œ|||}|{{||j}ʒ}|{{k}l~ɵ|{Z}~|j~k}¤|ף|||c~}k}v}_}|½|x}P}w~ޒ}||k}c~L~¹d}{||Y}||ș|z}KJ~H~T}c}}M~d}}R~Q~}ч}q|O~|\}|G~D~I~^}}}zK}_|u|EI~|{r|n|e|M~Gs||ɮ|q~J~u}g~E?S~~?<A~s}9Q}W}l|[}`}ʊ|ʾp|S}{{c}^~|z|~Y|I~Z||n|ɳ{_}|s|ţ{yD}G~g}T}S~X~T~C9X}ˍ|D~M}V}]}{}j}F~[~y}||ˉ|y~N||g|b}}|||{T}z:~ƾ|~XǼq|};H}KO~M}H~V~ͿQ~`~7t}{;~H~a}ǐ||||||{q}fB|||H~N}€|Մ|m|w|n|u|P}A~y7~i}A~~<~p}^}|s||N~C~°Y}U}N}r|{i|{}|R~l}e}g}u5~ƸU}A~?~A~I~M~D~A~d}u~|ó^}O}~|R}ׁ||||y||6~?~F~L~B~d}g|h|{֮{۵{m|p|e|{lj|E~e~d~z}L}`}`}z1~=~3~<~L~V~S~9c~U~K~8~~6~_}~J}n|||n|l|v||zL}ތ|T}Ĺa}z6~˻K~Q~G~~7~P}s}E~?~Y}a}ƽe}@~S~L~?~T~_~y/ƻO~G~h}T}O}f}p}\~Hƿe~ۂ}zD~n||a}t|U}Y}Ƒ|}|ش||{{{{{{{}L~{|i}s}@~r}^}Z}|H}_}ǹM~\~B~[|i|]}D~P~K~ǻM~\~f}{{{{{{|y{|~|{||Ȥ|n|K}a}i}Z}ȹZ}P}P}t}C~?~?~f}T}{9~H~D~ւ|߹{v|E~y}R~;~Ļ_}}>~H~U~ͿT~L~c~]~d~>~]|{S}T~Q~2̽O~U~ϸB~L~8~t.~³U}z|h}F}M}5~¬=~9~;~ϽV}~3~;~7~;~V}@~E~>~_}ιN}4~?~ƽX~N~w}`}r}B~v}Z}{l|}=}ҾU}{@}ˤ{d}M}`}|U}Z}[}q|E}G}D}|y>~K~]}ž|Q}r}_};~]}=~M~j|{{{d||{Չ|=~E~9~{|k}P}p|б{]}}d~BJ~n}n||n}~Q}|{|ٚ|||Æ}||{{{{{{{|{||{z|]}e~R~T~g}H~F~p}l}[}Ԅ}K~k}p}K~r~d~r}f}I~h~a~S~V~J~C~I~\}~O}k}r}H~K~x}i}{|۵{ވ|D~b}f}[}nj|||<~F~˼T~_~g~HU}xO|ɐ{ʽ`}h}[}]}D~z}|||}x}]}m}ܽ|a}|F||~r|^}ȼc}g}z|{ȸ{o|̈|]}S~O~l}[}S}W}t}=~e}\}{{|{{ÿ{{{z{}y{{{|w{}f{ԫ{P}k}a|T}=~W}^}d}|_}c|P}I}j||h}u}}z}[}Z}_}h}e}n}Ι||}|{||{{{||{{}|||||||||||||||||||a}}g}}k}|t}ە}ºr}^}|Ӹ|΅}v~J~ޓ|Ӂ}|n~~Ӯ|||r}v}|h}ǚ||ʇ}|~X}Y~P~T~܌}}Gq~O~u}}N~o}DŽ}ǘ|֊}f}}}||Q~}|X}W~f}o}l~x}n}a}`~ڇ}|||t||X}|||||p}ޫ||~s|]~x~n~Y~Q~w~X~~E~d}L~̿~|t}|U~K~\}ÿ~}x|{|||Ę||}|||{ÿ{}ƽu}ľw}u}ýv}t}ȿx}}y~~~{~_~W~v}}]~}}t}}~R~|}|}ɍ}|||||ٿ|}Q~||||¶||{}T~q}ϥ|q}ң||b}a}_}|||Ɖ}w||||f}ձ||А}|l}~}؆}ýz}|||z}|||{||~|||m}J~o}||{{|m}w}ŏ|n|||||۾|v}i}|||||[~|6n}V}||V}˞||{H~O~}}Ȕ||q}|\}Ǿv}|}b}C\}s~K~]~^}ƿe~y}|b}o}j}|m}t}||u}}o~M}{~q}^~}q|^}y}}S}|ߘ|V}O~wI}|`}Ǹa}^}܈|ɿh}E~L~L~X}}:}d}ƹU}A~AY~V~L~P}||}|T}F~^~E~o}^~O~f}B~|B~U~L~ܙ|}F~V~LF~?~U}|B~D~H~_|ܤ{{|̙|@~?~h~o}؞|ڔ|{A~C~ͻ_}V}X}X}[}|||||Ɩ}|{||½y}|}Z~ƚ|m}|ݷ|o}m|{y|4~ɶB~9~?}ڪ{{{||a}w}|}||s}p}|c}}J~߲|O~I~J~w}B~ǽ^}b}}m}|b|Q~]}C{~c}ܦ|O}~l~||b}{6o}T~}}g}|e}j~ԍ}Λ|߰|̜{a|;TI~d~E~`}٧|F~F|k|q~}{{}Mf~\~:c~S~K~Z~E~V}d|r}n}{{q|r|q|z}D~}}Ր|{{`|{x|W}l|e}|}p}|ՙ||j}||||~Q}ș|N~z>~e}{B~}<~|L~=E`}n}l}Ef~W}9͸G˷[}ճ{Ȋ|ɋ|q|]}͋||ђ|ӊ||½c~c}|MV~P}X}T}|w|ȿc}ùb}M~µI~0Q~C~]}W}||g|M~O~~y|v|V}@~:~7~^}|{{|Κ|y>~o}}X~|C~ôQ~>~R}N}ɻ[}{0O~A~M~3K~\}||n}Ŷg}Q}||N}b}E~z5~K}|g}f}t}?~[~y-Q~H~A~9~c}g}D~<~>~S~R~|-M~{7~S}q|a|E}ʼo|r|f|{Z}l|F~=h~|D}h|g|ʮ{z|I}<~<~ĴB~7~9~e}Ľc}_}V}w||f}S}H}X}ɺZ}\}ʻ\}X}T}[}U}P~7y.Q~Q~w+35443I~=~Ǿb}h}>~x}|Y}_}V}m}B~ŷI~36S}Z}|h}o}m|u}yJ}`}Ź\}p}b}q|||ő|Y}]}Z}N~K}{u|ە|T}J~X~S~d}p}C~<~B~t}k};~`}G}Z}<~F~J~:~N~R~\~f}{{{mo|{{ƿ{M}f}õ\}V}Q}\}<~H~t}@~{9~=~d}q}C~<~d}Ȉ|N}Ⱥ\}|ƃ|y|ļw||{:~h}x}C~}=~R~E~=~o}P~Z~J~6~d}L}U}ʿN~I~7~:{4Q~M~G~ɸ\}ѼU}wB}k|{6~;~@~K~ʻI~<~y6~t1~ʸD~C~b}T}|2~G~,ðA~\}˸S}8~[}F~B~:~X}y|X}x}o}g|ý}}N~P}D~Q}o|{x||ޗ|[}k}}|ĵT}I}Z}øc}\}m}]}zE}j}O~G~|7~6~i|g|\}b}X}¾{{v|O}|<~<~:~{p|H}I~ɽS}{V}y||r}ظ{{{|r~o}S|{{{ť|խ|{{{{{{{{|||{{{||j}[~o~b~i~Z~W~{}}Q~x}|r}u}}Y~`~ùW~O~r}V~U~|8e~K~{;~<~d}@}R}p}A~G~g}Z}V}Q}~<~N}u||ùl}W~O~ǻY~V~P~v1Y~g}ĺf}m}÷a}r}}?~¹U~_}t|~}@H~}T}U~ł}K~p}m|x|||||e}[}\}K~k}Ⱦn|ʒ{{{|E~e~?~ξb}f}o}n}U}{{}}|{{{{{z{{{{{zx{{{{wO|s|G}B}G}\}U}i}?~=~]}h|v|Ә|q}ց}N~U~N~P}s|p||z|{{{{||{|{|||||||||||||||||ɴ|o}}c~܊}q}|p}t}p}|h}c}c}}n}h}}HT}Ɣ|n}L~c}W~ޜ}|܉}S~}Y~l}_}H~u|||p}^~Z}ȏ|e}P~}}˒|}}_}Ո}o}U~r}t|R~Q~i}L~}}ܛ|\}i}i}}^~}ŷ|ѣ|ש||n}|~|Ŀ}΄}u}·}R~}Ѱ|o~ݘ}n}i}x}¾{}ӈ}n~~g}m}ºd~W~R~X~Y~X}f~}u}H~|{ҭ|T~Lf~Žm}|`~u~c~x}m}j}t~ۂ~_~{}{I~o~^~͋}}W~}ӟ}||||j}|||—|̓}}|||;||||{z||||X~|}ģ|ץ|l}ê|d}y}|ȳ||e}Y~p}}k}|||G~}ün}|}E~f}y}֔}m}d}Χ|h}{}N~O~t}|||||||w|Z}{}t}||Ҡ|y}}}||h}_}^}Y~Ӈ}}[~X~v~~}S~z?~i}P}ý|||||Y}A~B~?~y>~r}P}ڛ|ˤ|a}}}D~~@~{?s~}k~Ȓ|\}Ľm}R}^}{|Ǯ{f}[}|ƛ|_}ϝ|p}Q~A~~G~^~`}}}|f}О||d}^}h}t|ʿe}|D~U}t}b}p}h}y?~Q~C~}D~V}|k|x9~U|{_{=}~5]~ɿh}r|||g}Ӝ|`}X~F~ɿ`}@~V}Y}G~A~U~;~I~Y~V~l}s}H~w||W~w~H~V}{{r|u|m|G}X~H~||ѫ|ƥ{|u}ʿ|͋|}٢|Ę||||||{{߻|ژ}|Ű{o|\}y|Ҡ{_|{{Ӏ|H}^};~A}B}{|K}r||{||||ڏ}h~h~O~V~U~|u}|v}_}P}I~j~|}|l}e|u~ǀ}~p~Բ|_|K~|}f}OY}b~}l|v}JR~]}R~Z~ǿr}|ϙ|hk~`|yR|{{ƹU~Q~O}j}F}}r~|}F~|B~}B~X~S}ȏ|n|N};~>dS@~A~ۊ|||g}C~t}[}|o}\}}Z}L~x|{{Žu|L}||{{{ܻ{{{~y|{£|y}S}M~}s}}}i}Ѥ||c}|P}l}b}|m|{{{c|T~d}`}SB~U}P~OH|ƿ|n}HW~s|M~}r|f|k}E~k}n}|J}|u|q|ƍ|s|ň|F~5|/a}U}?~Y}؊||p}F~||Ⱦv|j|ŷS}Z}R}O}ʂ|r|{|||ɧ|ҫ|թ|C~K~{}B~V}y:~=~^}ƸN~J~B~ĴG~:~B~Q~U}ƿ|D}A~}4~w4~h}@~7~z4~@~f}}|f|ƶZ}K~@~ӿH~I~L~\~7C~j}U}|r|c}9~{2~`}J}x|L}òQ}`}ɺT}¼|s|i|b|b|{{_|~|M}w1~W}W}]}i}C~ƸG~X}O}D~<~g}d}k}q}O}o||B~d}ʽ`}f}D~[~Z~F~C~ƻa}C~?~:~=~9~<~^~4Z~}<~b}¹e}@~z:~X}¸^}l}o}a}\}‚|؈||G}|Z}l||h}R}W}i}ľ}|{{}|m|||a}|}|{s|}>W|<~\}W}>~T~H~a}g}};~U~D~@~M~P~8|/z/U~R~H~z8~[}|{m|Ƈ|p|Ɍ|~N}W}P}9~=~]}<~}2~O}N}S}X}n}a}A~@~D~a~D~G~Y~Ԓ|ѥ{M}̻Y}b}e}L};~;~ǹb}{33̾V~M~?~?~F~:~:~<~h};~B~?~3X~=~öW}ɹU}Y};~;~H~W~>~³C~B~>~H~<~=~ԼA~K~F~A~ɶG~J~4~[}Y}Y}o|T}<~z3~7~R}J}}|Ӎ||}r}J}٦{^|DyJ}|җ|o||U}ٔ||q|̿{˿e}ĶZ}ي|M}Ǽt|N}g}j};~R}P}Y}Z}|c}l}f}e}<~[|c||]||Z}O}|y|g}U}f}H}|l|{{|{G~Ɲ|~>}[|p|{{{{{vx|{{{{{{|||Ԭ|b}Ļl}||||N~g~z~¼`~J~K~W}b~a~|Ǿ{~k||u}}P~\~K~]~M~`~b~N~|=~X~J~M}y|n}C~P~c}U}Q}q}@~Z}^}]}Q~I~B~E~O~A~E~F}I}a}i}?~F~p}W}Ē|{{|Ǔ|k}N~~}L~G~|{{|{{{{w|m|g}S}q}U~T~>~h|ׯzح{g|N}k}J~|{{{{{{{{{{{{{{{{{͈|]}j}F~R~]~K~}=~R~I~v}m}]}<~ʸK~E~e}ك||c}H~g~>V}{{{{{{{|||{|||||||}||||||||||l}܇}ۇ}|K~O~d~d~r}||||Ѯ|P~e~\}P~g}||v|}T~~[~|||}X~Ձ}W~d}}c}`}|||{|v|ď|j}f}Ķ|l}y~}m}V}n}|ô|٘}||}¼s}Y~Ŀ}v}s}f}ؚ}ͭ|}b~}||٣|n}r}|e}p}߂}M~P~Ct~||a}|g}}v~^~n}}G~s~z~J~I~N~n}Ř|{{¬{i}}}|v}v}}y}S~}}q}{||n}|^}||||t}^~\~Z~R~ߜ}՟}v}||||K~{}|–|{}K~M~n~u||||||||||v}o}[}`}ùj}q||||l}R~|G~Ӏ}x}t}|э}^}ž{}b}J~a}j}R}ِ|x}HRu~p}}|j}f}|z}||||Z}u}G~A~Q~|}||c}}h~|~||c~x}{J~V~[~k}|K~}V}ʘ||x|O}S}\~b~ؔ}?~l}U}~|b}R~I~}ļn}\}F~z}n}}p}z}|غ{a~t}||f~\~i}a}e}s}|}]}o}||{K~I~yB~|p|V}m|^}y~s|b}}}}}C~q}ؒ|s}P}\}|}E~v}l}d}`}Z}e}{|m|v}}G}_|}|Z~Vw~u|\}z6Y~ȻM~>~X}||}9~8~U~9>~M}W}Z}ŹR~E~v|{x|Y}L~>~O~}k}|{`}X}]}W}m|Q|w:}E~B~|}Ɂ}|x|{^|{s|{{{{{{|x}|_}i}M}|Y|ǯ{{|{{^|p|V|J}8~z0~{V|Ԩ{ڢ{_|z|Y|_}|E~`}||q~e~x|}m|a}U~]~\~x}Ý||{Ľ|Q~P~}|x~S~}c|V}|{|q|T~}]}ow~V}}Z}}|}y~x}{?~Q~O~|8~xD}|E~K}I~i}`~}ԁ}a~z~n}}W~_~`}}E~y}{Q}O~]~ôN~E~O~D~O}f}V~S~H~g}?~J~¼}m~Y~o}|o~x}{D@Lq}||k|C}^}{C}u||O~D~M~ȼQ~~A~B~n}U~X}r|||~i|Ԏ|n}P}{{{s|t|h|g}T}؁|S}zJ}»b~e~c}X}s}l}A~ߒ|||||D~B~ʻM~_}̿]}l}F~j}\}܉|g}{:~o}G~5A~^}R}S}U|>~2´G~@~w3~I~?~;]}b}{;~w;~v|p||||||||s|y|w}r~z|ɼd}~I}v|u|i}7~?~WXd~}F}O}~6~L~8~o}@~8~;~ӿF~E~5~f}9~ͼU}6~H~R~8~J~S~;~J}||Z}6~8~<~D~H~8~u/~~3~7~:~M}{A}v0~P}~|b|{C}w0~6~~2~6~v1~N}M}o|{S}A}ijM~g~B~|{{{i|{_|5~8~_}g}k}Y}V}b}7~;~n}ŸI~?~9~@~W}||}=~]~}3<731\~_~̿O~D~<~ƹK~T~q}~D~~@~}}e}k}e}o|N}{{d|z|v}Y}ê{{l|=~X~\~D~n}s}o}U~N~N~D~N~5514?>85^~_~A~|xx|{v|f}p}Źb}[}I~e}D~ñC~=~I}I}<~x5~˽^};~U~?~q}z:~>~;M~U~\~v}||y||Z}E~>~|\}K~n}j}V~S~G~I~>~D~J~H~J~D~h}C~>~R~@~;~z3~>~:~>~<~[~h~\~G~=~j}K~U~E~:~J~N~R~<~;~V}ƵQ}ɶg|X}Z}c}L}ɷU}9~=~_}S}q}T}X}~K}f}v}|D~Q}}P}o||v|g|_}΁|g||]}_}|e}:~X}p}k}l}N}܇|Y}W}Q}P}|F}Q}B}ݝ{|b|k|w|Q}p}U}~|s}A~Z}H}p}{v|U}||љ|w}^P~{͙{‚||{{{||{{{{|u}΁}||}}g~ٮ|{{||D~m~e~L~h}s}e}C~c~J~h}{{{ͯ|ľx}S~W~d~S~E~C~K~X~m}^}T}R}j}b}|}y?~|}C~C~N~>~M~B~F~`}o}U}|ی|[~úU~q}Z}t}H~F~E~G~i}{|T~f~ۅ}҇}V~P~t|{˽{|{{{{{~|ۼ{|a}d}@~W~4E~{g|{H}V}Ս|{{{{{{{{{{{{{{{{z{~{{e|C~Q~Q~^}Y}y}R~Y~E~o}Q}L}:~̻N~S~w:~\}J~{M}Y}n~H}~i|{˾{{{{{|{xz|||||||||ȼ|||||||q}a~k~w~n~i~f~P~S~\~ݗ}}t}q}P~܇}޴||g}w}|n}Ά}}||l}]~e}l}_}[~J~|ߎ}{}m}m}|{}|a}N~~I~i}„}f}|[}}}?~z~|F~U}˟|n}W~|܋}O~V~q}l~K~}\}||||Ԓ}Ӳ|y}υ}`}|}}v}}||ņ}}b}~~}||¾||H~M~|}{}b}}}_}ÿq~P~L~}l~N~|||n}z}π}|~a}{}}}S}|D~Z~T~||T~}v}w}z}x}s}r}||||n}ӛ||܇}a~q}||Í}Ѫ|_}|}|d}}H~}M~c~Q~Z~_~ۅ}||ҳ||k}}X~i}y}Ŀ}D~Q~b}Y}||s}|Ϫ|ɨ||К|Z}}ߒ}n}|||}|w}Ҵ|ۼ||}|¼u}=`~e}[}|s}|J~[}|V~M~\~}|e~X~|a}}}}w}o}M~x}f}X}e~N~g~TX~~U~?~@q~U~E~o~s}F~e~_~c~R~i}b}M~n~}|d}|u|}`~|k~t}s||ϥ||i|}V}{V}Ш|||Y~Ӯ|k}_~}v}]}R}o}Ƚm}R}X}h}H~A~~?~B~{}b}p}|e|Չ|T}C}R~M~ͻ\}˻Y}=~FIz:\~@~T~ķN~V}|}`~F~S}j|r|G}w|Z}Y}{é{E~c~X~<~|y|t|k|{{|n|԰{Ļb}w|{[||=~|}|Z|a|Z|^}R}P}P}|{{Ĵ{t|i~|ƽa}q|o|q|{{|k~n}|m}d}I}[}X}G}4~a|k|A}{٠{~^|{Ҵ||ԾH~D~`}n|ׅ}|ƒ|c}?WOX}||||o~h}||ˏ}}c}|{{{|d|M}^}r}IR}\~W~l}~c|Ř{ب{e|˺S}I~m}B~w3~||n|f|P~e}X~Ѕ}O~B~a~K~|z}j}~}N~}a}|}K~S~J~|||ۊ|]~H~s~G~c}|<~F~Y~}I}ۈ|h}e}k}݉|{r}p}Q~I~Ƚf|{4~i}e}K}e}L}i}\}X~AU~S~s}}}Q~c}X}y|B~x.M~L~C~n}ݏ|o|A~ûk}Ę|o}J~b~F~_}|v}a}U}h}W}[}|ݎ||`}@~H~Y~f~W}O}ǹU}y4~ŸW}>~Q~L~ǹY}c}ȹI~:~x5~H}҇|W~Q~F~|:~z1~b}^}R}c}\~>~F~Q}b}m}˹_}Z}~J}O}|w|~s||q|}T}~;|~U_}j|u}w5~Z}W}x}|7~b}R}ǹx|H}|s|}|}||<~;~~9~i}9~=~<~>~`~L~z8~k}@~A~N~B~λD~H~6~\}2~{1~5~5~B~<~|1~t0~λR}U}3~K}ʽs|G}|c||A}~C}N}zC}y|p|g||q}S~B}@~x}H~L~G~K~a~P~F~K~?~6~@~F~A~W}e}g}l}º`}M}X}~f|z|M}9~T~:~3y3}5G=;3Z~O~p}}H}|M}Ko~n}Z}}ܓ|β{û{{{{x{e}C~G~t}{|[~=C~m}T}W}@~[~Y~G~~8~N~q}O~x/24B6@h~z~e}|{m|J}_}O}W}ƶL~G~ȺM~_~d~R~p}^}K}¶e}~L}X}n|ü|u|R}ûf}_}}P}ƿ|u|n|P}\}j|Q}N}3~B~r}K~I~I~S~Q~8~O}ǹ\};~W~|<~i}}O~n~z44^~D~^~|0]~9~`}c|v2~V~T~K}L}=~{/3G~ϾJ~N~пH~H~;~?~4~9~W}?}A}[}O}v|Ͽ[}V~;~d}}:~؍|}N}`}{=~g}}g}{{ظ{{|_}W}Y}~|۹{o|m|و|}|R}q}k}d|h}p}}V}{||^}W}ъ|S}z|o|{t|π|a}G~|A~}|U}U}út|g}f}}|{yy|{{|t}ƫ|j}ÿ{{|||||r}Y}a|{{|||t}Ӷ|q}^~[~|{|Ѷ|K~U~û[~R~U~l~K~ƸP~J~M~S~E~d}|ã|]}u}|s}N~ļX~V}4~L~G~E~r}|\}}?~\}_}q}O~~53z3Z~a}H~x7~~|j|{h|{}}û]~i~ǽV~ދ|D~X~}=~X}|||Ŧ|Ľx}ʤ|k}Ŀp}ڗ|{{{{{{{{{{o|ƾ{e|b}v}C~=~]~E}f|_}q}M}f|{{{{{{|{{{{{{o{ɢzݿz{c|c}y=~H~¸Y~s}t}r}|}H~ɽ[~|>~K~{:~J}k}@~;~C~S~J~M}?~}}k|m|{|||||||||||||{|||||||{}v}ӌ}T~Q~t~Y~]~`~l~c~a~e~R~k}||g~S~|K~}ʺf}ĺt}^}~Q~||{q}݂}~}|v||z}ż`~]}t}o~K~h|Ú||}Y~n~}H~b}_}՜|}X}ſf~n~i}|Ŀ|ٶ|||}|o}}U~m}|}T~K~o}}}r}||}}|||}}R~}}M~މ}}g}R~H~W~i~t~i~}u~Z~F~T}~t}ܝ}\~L~n~Z~}}u|u|||q}r}j||i}t~֚|||~[}v}ѹ|q~R~|}ʍ}˃}e}q}Ȗ|c}^}|||β|ɭ|||||R~L~~}}٪|}o}\~I~J~z}z}l}x}z}~}j~ԋ}ߤ|Y~~J~|}c}~^}ܖ}ј}|l}R~}v}|d}z}|]}f}¿|||ѻ|}o}X~ߎ}}}Ts~T}|ĕ|V}z}s}{}o}R~ſk~ό}|m}XV~f}P~h~M~n}|R~k~úZe~V~V}f~t~j~m}Ʉ|}X~ʿv}k}J}z~K\IȂ~}x}}|M~p}F~t}{ϴ|¿|¼|\~u}{u|{|x}|{}|v|}ݟ|~s||}`}|W}\~י|u}|O}||h}|r|_}f|K~K~¶`}o}M}܍|~i{{؏|}s}V}W~F~U~q}@}H}j~J:c}@~S}u|k}z}ùc}t|u|v|?~J}{{{{l|u~JF~Z}x9~ˇ|ٻ{{ܸ{ʽ{{ܹ{{{f|B~}~|^}<~x3~k}O~=~f}R}]~u|۶{~e|s|X~շ||D~|{{{{{Б|{s|q|_|~V|ߦ{݂|P|Г{՝{k|;}~Q|{`||I}{{7~2t'>}\~|{|i}k}~~X~؅}֣|d~}}Y}{||{\|{ċ|l|J}B~~|z|[}>~Z}m|\|ýf}J~F}zG}M}ôT}ŴS}`};~ͽY}E~͹D~S~E}K}zF}k|i|x|}}||p||_}D`}|ڤ|k~Z~ĸp|ùU~q}\~v}Bu}k}l{Z|W}b}k}ۃ||]|KQt|~e|k}c}{|ɷ{{R~Ǿe~ݾ{[}νV}]}|y||f}C~t}>~Ҍ|w|q}ƽ_~y~Lw~M~R~\~=?~=~F~w}]}]}o}X~g}V}b}ù[}a~]~νJ~`}|x1ŸM~y5~c}8~O~V~y-B~e}=~@~Z~C~L~u(J~ʺH~|,N~c}T}e}}q|w}l}þ|V}P}g||m}a}?~Y~Z}h}I}z4~L~95>Z~k}ƺz|ߎ|{H}˽s|¹e}n}h|V}Ķ^}_}u|g|Ê|?~j}J~8~B~;~i}o|xG}}K}j|n|X}e}=~i}ĸc}X}Y}x|w|[}v}ɽb}P~ȻJ~C}I}||xC}_}M}D}Y}4~Z}X}[}I}S}>~_}|>}Q}Q}N}K}D}H}G}]}|3~j}[}ч|`}G~E~I~I~9@GJ@8>W~=~A~=~@~C~Q~H~<~G~k}4~S~z.H~~>~Y}:~P~N~Q~\~@~e|n|M~|w}o~v}r|i}J~H~|ƽm}j}{{ߵ{C~J~<~E~c}V}77_}G~F~÷M~|0˾L~P~@~V}H~t}`~ʾS~T~5W~?~q}p}[}|||~|ԅ|<~9~K~ǸX}N~}.`~f~e~Q~E~}T~a~m~Q~K~s}s}žf}x|׍|M}U}||Ƽ|P}{9~@~M}U}z7~<~F~E~>~c~E~~|a}:~Q~f~E~w=~c~;^~G~ǽV~X~C~Y~_~K~=~Y}z|l}D~ȼd}Z}h}K~[~˻O~H~@~9~F~óG~<~@~4~<~w|q|A}ɸW}{̮{r|w3~˾[}|ɻ[}|q}O~d~J~ˋ|i}|{{{z{e}~}}|Η|{q|Ӑ|{Ӻ{|e|x|y}y}D~q}Ži}s}U}d|~M}|{y|K}t|À|ψ|h}U~·[}|I}|H}r|{{{{®|{||{{{{{{|ԯ|n}}[}l}}g}ƥ|||ء||N~S~R~e}r}{³|~}O~X~}w}}R~Z~k~]~N~B~|}|{{y|||m}o}Y}f}g~O~D~»f}k}^}j}m}>~E~[~O~D~@~f}π|^}l}P}{{{p|\}zB~}¹^~~9@c~A~~I~v}݅}}}m}}||{{{{{{{{{{{Q}A~ŷo|I}@~;~<~µN~N}x{{b}J~A~y|{{{{{{||{{{{{{{{{Ҫ{|S~T~r}s}_}s}q}\}_}_}X~?~c}z6~Y}G~DC~m}D~|}O~N~|~|||{|||||||y|}||r}T~g}[}n}}Ž}}w}m}k}o}}}Z~Q~O~n}y}X~||||x}|V~z~b}X~b}ߎ}Dx~׏}y}||i~Б||a}y}э}x}ý}}}O~R~^}j||̝|¿||{||¾q~ڈ}|j}|޸|،}q~}|||h}n}m}}[~g~\~J~|}߳|W~Ŀ|}֢|{}||ڜ}w}z}~l~v~}L~ûq}׈}}}|g}b}K~~a~}@U}|P}||{|\~b~ك}̑|ɳ||{||f}L~[~Μ|p}U~޳||ԋ}r}Ā}}߉}N~|}f~}O~w}ƕ|}~T}||˼||b}܉}}}b}c}|j}}k~v}}}S~}~g~X~Q~L~r~k~t}x}J~]~k~a~»t}e}ٱ|n}|٫|ׁ}z}ȕ|l}}ކ}φ}||s}|p}u}|f}}Q~Ä}̓}[~b~f}|x}{}z}u}Q~Q~{}Ÿ|ڥ||||W~s}|t|{D~e}w|x|w}ün}͑|Ľ|c|i}?=~@~~|{||Z}W||v~Z~}}~}~s}|^}]}[}…}j}q}O~`}d}z}|d}~Z}Ƅ}|̉}|˻{{o~h}}|Ͱ{v~}΀}{}y~u}j}z}S}X}żl}R}_}T}}K}[}H}|ȼe}|F}~T}S}v|z|Z}V}V}V~p}i}}M~b}Q}L}U~{A~W}P}^}z<~n|d|ݿ{{{{{Ŕ}||k}_~}T~\~Ă|{k|͵{{{˹{v|w||H}q|K}N~ǸR}:~ǺW}^}b}j}L~=~b~b~ؠ|{{{}|||{{n|r|h|ֱ{{x|ߥ{{{g}s|{ē{͞{x|A}˺a|}s}@S~{ط{[|b|øf}U~X~M~f}Ļp|ͺ{|q}R~f}||d|Z|{B}f|<~A~H}e|n||;~7~a|:~I~S}ƻk|u|v4~J}R}b}A~C}^|{{z|=~0űD~z5~G}|w||]|ɝ{ӭ{Ҭ{Χ{{_|F~|D~zO|5~H~|z|Z}E~[~x}S~N~j}zzv|Q}݅|l|P}ƺO~Y~k~X}_|p|{{{||{Ę|{{{{|s}`~:H~:P~U~V~J~[~c~W}t||ˋ|D~||ȇ|z|}|}K}`}[}D~W~³H~μI~H}o}}3:68:7.N~2548A~T}U}i}Y}:~U~9~@~=~<~G~\}O~e~T~_}|y|U}i}J}W}|M}R}I}H}E}b}R}J}s|n|t}p}g}]}L~y}U}΂|U}w1~J}o|yF}S}}6~c}|Α|F~z9~a}R}h}w|{^}y|˼{Ć|k}||u}f}P~:~E~X}{ň|{X}V}:~a}a}g}\}>~x6~ȵO}8~H~E~B~z(*/ϿCEJE-;V}˾m|z|`|Z}ºU~g}Ľh}\}K}X}u}~?~B~^~p~R~I~O}O}h}5E7I~~:~;~i}^}^}w|K}M}b}ȻW}X}j}g}B~P}a}B~?~a}}s~J~ʹd}w~l~Ǽg}g}L~|}t}{{l|{u}b}>~H~A~m}G~5V~M}LJ|]}k}r}=~p}[}e}W~Y~[~]~U~a~H~q}W}C~i}ʞ|x}|y|}J}:~G~µN~J~K}e}g}ù]}p}e}`~m~=CB@F=Y~c~C~ĸS~C~T~úZ~S~C~?~G~g~_~a}8~͹I~C~M~J~N}L}?~G~P~J~J~X~_~a~D~E~};~T~K~R~^~X~_~r}S}q|]}d}s}^}w5~D~v-~2[~L~ͿJ~F}g|ȶS}P~f}]}]}\}~E}ƹk|||{\|ѻS}9~?~^}L}}|[~X~H~d}W}ь|đ||u|{||{v|||t|Ā|||d|ݓ|[}[}j}o}s}o}f}[}~F}_}{}N}Z|}F}m|\|u|d}H~^~J}c|m|W}S}g}T}{{{{{{{{{{{|ʹ||~N~V~M~T}]}}^}h}|}}t}}n}}Y~t}|m}}]}z}K~H~R~R~ƻT~T~Y~G~]~V~}h}{{{x|||r}E~G~~>~{}a}u}E~K~@~U~@S~C~@~|F}K}G}߄|h|{n|k|r|{|ٖ|F~\~_~J~K~\~̿V~M~}~C~a}r|{{{{{{{{{{{|||K}yF}|g}j}x}M~M~ʿZ~{|i|܇|j}m}c}{{{|{vv||Ƨ|e~a}{{{{|{{{{¿{}Q}W~_~{B~k}G~o}`}|l|K}n}>~|;~l}@~d~W~K}k}y}q}}||{|}~|{|z}Y~}|||p}ºc~}X~LC[~Z~o~V~M~Z~k}ĵ||ǹ||Y~a~ûj~}||{}z}}h}Ǩ|n}z}R~}}U~S~e~|@~{BES~s~|Ԛ||P~ڤ|q}v}b}\}q}h}ŋ}}ϓ}j~u}|t}|}͓}~Kp~}|ԧ|}_}|r}q}y}`~_~||}}a~j}^}^}~}_}ڡ|y}W~z~L~~ESWEB\~l~p~g}~}V~h}Q~Ն}ó||Dž}g~r}n|Ɨ|ԉ}|{o|{y|o}|W}ϵ||}}̟|l|}}e~|M~M~v}T}R~{}`}d}^~L~M~ʀ}g}a}t}χ}q}l}|T}W~~K~r}|u|[~[~T~a}R~Q~Z~}E~||~}[~k}o}܊}T}}{}n~t~}}Թ||{}ʖ|~}Ļn}j}g~}Q~h|ʼ|||b}}\}[~k}T~^~}}T~r}z}N~{?~I~W~L~I~ļ[~k~J~o}}V~}n}Ĺ|Ɔ}a}k}z}֋|X~`}ȫ{|֙|Z}@~`}||L}Ľt}~|Y}t}k}}|̕|\}Ԅ|O~}}||{{{{|||S~|ң|{}b}}z}|q}|ľ}r|[}ܜ|{h|ʋ||n}]~k}g~P~M~h}k}[}_}Q~X~S}J~}׃|N~ɽ]~Z}ɽ}|V}W}S}~T}|\}w}v:~K~W}M}T||¸n}u}e}[~}I}u||||r|{||||K~R}Z}_~}}G<{=~Ē|}|}|v|{t|ς|o}G~j}σ|o}T}O~Y~6a}?~=~L~<~_}}|||{|ʇ}|{wy||||r||||}|r|{i}V||{|s}x|a|y~}?~^Ri|{{{{|}@~9o|h|p|Ƀ}‹||v}O~b~üm}h~ʣ|||v|R}»|p}P~^~s~t}˅|O}|l|\}x3~`}\}B}]}H}||c}N}E}8~<~L~I~x|o|M}ǺZ}K}Ĺo|{͢{֫{i}|k}ߣ|j}h|F~K~q}݉}D~u~S}|NFq|{{{Щ{X}T~5GH2~,H~ͽV}Ⱦz|Y}P}{{{{{{{|{˱z{|||w|m}j~B7{6]~}3D~:~>~9~f}W}m}~K}L}j|{d|\};~;~}7~t|_|O~EF=y3?~g}g}_}9~d}N~ŵV}}|ņ|~|f}\}e}{-:.S~h}c;A~U~;~P}e}}Q}R}P}W}f}ŷU}C~5~F~6~y=}V}C}z7~B~E~M~ȹI~?~:~a}ʹ^}i|R}k}~6~a}[}U}ǀ|o||͔|s}~Ґ}f~\~g}T~|z|Ä|e}=~k}|x|ߗ|e}J};Y}J}I}u|O}A~S~/64u+M~S~x)P~w3~ʺS}v0~X}X}2~ƸK~\~S~Y}H}Dz?~<~ʹF~Q~w5~z}:yD~|}ź_}|}z}~A~W~B~P~z}}C~9Wh~<P~~9CJ;z-J~L~8~g}I}s|F};~A~=~B~o}m}b}||^}h}h}_}}ľ\~[}V~W~|||{|ޜ|K~F~E~M~F~A~Q~I~zG}^}_}f}t}m}C~L}ĸM~>8]~?~|q|{|n||t|x|||y}ɤ|_}K~}:~L}{p|K}ĸ^}K}:~˻I~R~Y~F~~;~B~J}d|q|J~L~]~^~I~C~H~@~A~f}U}>~{7~]}T}g}<~N~A~Z|B};~Q~F~S}ą|V}e}нM~K~N~]}A~P~ĹO~D~i~b~H~K}\}a}s}þn}}>~t}d}W~y.L~ͻH~9~÷Z}N}<~M~\}Z}5~?~=~[}׭{[|zA}B}6~<~ҾR}[}^}P~\~@~D~|o|{{{{}{{z{{{ւ||ܓ|R}|T}>~A~}>~V}R}Q}[}Y}\}u}j}Ϝ|c|צ{||K}ȿc}n}||^}Ǫ{{ʓ|`}u}|p|ȹzz{{{{{{{~|c}x}؃}c}s}Z~k~~}ށ}}}L~r}|c}o}G~V~||e}}@~F~E~I~S~}}G~ļ\~n~i~H~}R~a}|u|{~|{{zC~b~ŸS~|ˊ|t}¹g}P}R~X~V~V~E~A~=~@}I}Z}¶\}؆|ϴ{Ӱ{k|{r|S}[}E~øT~s}|E}H}U}|f}F~T~A~d}e}]}¼||{{||t|{||e}[}T}^}z}|q}K~¹h}b}x:~M~R~B~b}H~\}{|}}ʷ{|t}ʺ{W~a~Z}{{p}G~{{{{|v}E~N~T~ž_~P~B~~@~||q||n}ƾg}x}C~\~C~v}M}m}~K~m||||{d|c}M~Ə|||a}w}w}B~e~s~Jo~Gv~O~}üd~`~ɍ}|ӯ|M~P~X~Z~r}щ}¾}U~ƽc}߈}w~]}|O}f~C~r}o}e~Hp~LJs~Hݒ}u~a~l~Cq~v~E¼n~z~PbaRHr~~@ICKC^~V~{~Ђ}ـ}k~p~r~|B}AJ|~Dq}ĸ|›|̋|y}~G~\~j}p~u~v~EE{=Ǿd~t~H}~w~j~c~R~U~e~Z~Ӊ}w}Ȧ|̤|Ƣ|}́}T~t}|U~j~|}ć}|ĉ}Z~o~n~N~||r}ث|m}S}d||}|k}ȅ}ÿ}|zS}xA~K~n}e}{}l}[}}^}N~u}b}{}ڇ}҂}}M~z~M¾|~Z~W~}ȅ}Ԑ}}]}q}ņ}ҙ|k|]}^}|}ت|y}i}|߰|Ѷ||x|P~}}M~n}{ք}}|_}|Q~݉}y}Z}n}|x}||r~M~~e~c~P~L~_}|`}߇}}}ʠ|||v}a~N~s}c~żx}n}}~}|||֝|K~m~}ɽc}d}z<~ڑ||||a}}q}\}t||g|~|׃|{{{{|{{||{\}|k}|m|`}}J~e}||v}}|o}E~}U}M~d}V~Q~o}m}q|||y}n~ς}k|:~R~X}}X~Y~оs|{L}O}S}K}{|ɺl}q}=~@~}q}N~}}N~P~|I~R~?~j}W}]}p|{|T}d}@~V~W~f}o|]}f}8~5A~v}B~ʽO~B~W}U}|f}h}R~n}i}]~S}ǺO~\}y|7~h}W}ƹj|\|y?~p}||tv|{|{{||}|{||||{X~c}\}{ȋ}k~r}|U}ǿk}FRb}c}ұ{|M}O~q~{R~S}X~}E~Ch~C~}|{x|{|||Ձ|I}{F}i}|}@B{л{p|L}U}C~=~9~b}O}6~ʸV}ɽq|٣{c}ɷD~:~B~>~{4~7~P}x|k|h|p|~Z|ܯ{Ǩ{Ҭ{p|ʁ|w~P`~s}P}C~G]}{|z~X~t}Aq~~B~}|L}K~|A~Y~Im~k~b}=El}a}R}{5^}|||ι{{{z|||{{w|{|úZ~>J6·M7U~M~ųE~{4~Q~N~B~H~8~j|h|r|J}ǺM~C~N}h}]}K~;ɿe}>̻K~@~L~y.2448w,;~z5~ʻT}L}c|I}7~X~^~d~n|>1F~7~̽V}ŶU}݂|l|k|ā|<~g}H}|K}<~8~^}R}C~A~G~Q~˻J~˸B~?~<~ǵE~?~k|Ƭ{{|[}ʺK~@~h}W}Ԓ|Q}}M~|^~z2\Nd}{}|_}?~Z~T~;~B~L~5.E~:~5~6~;~T~22N~=~ĴS}µT}J~T~G~y1~ͺP}B}H}~|E}_}w0~?~T~2W~W~7R~S~O~K~x}q}m}K~ƻY~Q~J~Q~c~d~>Hv~`~}[}f~R~N~K~ȾQ~Y~8z-,/Y~W~|2z:~A~I~|{|||d}V}e}z}}|a}{{ɡ|n}S~78R~Z~d~]~54\~X}P}X}ȼL~6]~37:M~[}t|{s|{|w|||R}ڎ|q|Ԇ|||o|֍|¼|k|u|o||J}V}R~N~>~ŶN~n}ψ|g|b}ʻ{{{d|?~O~[}|~A~=~;~z=~F~A~U~ϺG~]}e}I~8A~}8~w|W}C~y>~E~;~̺I~D~D~F~m}}I}D~d~?Sh~;E~m}m}o}I}c}j}~A~P~@~k}J~F~9~`}6~E}K};~_}Q}N}i}N}f|ּ{޼{Ż||b|t|Y}9~Ǻ\}p}{<~ɿg}F~W~o}`}|r|{{{{{z{{ъ|n}^}|||L}^}h}>~:~{;~C~i}zA}݀|X}m}|W}|H}||΃|Z}d}»h}`}V}|{{r||x|||[}g}N}|X}{{{{{t|X}V}Q}|o}P~L~}e}f}l}_}_}O}zJ}F~n~}X}S}l}b~P~t}[}g}q}q}y}h~Z~m}ɑ|X}|U}Z}^}h}W}~|c}e}||l}C~A~O~w3]~;~e}m}ξQ~l}c}o}ȼ`}d}j|{а{u|{¿{½{|;~`}[}{Ȳ{{ι{T}x}f}|\}}{D~}B~ހ}}|ȅ}}|l}X}m|d|Z}ӕ|}v}{|{e}R}|i}?~};~F~d}?~A~K}g|{Ű{|{{V}[}E~{{|Y~{{{y|ƒ|w|̔|}j~k~K~Ĺb}U}Ձ||h}t}H~F~_~?~}{D}|||ʂ}O~w}||||u|{{|}a~Z~D~_}‹|U}i}S~h~FOKh~a~S~m~s~S~|U~ƽc~Q~O~h}|{z}~U}H~}U}w||y}j~z~z~z~~^~UbpRX_^THv~~`~|}~Fr~d}t}ω}]~]~`~E_ZNr~HPLv~x~KQRP~u}|}p~}=z~x~y~x~n~EGJw~=D|@H}l}Z~\~m~j~Z~_~i~|}]}|c}Ϟ|Ƽ|}|ʑ|P~K~`~Ž{}||j}n}{||g}W}}`}S}H~^~||t}{}փ}}}F~Y~G~}X~~I~s}}f}þy}q}ӗ|h}q}i~÷T~z<~h}k}}|{|ɛ|b}]}}Ԅ}Ӈ}k}V}|||ϼ|_}\~~p~z}||||R}p~}L}[}q}{n||θ{͊|n}խ|z|}n}|q~~~}q~}׍}O~d}ǿ|Bc~u0W~b~e~}h~n~`~P~B~L~[~X~i}|||J~~}J~J~݀}Ƅ|Z}˿`}ĸa}W}ć|~LK~U~d}|Z|O}w}}P~Ľn}W}x|{{|||{||{|ġ|{ި|r}|||{|~n||L}t~h~v}g}|a}a|[}Ҷ{p|{G~J~y}e}r~g}a}~?a}|b}N~H}y}ό|ʾ||]}{|i}L~?~=~ñ^}I~zH~Y~P~r}b}?~g}~|h}I~X~K~I~^}h}I~C~Z}v|~K}v}H~h}|f~OG~R~ĵL~k}ю|X}M}e}i|n}K~p}~}G~|}E}{t|z|9~H~4H}{|{|||||~||}|vx||g|O}{{{M}y}۽|||o|j|o|M~\q~P~}o~~}A~ɽX~P~Ļa~ǎ|v~[~¹c}|ˆ|X}A~k}a|{{>}{̣{{r|˵|}}s}{s||ç{Җ{9~<~IJU}{4~dzF~̻W}}|B}f}c}C}J}{3R~{U}|ȷU}L}L}8~P}R}N}i|n|a|~^|c|q|ǻq|U|\~|հ{h|M}=}l}V}}J}}|l||t|µL~`~x.M}L}|_}||s|ļZ~K<~8~G~/J~|{|{{Ϩ{{{{{{{{{||̯{|~b||O~}8z28~15~=~|4~5~>~P}a}J}d}w|r|F~X~s}^}n}S}I~Bs}ɾOY~F~U~B~I~7~C~A~b}e}h}8~S}_|||5~{4~Y|B~c~4d}@~9~7~νF~~5~H}k}C~{7~F}`|Ⱦ||v|ց|||q|Y}|8~I~]~P~u*P~|5~<~8~d}c}Y}>~<~^}Y}s|u|~K}\}O}r|}j|þn}c~\~L`~o~~}[~p}t}I~H~^}À|I}w5~L~M~M~u*O~=~M}H}O}E~P~?~T}|m|V}<~;~7~V}x@}L}<~A~T~~13F~\~\~u~W~|C~f}y}F~g~|7V~d~Y~J~|=MO@=m~Vr~m}T~=N}T~k}Q~<8;e~|u|k}|;~{{Ŕ|}ڷ{^}}߆}s}]}ή|i}x|~}[~D~A~K~I~Q~̈|[}<~v|{l||O}ȻM~z3o}F~Z}ƽg}|ф|g}ώ|}N}P}S}Ӈ||P}c}S}:~P}ߓ||b}y|u|L}e}>~P~=~<~̿]}d}X}R}[}t}߉~}{{{Y}d}ԕ||L}|;~f}i}_}q}i}ùd}S}?~G~|<~@~I~a}u|Z}B~H~A~J~|J}K}ޅ|u|P~^~S~;~v}U~G~{;~H~j}S}p}s}M~K~{}d}p}A~D~Z}d}:~?~H~=~c}E}O}|Q}N}L}X}||I}h}d}ʀ|i}q}t}v}?~N~ɿe}Ւ|¾{ͽ{{{{{{g|e}X~Y~X}z|׳{|úf}H~G~U~J~e}M}N}v||Y}`}Ƽe}U}M}K}o}ɻM~{9~T}{|Ў|y|l|x|Ѽ{ſ{{{{[}L~k~T~S}t|p{{{{͜{{|ݗ|e}]}r}o}e}|x||n}ȿX}e|~9}n}t}Ļe}g}z}F~M~~I~J~`}s}{}}X}B~M~h}F~x}[}o}}D~F~y}t}u}]}٭|}N~N~b~;99~|h}E~:~|÷`}f}^}X}U}y|̇|N}|~;~d}R}}9~|9~e|{|Z}ż~|||O}T}p|R}{L}ړ||Ę|]}֞||^}~}ºl}|J}g|}H}|ʕ|[}R}{|]}z}|T}d}g}i}Ĺ\}{8~w7~n}:~j}z;~h}G}V}W}g}z;~r}A~?~W}i|I}Q}q|Ɛ{u{z{{Ǝ|G~S~e}[}b|~|w|r|¿|׈}T~d~Z}s~Zj|P}V}N}K~?K~ϧ||u}R~||ְ|o}a~Z~I~j}v}{}I~^~8Cc^i~w~a~}Ͽ|߰|p}q~p~||||||e}Q}v}Z}p}~Mـ}o}W~^~ý}|M~W~ļa~u~j~π}}{~~@F_~^~Nv~d~AL»[Mk~ſp~R~Y~l~~~[gj]WVjes~m~MXFU~ľe~UNAMK|AS~}N~Ļ^~M~[~K~^~d~>¹`~^~@M{?x~]~U~m~r}ڰ||ê|~^}m}~}}X~l}|`}f~d~Q~v}o}X~{}j}|}~R~v}Կ|}|Žr}}b}n}߄}U~}̓}݉}|y||M~Y}`}|ˁ|`}·b}F~I~f}V}~|v||}V~]|}l}}~}\~҆}\~Žz}e}}t}k}]}^}Й|Z}܋}~Z~j~j}÷i|]}~S}|~}s}X}G~u}|_|{¥{m}m~‘|~n|{Ҍ}|w}b~Y~E~W~y=~f}y;~p}==H~B~l}o}G~G~n}|}u}m}\~r}S~`}X}g}z>~p}J~žd~_~V~n}J~n}y}|f|l}@~j}B~^}s|S}F~J~W~|q||l|||{||{{||֜||^}ż|}]|_~g~}|ݢ|z|O~b}Ȼ|ڱ{{E~l|y}a~^~b}H}Y}K~|I~v~Q~f}O~Y~t}Z}s}}Z}~L}}H~U~K~}F~P~Z~J~w7~g}}C}I}~d|L}a}K}q|Q}O}F~J~e}|q}i~f~M~P~|[}e}f}O}J}o|{|||\}T}g}@~øY}Q}Y}|ޜ||K}[}k|{{|{~|||{||vw||~u|Ҟ{u|ɿy}Hd~{{{{V|{̏|ۊ}σ}z~z}c~U~I}ɿ^~L~e}L~L~|{C}x|a~V~T~D~L}{{{||{l}u|ȼ{{{ž{|U|7~s'@~F}@~͸G~P}]|c|ӱ{ڥ{E}Q~=U~`}|p|{{o|޶{{ͯ{{{|_|Żq|k||a|ң{ǘ{{l|{|YG~ſ|{֞{i|ôQ}w=}\|~Z||U~?~T}̿b}T}y||t|Ե{}GǾS}7A;1C~|}^|s{~z{{{{{{||{k}T~a}h}Ŋ|Ǽn}W}U~h~k~A~>~h}b~j~úw|=~|7~R}f||e}[}Y}~O}{}]~l~y}y|t}n~84I~>~2.N~N~d}E~r}<~~9~T}D}w|ց|k|A~6~<~нF~I~6~;~@~9~A~53G~|p|u|V}Q~~<~ƹ_}@~I~Y~A~?~ȶG~5~B~W~G~̾Y}K~O~A~ĵN~9~G}||±\}M}ƾ|r|W}||ʚ|e}^~z~s}Y~r}q}|i}ŴH~<~K}n|~8~A~@~z5~ʼY}h}:~<~A~;~=~8~K~~-L~a}W}R~x-V~1X~M~R~Z~Y~9K>ƽa~f~K~H~R~O~N~`~\~u~|;=z?~X~Ƽ]~M~ʽb}Ľi}|}Q~Z~>d}e~=u~x}~}X~C^~V~XdP}E~CQo~m}K~^}||{{}K~~}N~K~P~y}Z}E~J~|{|Y}i}k}d}h}>~c}F~E~{6~:~7~źO~n}݌|j}A~P~?~Q~]~?~=~H~;~k|yH}~J}y|p|P}^}܀|n|Q};~T}S}̻`}N}{{a}ٝ|{{۟|`}f}w|||{{½|X}Z}@~C~u}T}o}~R}|˾`}f}|D}R}O}f}B~B~x||f}e}i}~4~=~P~I~{+v/P~y8~F~X~a}o}d}\}s}}E~a~S~ûU~I~;~w,M~C~M~9~i}yD}~|j}f}U}R}h}M}Y}V}~;~l}h}i}S}U}x}U~h}|{{׸{ʰ{{{ݽ{g|˿|t|ȑ|՚||˝|Z}]}̾g}g}k}Ʒ_}q}A~n}j}M~@~q|_||6~:~ѽW}>~i}c}<~N}o|l|ƷS}Ƚr|ߴ{{{{r|B~T~G~G~f}V}ה|{z{{o|o|{{{|Z}Д||[}f}H}?}z|W|w{Q||U}<~s}e}@~z<~t}T~v}m}m}j}n}t}Q~W~g~D~d}}[~}X~L~p}_}O}}S}_~y2B~Z}b}M}y|Q}N}W}j||[}N}|{s|h}c}Y}r}B~l}||żf}n}O}z||d}x}`}|Z}z|{{Y}|}x}a}F~D}B}R}Ғ|}|v|{Q}s}p}Ļb}}|O|d|v|~K}O}f}N}|{H}S}a}f}}?~@~|M}n}A~`~F~`}|͊|t7~;~^}ǽo|Q}r}B~h~a~R~p~r}b}V}s}K}i}ҟ|t{g}Fu}}L~O}wA~s~QI~Žt}||||S~]~E~k~g~E~v}}P~O~e~|6@Tj>c~~}}_~m~t~{f}}i}||ȴ|||֪|P~U}|~|t}r~[~e~`HZ~a~_~T~f}|R~MRLRZYZ~n~@C~c~m~o~Dd~b~X~`~r~y~}ºe~Iv~HEJPBo~a~=Bj~RMBNBp~e~|Ǥ||f}˷|r}X~{<Jv~`~_~p~GMXaNSaCp~NQKq~ľl~W~k~|C~b~y}Ƈ}o}k}}Z~Z~k~O~`~_~y~o}L~q}V}e}|}}|}|a}}u}|׃}l}t}c}n||{̮{|}Z~d~U~}[~Ei~]}t}I~{H~O~Q~þl~~H~ԁ}V~}^~~HW~Y}ǂ}z}؁~Z~»k~^~փ}|R}K~¹Z~]~CO~|{{j}S~n}ޟ|e}x}p}O~w}}|ژ|s}v}|9>_~h}|||]}Żi}r}r}D~o}h~P~y}S~}AX~N~J~n}L~j~Q~l~z}|E~]~}}}ļe}p}s}[}c}e}[}c}L}}|~k|}|Ό|^}–||||{{wx||||{;i~S~ˌ|G~΂}ӝ||r}[}y?~o}e~|l|}q}U~Jɂ}R~||W}X~}@~k}_}V~aLk~L~^}O}s|xA~k~}D~C~H~m}l}Y~S~C~ĵN~E~E~V}n|g|Ϣ{Ǜ{ן{>}N}?~E~O}g}\~ǽV~=T}ɞ||{l}R};~ŸW}}J}|y||I}R}S};~Z~:~8~C}_|z{a}L~||||||{|st||{}}P~}}R}^|d|k|{Τ{s|L~u}_~g}u~O~|q}G~3P~w4~<~8~F}f}9~J}^}[}w=}h|||{|m|ڳ{ذ{u|||{{U|7~ȸQ}̻P}ɷN}V}ѿT}ҿR}6~v<}r|νV};~C}d|{ĵG~|*w(C}n|U}H}{{ɭ{¬{{R}=~G}ۇ|F}e}|g|ݩ{{Ŀg~٥|p|k}\}źh|R}o|´Q}<~5~r|O}P~;~?~|>~m}g}|e|ѥ{ޝ{_~\}f|zB}|}N~h}μj}X}|ޝ|Ѝ|Ɠ|k|k|{o|m|{o|f}Q~X~l}g}h}z~HDZ~v6~n}c}O}m|I~ȾS~=~þ`~[}D~f~s}Ŀ|c}\}W}ɾV~[~?~j~k~ŻR~K~:~N~R~M~]~;~H~D~R~·P~f~~:~E}y|X}˺K~°F~Q~S~=~y3~@~>~@~A~9~4<L~=~Z}z|úb}?~F~;~V}]~S~D~>~R}g|b}\~P~F~d}^}K~³J~a}M}d}{9~F~e}k}?~w9~^}Y}إ|Л|||||z|x|x|i}Q~9~R}y7~F~w4~T}8~?~B~}2~²Q}6~ñA~F~>~A~@~5~a}ѾT}F~Ǵ@~7~;~F~^~25:b~[~Q~~B~Q~_~L~S~[~¹^~?e~k}´z|\~f~}A~H~~}e~dUz7:[~v}K}ψ|{|v}i||ی|o~x~}ښ||ڙ|||x}[}Ա||}|z||}~|~||׶|ľt}}D~R~e~M~d}v}t~g~||@~S~ɽJ>68=x.Y~0B~Q~}/V~I~T~y/U~T~64B~H~V~>~ƻ^}i}]}X}R};~<~c}d|G}d}N}|z{|{{{{l|g}_}{|{{s|N}\}̻Y}@~F~V}v7~D~Z~Ⱦe}н^}C~a}i|H}[}k}h}m}]}H~[~R~;>x-9~>~K~B~}:~i};~D}P}J}t}d}N~]~ƾW~R~J~@~L~]}V}K}|r|||m|ùx|e|}[|Q}r}c}V}c}n}b}[}a}m}R}R~<P~Q}R}|o|Į{{{{{w|ј|u}zD~s}]}u}C~m}@~C~G~`}c}G~~8~B~̸G~G~܇|{ɾy|V}X}L}̻Y}^}5~[}L}{x|f|g|{~|n|i|p|Q}_}s}a~X~T}ӿ|{z{{{{{{{w|~|ْ|ɿe}p}Z}|ˊ|O}6~{1}q||q}o}j}@~j}e}A~R~ǼQ~R}x||@~F~|D~T~H~f}j}\}l}żd}d~R~{:~V}k|{`|o|w||Q}M~r|T}|{{{T}W}}N}|Ż{{{}||ķe}H~o|z|ľ|ӓ|b}_}h}Z}z|s}ՙ|{{{{a|ױ{ߘ|d}q|P}ƾe}χ|k|Ų{υ||I}s}`}q}{:~Q}j|k|ˏ{v|||l||M}[}U}¼~|s||{ʌ|\}C~ŶQ~E~y?~f}g}||i}?~{<~|=~A~q~{;~<~e}c}]}T}w}ža~q~f}h}||||||E~W~u~e~M~f}||m}JGl~:}9J~G~T~I~c~ȾY~_~AMYE?T~p}x}Z~P~b}̾|m}m}̼||}}|||||ñ|]}ʧ|u}b~h~c}_~w~|?}~m~x~~MCCſ\Ns~CF~W~u}`~~Hg~n~a~x~PY{~}~Ō}|n}f}|Ӑ}U~u}|{}}s}X~U~c~~B|Ap~T~p~PZHRt~Ȁ}׏}c~{~Q}@{~F?z9q~w~c~b~W~V~R~JLRbbbSR{=Y~W~Nl¼_QUXXX\~O~V~^}}M~N~s}|}z~x}|l|e}}I~e~V~w}}g}||W~V~f}Z~`~¾w}X}ݜ|f}x}Y~m~T~R~v~d}I~N~|||{{{{|ϴ||{ù{{O}}|||z||ſ||q||||}J~»c~s~l~J~y||p}ą|x}Q}V}k}b}||P}}s}S~Y}ّ|}W~ʂ}O~_}|`}ք}e~B~y~^~O~|þ{}Љ}P}}F~B~z}g}_}d}R}͎|[}|||ί{{{v|\~؈}~|{||||}C~s}X}̇|Ȑ|`}Y}|ϣ|S}j}\}F~O~|q|ſe~f}l|s|y;~m|U}E~s}|N}Y}|~P}ۂ}k}k}b}|a|Ⱥ|yL}|}^}y}M~N~E~Q~A~ȹ_}`}ʽ[}I}9~X~A~e}LJ|w|¶Z}=~C~=~x?}=~v*>~A~{8~k|r|{{i|b|Y|O}P}G}A~G~e}m|||C~b}y|Y|y|{z{||{||uw|{|||ܴ|||ٖ}`~T~۟|t}{{ߤ{{}a|P~\~}GJ~X~<~G~I~[~Z}¹t|d}l}Q~:M~4~M}7~g|̚{L}{6~e|N}|\|ը{{[}U~b}{u{{Ț{M}6~8~D}\}ǸS}ԝ{H}x?}6~~3~I}a|A}-5~z@}O}8~v2~P}Q}¶p|g|]|f|O}9~P}f||x|I}ĝ{{{{{Ƭ{{{|9~?~C~L~b}f}7~ʺY}|T}w|||I}q|s|Ǖ|̿o}\}R}¶r}f}a}_}|wG}}|V}Δ|{ӑ|a}x||b}^}Q}~|M~}|;~;w}]}O}{<~A~t7~\}?~B~\}W}d}D~H~Ĺc}q}V~c~T~W~Q~¹R~K~U}Z}}7CM7.S~y-;O~8~y7~Q}P~GN~A~D~s~b~0\}m|U};~f}u|F}D}w|c}b}[~3f~B~‹|~|k|o|l|ސ|W}=~{0v+U~>~D~?~r}o~\~h~7O~ŵE~ȹI~Q~G~ۀ}J~`}v}ɼR~J~m}_}A~y7~c}`}`}w3~T}Q}z6~C~<~M}S};~`}O}R}b}:~;~a}_}}G}ý{|Ľ~|||r|p|a|[}J~]~Y~U~O~r~U~O~Ÿ\~ŷZ~Y~o~Z~S~^~d~O~}m~Y}{d}ߘ|e}?~F_~N}L}W}Ľl}|X}O};~B}ûX}M~n~<V~|~}ٔ|}M~~^}}Z}w}x|{{{z{|||Ɛ|P}L}}8~L~{=~h}j}x}Z~ſ_~q||s}D~G~<~^}S}N}P}_}h}_}:~H~E~ĺT~G~J~}L}ɾ_}J~I~R~J~k|L};~9~f}<~R~O~D~?~<~;~C~^}{{{{{{k|Ё}\}||{|º]}_}йS}5~:~=~;~D~ĴE~?~>~ƶG~J~~1G~:~G~z:~k}ʸI~6z1RC<6}1H~~:~v}|r|{|l}}T}w}c}A~ƽV~a~R~[~d~W~V}{{{{ý{{{{{s|a}<~F~E~;~u}ǿf}P}y|i|{|O}D~L}e}ȹ_}ô\}]}N}Ļ`}k||W~{|R}N}z<~J~P~H~k}W}H~C~c}I}W}C}ʸS}|2~G}s|R}~}W|ĺu|[}\}^}ż|{j|Y}[}T}c|w|3~;~W}}|÷Z}~7~`}ƻP~I~F~S}|zz{zzzz{п{{|V}W}k}W}X}c}K~e}|i}j}|Q~U~O~N~W~^~H~ø^}U~b}H~`}p}b~T~P~ĺa}j}z}ƽb}|>~G~Q~O~q}|||s|s|P~|9o|f|Y|٠{k|ھ{{¾{{ι{{{{{ɵ{{˱{{ð{{˶{Ĺ{ʺ{|s|۸{m|{{{{{{{{د{{||Ї|h||҉|ˇ|T}J}X}\}Ƽ^}X}Z}O}O}X}~|Ʈ{k||Y}a}s}p}H}o}^}}}n}|I}]}}I~p}n}|q}J~J~J~w}_~CJ~Y}Z}t}f}g}v}e~c~U|g|||[}}x}m~}[}n}I~J~|{|}}]~_~I~J~G~U~J~P~N~h~a~]~f~[~t~{=Dh~j~HƽX^UGي}|||o~|~b~t~ķ|u}||||Ʌ}̌}||L~Lk~}֒}Q~}Q~O~M~}[~f~v~DHU}~y~R~Y~ľg~u~\~c}k}}}u}}}}}P~c~B|~v~v~}y}c}|Ɖ}k~}I~e~{~s~V~ƿd~^~X~T~_~FfRP_PJFu~t~[~L~}Q~}O~Q~i}|l~v~TVP`~]~}]~p~EOXaWNž_dNj~X}p}s}`~|˦||ҁ}}n}T~k~q~|}{H~}Ո}S}ļo}l}}܅}U~Q~b~^~V~p~}z}{}U~q}|}U~q}ކ}V}~|}N~||}g}Q~ƿw}i}j}¶{}|{||[~ȾY~y@~ý|U~|{|i}܂}̈́}|ڠ|p|X~Ft~X~Z~p~P~|f~]~M~¸d}Ғ|t|x|e}S~~}q}~}Ђ}С|||u}<<E~Ւ|M}u|c|أ{{{t|}L}L}T}W}Z}O}d}v}ſu}|v|ǖ|||M}U}˄|ԉ|ږ||{DZ|{{|{¸m}|s|y||v|P~]~v|ɤ|]}G~{}V}}j}p}A~|B~~R}c|k|R}i~a~}C~|w||ٻ{z}Y}Ħ{|ґ|n|p|Ĵi|L}U}`}Y}L}ķe}g}q}U}K}P}B}R}E~7F~|V}{<~Y}|O}X}7~:~ôQ}G}7~Z}Z}S}T}j}m|ºn|I}|C}q}S~T~U}w|{|w|đ{|{|j|{{||{{}~|||||}W}|¼||||l}w~U~C~d|Qa~d}{}U}T}]~S~f}b~q}b}@~9~L}Y|}|X}I~e~1D}u|G}|Y|m}O~ީ{{i|D}D}˺F~]}b}N}õI~;~D}˔{x{zʰ{ɣ{{Z~F~̖|{|v{zR|]|{x|/<~C}||]|j|}7~9~B}ժ{{r|W}X~?~:~}2~z2~O}_|ߴ{{ȴ{e|ĝ{n|a||B}\}7~U}Z~9~A~d}v1~7~O}a}O}ܫ{v|l|ۃ|M}k|k|xQ}|P}t|wC}S}V}܈|{z{{|v}|{||Ms~o~U}\}Y~F~s}E~D~D~?~y9~};~l}p|ɽq|j|µQ}B~`}f~^}|}W~a~~5D~g}L~{8@~A}8R3ZVEL~U~Fq}Y~b|W~XMȾQ~E<~;~~7~?~ӿH~P~IJA~y-ϽH~N~Q}A~<~a}p|I}F~w|o|o|||}\}]~9[~W~ȺN~J}||g}b}l}^~Y~_~ʻP~R}w|y||||p}O~ĸP~{:~D~:~^}´W}P}t}f}w7~e}|w|v|{G}^}ȼ[}Y}R}x5~˺F~´G~B~?~<~>~M~K~y4~W}D}M}p|t}H~[~Cb}|h|w}b}|yP}ޕ|ۊ|߂}q}|}m}v}{}t}E~u}L~k}o|z||I~}||~}}P~R}x}~}|}|c}|X}i}|||š|q|ʹ{{{{{{{s}||r|{R}o}q}W}}}n||p|{{{{~|k}:~F~F~ђ|Ň|p}z|z|`}^}B~B~Ľ||B~|}}J}J}l}o|f|µX}b}:~|2~C~{8~;~O}p|{{{{{{{{||l}g}[}]}u}k}{6~s2~8~Ĭ>~G~\~E~±F~I~C~K~_~b~Q~d~>=d~X5N7L~7~J~ǷK~@~H}L}=~\}}E}S}G~q~t~Z~V~K~I~}8~}=~?~||R}n|{{{{m|~|P}j}>~P~^~^~L~G~Y}͡{z{{Ӭ{R}I~\~z4~>~>~h}E~g}|ȧ{{{x||\}u}y9~Q~38~@~Z~r}V}C}]|^|{Ǘ{h|L}]}A}K}y||{9~n}H~M}^|X}L}D}^}5~8~ȶS}G}|;~A~|P}ɿ[}e}|u}||q}{}|{zq|f{{{{|׏|x|V}o}m}c}]}Ľh}||j}a~W~S~E~B~B~B~|<~M~L~L~E~H~K~@~F~K~r}S~F~K~A~:~A~H~H~R~?~Z}Q}L}ѿX}V}R}e}P}{¾{{a|H}ҵ{{{{f|Żs|{F}f|{{{{{{{{ϴ{ݽ{t|X}݂|ݱ{{z{{{{{{{w|U}N}L}h|d|c}A~l}@~}=~?~;~e}ʂ|{||^}R}}N}̿d}N}|S}v}L~?~o}P~G~}A~G~z}N~P~û`~i~Y~M~U~ȼT~`}_}b}d}K~\~b~m~s}m}Q~x}¹j}~B~ƽ]~y6u~F~T}I~}}|ڱ||T}{}Q~U~K~\~f~}K}o}J~`~\~[~9DC@{9]~m~;RLĺVih@AY~N~T~U~^~̏}׽||i}Z~]~X~}|||}~f~~}||||o}r}؊}P~]~n~KSE{}r}Z}Y}S~b~Q~g~ƾf~ļe~CP}Co~DOVȾ^I}[}||`~TJBp~ƿj~r~W~y}Q~^~p~h~=LSRHSQr~i}\~f~z}g~g}}Y~|}OOIAP~}͕}d~u~~~~^~»e~v~h~Y~֓}}|~}BY~ѯ|t}s||V}|F~\}͢|T~f~ԉ}_~q}h}}}S~[}N~}}}؁}z}Հ}d}w}f}Z~Y~Fq~h}D~w}~}}|}O~J~{}y}_}h~u}y}|؁}V~j}|||c}^~}[}g~Y~a}|{|q}u}b}^~r~}m}t}~Z~h}l}a~}=~\}~M}|G}y}j~Q~}Ӓ|}W~߽|}|a}~A~_~8k}O}X}]}]}|B~R~g}f}}Q}m}F~p}X}T~X}Ћ|ˈ|||Ȓ|}}s}k}f}|{}]~p}|~~|{{{{|{|}N~}q}N~|{|e}u}~f}~y~x}w|ϊ|t||}t}ɿq}[~E}e~S~v~Y}½{f}V}p|{|~c|S}f~S}v}ѿb}z}B~B~|:~Q~9~\}t|P|wC|Q|G}:~W~f~X~Ƚb}V}t|c|~|8~òE~ųE~S~S~B~l}L}{h|_|ќ{д{u|~|f|ا{М{{]|f|{{{{{z||{{|{z{e|k|V}s}b}P~y}]~n~ľp~|||y}g}S}|`~y6~M}j|B~B~8~x3~<~K}V|Y|QWG~^}߉{{d|ѿR}˾o|{ǧ{ͯ{{a|F}I}E~@~9~y|^}E~±F~wA}j|˦{{{{ݧ|M~V~p}o|ƒ{k|n|I~~+^}O}±T}z|{̚{U}³C~P}a|q|ځ|g}t|z|I}7~J}j|ݿ{{{~J}n}~:~A~<~y0~=~?}y0M~7~;~k|}E~K~{8~Z}E}W}_}U}Ӊ||W}ؗ|[|ʽy|y}^}K}Ƶp||||y{{{|U~]}X~j~<?M~q||p}}}G~E~t8~˷[}?~x8~P}d}xG}zM}W}y6L~n~Be~Y}\}:}:~F~J~}9CU}C~~F}I~T~R~u~F~j~N~j~{A~Ǿb}O~f}}~=~BQ~?~R~N~D~<~Y}=~?~N}o|ɾ`}@2I~:J~6~8~b}ʾd}[}x}P}_~Z~T~B~i}Y}p}P~x4{;~`}|}S}f}4;~e}N}|||Z}~9~t}D~L~e}Y}M}r|ɿ}|x|x|t|n|j|u|¶X}Q}L}÷V}N}J}}2~M~.363K~{*P~K~B~ȼL~<~B~K~O~|}o}{{||O~n}M~ƽs}`}||}y?~L~Z}_}R}b}|w||}||z}e}|I~R}k}{H~j}m}]}~S}׃}z}|B~V}k||||̔|Ż||{|y|{P}K~V~W~E~B~V}j}n}}|{{{{ɿ{{{t|~r|Z}P}|ƌ|a}~}|P}W}|ˏ|ƾf}||f}ùV~l~D~h}e}|\~_O~Z}|y|T}R}z|m|{s|{{ν{{ϙ|}K~|}h}Z}r}@~B~T~Z~\~x3N~w}l}r}R~[~BG@>5;=ĺKCy5X~M~Y~A~;~>~{7~Y}^}9~=~|||d}N~x}F~r}c}I~F~G~¼`}ׁ|y}H~H~?~úY~D~?~źd}n}?~D~G~IY~G~̿V~K~W}o{z{{n|N}x9~M~ļU~>~ǽX~J~|D~|{{{{w|T}P}\}Z}n}J~D~e|p|P}z|˿y|{|b~b|b||f}}G}c||O}؋|~`|a}B~|;~=~k}f|P}@~`}t|{b|B}4~w1~}D}݂|R}F~>~|W}c}|[}{z|m}<i{s{z{{{{{{f|\|]}M}]}|u}O~`~F~\}]}=~@~U~b~G~ĸM~7~e}X}k}m}x}F~}9~i}C~E~J~ƹP~[~2Z~\}h}>~Q}k|_}h}x|i|{|ɼo|<~g}}f|{|l|z|]|̓|=Z<g|zR|c|{{ʯ{ʼ{q||e|ļ|W}W}Ď{zS}k}g}l|Ÿ{{{ÿ{k|s|X}_}z=~I~?~z}y}t}y9~öS}m|Y|j|v|~F}|D}|R}h}P}s|ײ{l|O}c}}N}_}Q~~}|}ʀ}f~u~g~`~R~J~W~t}n}X~L~s~LU}?~b~d~q}X}{|U~F~úh}S}x}|}|̧|~}D~P}h}e~GB\}s||I~T~˿X~Y~e~o~C@?<l~{9EO^RQVUSLGF}ӻ|W~e~W~f~~~u~X~a~^~}d~[~þg~`~S~f~`~z~g~k~CU~`}t}h~|Ѵ|f}p}}ľk~L~[~~I~O~\~f~`~j~X~M~d~d~s~Ar~s~k~]~W~}?LJ}@a~y}j}|i}½}}e}E~L~a~º\~b~b~z~Ft~|}y@~|}p~s~a~h~}„}Y~j~~]~m~`~}}{}}}x}}_~\~j~z~_~d~}|ŷ|ܚ|U}ݓ|K~G~v||l~s~ǁ}\~Ľn~}Q~K~f}g}|]}\~u}[~c~g~a~ɽn}p}c}n}||̟||j}ߺ|˓|n~}ý|}z~ϕ||i}T}|O~[~Ҝ||||L}c}O~ߍ}|ʨ|r|V~j~w}I~u~Y~s}]}o~}b~Q~H~n~x}[~M~w}X}x|k~T~|c}e}`~d}}^|||E~C~J~`~Q~M~=~x}~3V~o~W~_}̌|T}?~S}_}I}w|X}|{w|Ƨ{u|x|{||}Ӂ~Ȃ}˛|||è|l}d}|ʏ|a~K~Mk}؜|i~|{}d}e}H~f}C~q~_}l}Ǣ|t}x~r}}]~yG}Gg}w||n|u|ъ||{{]}R}f}g}g}N}>~@~d}v4~t5~y6~C}[{uS{Ռ{zQ{Y{@??>~z4L~?~l}M}^~|2P~A~G}K}Z|b|{Ҟ{º{Α{՗{{Ц{{ޠ{{\|q|Q}٣{b|z{ʣ{{{{zs{v{u{^|W}Lƾt}I~`}|UZ~||ε{|xH|@P^~Z}c}X}X}n}Ϳo|8~:~z3~g|j|E}}4~b~o~;~G~ͻF~g|J}P}s|{@}l|`}z4~U}V}9~P}p|`||g}z4~M}T}i|ߵ{{c|[|q}W~j~|W}|V|G}P}ƶD~_}yE}{ا{{I}ͽF~y1~x2~@~6~Z}Y}M}J}`|{{y|l}i}||_}j}=~R~I~M~M~B~T}r}C~S~x}`}ƶ_}X}ξg}|w}|v|p|ȿ{ž{||s||k|{{{{{{{{|x}}@t}@~B~ͺF~I~t}j}D~@~y=~K~@~~7~D~p}̷Z}e}`}Y}H~¶_}z?~d~n~Q~R~M~R~W~@y;~P~`~V~^g>N~d}ν`|Jj~{}p~;I\~\|V}żL4IO~Q~1óG~ǷG~ɷD~H}p|u|͓|ǿj}m}~L}@~.G~ŴA~0:D6I~x}K~G~ʺF~J~D~?~ׄ|{A~^~f}Ā|q||b}y1@~J}O}L}Î||<~w4~Ÿ[}9~Q~?~:~B~8~;~7~5~7~[}O}U}z4~<~6~8~9~N}A~=~O~@~g}9~c}=~D~K~R~W~g}L~W}|||{\~}w|}W}j|||]~}}H~Br~R~{}ڙ|zS}c}T~y~ƅ}V}|Ε||ʙ|b}||W}d}m}~}z}P~x}o}h}a}R}N}{R}{ʈ|[}g}B~R~E~I~F~c~[~{{}|{{|{{{{{{{{||{{{p|x||{x||}z}h~M~P}|M}I~`}|J~F~i}7~:~<~C~e}|¯{{Ĩ{{{{{U|}D}{}A~|>~K~}u}}>~F~h~O~ǻV~|:~g}ļf}E~G~N~Ai~ĺS~H~h}ʼ\}B~_}=~c}D~@~d}÷a}d}óG~H~U}y6~{6~=~i}V}r||F~`}f}I~Q~`~v1~;~^}O}|8~U~Q~H~F~|zK}<~ùP~F~L~M~[~c~G~S~ĻR~û]}w{~{i|Q}`}f}|J}[}b}l}u}m}t|ȧ{{{˪{܈|Y}|S}^}_}Z}h}f}d}:~R~H~p|ˆ|Ҡ{a||s~=~|>}w0~D~F~||a|ڳ{{||P}zF}~D}P}b}Y}@~[}T}ۓ|k|d|ķU}D~ɾ]}<~b}|j~L~n|j|{{{T}Z|{{}~{{{{{{zy{̴{t|d}i}l}f}p}y|N}D~ŷI~6~g}H~:~C~Z}Ճ|r|{|ה|k}G~D~>~f}~9~i~8e~h~a~B~K~D~T}}|}|`|c|}||f|{˽{{Ĭ{{||t|w|^~>E}T|{d|ʩ{{{{͸{ܑ|΋|W}Ľw|z{Ǝ{z{|_}`}o}u}k}|Z}^}P}N}]}ºX}>~<~@~g}O}|6~8~f}ɿ||{U}[}|q}i}X}Z}R}a|σ|z}\}\}b}H~I~K~O~i~t~j~[~w~F[}Y}n}ýw}ƾe~@j~}J}x|||g}e}}D~N~P~M~||||Ŀ|F~^~}u}Y~y~m~P~|}t}J~R~źT~S~R~P~V~N~r~`~c~?e~D¹TCHBCGAF~M~ӹ|Ճ}üb~DHLISQACr~ƿg~K~y}|}}t}o}}}h~i~h~N~\~}W~ש||V~Z~Ľk~׃}۟}Ŏ}|t}}W~Ɉ}ɲ||e}ݻ|{}z}x}|s}t}}}k~@GU~}˂}M~}Ռ}|m}M~Z~W~H~\~@n~}}a~x}|U~r~m~ҍ}Y~n~DICx~~~A~~h~V~k}}l~DTXOY~}֑}L~T~X~|@j~x~y}|{I~l}c}z}N~}u}|\}|b}Q~h}~v~_}\}|q|s}¾~}[~؀~x}x}ݎ}|q|{||Ġ|w}ͻ|ƶ||||Ƚ|]}K~s~c~|h}~݇~w~h~||}٘}V~||g}|y}s~|?W~U}}}X~}üq}t}ȿ[~@~t}l}o}@~Ǻn|[~z~|<P~o}k||o}RBH~K~`}ψ|z}b~}Ѓ|X}H~i}T}t}y}q||I}Q}t}¼n}؏|{{{{{̳||}|ϻ|˾|ޭ|e}h}A~d}ſr}Ƃ}}Z}_~||||Z}N}}ƺ[~L~h}|o}b}L~Y}q|ۀ}V~X~^~p~܂}ϙ|{|ɟ|Έ|Ļ{zz|û|m|Ӆ|z|P}F~g}L}îN}|6~G}O}o|ȇ{{q{ƛzՐ{=~SD{~m~X}}K}v|m|y|`|{S|Z|Z|{{{˝{{ý{{{{y{v{{»{{ө{Ͷ{y{zzz{|~|{{r||y|v|A~{=E~l}u}_~p}|˸{{٫|b|þ{b|Ⱦ^~W~W~~95C~ɾZ}{|<~L}yT|i|K}ƴL}@}k}x3Z~ʼM~B~C~t(-V}s|a|p|i|C}D}6~7~}|]|G}ŶU};~K}ĸs|Ԧ{Ś{{ž{R|_}@|}|Ⱦo}||}5~07~Z|ԗ{[|~}xM}`}J}d|\}a}M}i}M}[|`|u|yK}Dž|k|ƾ|}{|g|x~h}ͬ||{Ъ{Н{٘{U~Vf~||8~öO~A~^~?~}p|||{{{Ƚ{{{{»|z|m|{|}|{{{Ի{k|c|ƞ{T}\~t}E~Y~P~C~ǻ^~f~G~j}^~L~=~M~@@~:T~{}L~ƺY~@~v}Q~t}El~e~^|W}}a}~AC~t}D}ûf}{~R~W~P}>~KLd~z}х}~}K~W4;~Q~N~53R~ȸS}߆|R~]~U}|{|ƽc}}7~M~G~@~D~Y~Ľ^~z=~h}R}v|}|l|=~C~|;~653L~>~m};~C~{0:~v1~F}X}N}[}O}I}W}9~d}9~6~g}a}}B}S}@~l}c}T}c}]}]}c}>~@~|L}]~>A@_~L~j}|ݱ|}{Z}s}||}u}ݳ{{˶{|ʀ}a~c~Y}|k}R~|C~~d|b}t}{{s}DJE~Z}}|q|ژ|e}z}Y~v}j}}@~~@~m}{=~g}i}n}z}o}u}E~>~n~}}T~k~`~A~H}x|z|{{{{{||W}c}||||q||r|b}OH~{~|||{{ύ}m~o}}V}ǹc}P~^}_}G[}Dž|v|s|݇|||f|K}S}~E}l|ڹ{~`||N}ûm}r}p}F~̅}t}|_}f}Q}o}e}ɾ_}i}v}a}f}w}{}M}`}Z}S}U}ŹZ}a}H}Z}c}k}A~=~=~~5[~^}a}c}z|H~{D}|s}p}^}p}C~;~R}3~4~G~ϾJ~H~~|q|~|yF}~8~W~M~t}n}w}t}I~L~?~Ւ|`}m}m}i}^}`}R}؊|Lj|{{{{p|q|h}}g|{̈́|U}`}P}q}D~C~||\}ƾd}:~]}Ĺu|T}U}}7~a}a|B}͸E~W~|Ѐ|O}M}||{õr|v3~_}n||u)w*ʷD~q}q}|K}}[|C}a}Y}@~d}^}l}ڃ}s}þj}{֨{[}||{{}}{{||k|{{{{{ˀ|q|q|p}C~N~<~H~H~x5~D~?~B~U}]}X|j|{~e|i||l}\}~?~H~L~c~^~X~L~S~ɺK~=~H}ϟ{٧{ͦ{Ǡ{Ǽ{|Z}v|ػ{ι{ʴ{©{{{{w}K~y}w~~}ۢ{ֲ{{Ǩ{{{~~{{{{{}|V}|l}Ʉ|{{z|h}y}`}Y}}c}}z}f}|}s}i}o}\}üm}|=~e}û^}P}y|E|W}a}j}Ľn}{}}Q~E~l}V}R}{||ÿ}~I~}}Q~n~l~V~=@P}އ|~K}[}}L~H~X~I~I~Q~C~}o}v}h}||h}}||r}X}Ǐ|Y}L~z}X}}f~B~L~\~q~j~ú\~i~Ż]~J~G~~}}}S~o~?|8@=;f~ŹV~z:NCZ~n~V~g~[~d~Ƽ[~h~~:G~a~|~MBs}x}}k}s}||ڱ|v~R~|ޕ}t}^~Ȳ||||||܎}}|}|w}׎}|ľ||}S~}q}Q~c~҉}}}{}i}}ƽd~a~s~PF>l~?g~^~_~O~O~~M~V~T~|ݿ|r}Ã}d~s~t~~~k~h~}|u}}c~a~a~e~n~|C~]~}Q~N~}y}^~j~`~`~~G~Y~މ}W~]~P~o}΋}[}c~Žr}T}Ł||ɞ|||b}|t||||^~o~ށ~ݢ}y||}[}ǫ||O~|S~R~Y~S~||_}ڗ}r}|y}^~|l}{}Ԇ|}x}z}k}|||{{||l}||u}P~|}||g~Z~v}Y}W~{@~8g~S~B~i}[}L}I~S~o}|i|i|Q}[~Iz}`}Q~m}E~{8~N~^~M~O~r}ݜ|k~U~I~e}M~~@~O~k}|{||{{{o}x~P~V~ގ}b}u~U}b}k|e}Ռ}|b}\}f}u}ԁ}|~L~p}ļm}f}і|D~y>~P~I~y}v|||X}I~Y~x?~F~||r~ݼ{˿{ż{{{ƻ{x{|~|}L}t|ɶ]}n}S}B~j}c}w6~m}E~]}{}P|u|D}w|h|||}`|Ó{k{zn{w{{s{{{ϓ{{w{{{s{z{{|{y{ſzz{{z{{ִ{{{ǩ{{|u|s|`|L}}9~}_}О{G~8~@~h}ľ|~|{ѽ{}c|x}j}t}X~=F]~f}|;~i}g|{ܩ{_|?~t.~w/~<~R~B~98F~H}{ܬ{њ{{T}U}J~]}V}M}~|<~@~x|{ʠ{ʢ{Ҥ{{{a|h|j||k}{{v|W~X|||ĸP~w}|{ۨ{d|{`}H}^|o|r|{ս{|h|a|f|g|m~};ü`~z?~m|e|g}ʾb}l}z}R}R}D~S~x:~J~Q~}H}{ſ{{{n|æ{zz{|x|{|s|||||}||{{{{{|e~p}C{}·`}t}|D~|Ȼ|D}B~|=^~C}G~o~O~V}ƽ|l}i}W}N~l}_}Žs}÷U~H~G~V~]}һ{¹u}I~V~j~V~zF}S~W~g~g}QIr~ƻe~F~hp~L~>Q{+I~0K1DA~U}B~¶P~µN~z5~N}v}k}s}r}ԗ|[~}~Y~LW~D~D~A~a}Q}O}k}N~n~^~E~E~U~@~=~G~S~ȺI~5~L}P}P}v4~z6~h}}5~w4~J}f}V}L}f}Q~W}C~I~e}_}Z};~¹T~U~ûX~]~֨|m}j~F~w|_|||||o|i|^|X}}|{{{j}t}S~U}f|J~}K~}Ah~H~{{{ÿ|~|٢|{|˲{ʕ|ф||}՞|}M~H~}S~z}}A~żm}žn}d}h}¼l}s}k}c}r}F~G~W~V~M~|:~|5~C~c~c~W~e~K~T~Z~R~|Q~MZ~}S~j}|K}{x|Ä||y|P}r||ߋ|u|{ܿ{{{{|||w|y|{{m|m|}|{|o||{o|{{{n|}|d|{հ{|j|o|ɻo|Ⱦk}O~U~c}|h}\}n}|<~y}z}f|{s|σ|}i}u}z}d}a}~L}ޕ|V}Џ|||̙|g}·\~J~d}I~9~4~^}Q~m|í{}}l~m}|X|{{|S}I}v1~H~^}ùt|h}E~a}҂|R}~;~y:~B~L~_~M~c}<~;~T}т|w}H~V~[~=~?~L~L~@~l}ȿ_}b|ę{m}~RZf||S}L~I~ºd}e}?~?~P}Ň|K}¸c};~u5~f}d}_}<~6~P}[}_}8~j|{{}a|d|^|i|w?}ö^}M~߀}E~Q~}||9~7~X}|U}ĹY}R}X}Q}W}K}{|r|z|H~~E~W}{ћ{X|}|۰{{{{{j|K}Ԥ{{{{h|P}?~P~T~>~T}G~D~F}e|Q}|u|N}_}H}{f|a|{|[}^}Q}r}\~Y~S~m~`~W~b}R}}A}O}}|ޫ{f|h|~:~t}{ڧ||{{H~{=~F~~}~||Y}|{{{{{{{{{{{{k|y||b}N~k}|x|Ӥ|a}i}b}^}ýu}}S~a~y}^}d|{͔|b}E~x}U}g}h}r}R}c}΁}}]~d~j}Y}}d}h}x}q}c}v}e~i~f}||Z~{?b~ùX~}Q~J~=|9h~o~p~c~Ⱦ^~i~L~`}b}X~X~zC~y}ǼV~x}||d}}}}L~}L~\~j~]~R~I~@~E~b~f~N~}O~U~d~b~b~;BDEDz6Fu~b~I?<;IV~zA~g}O~v~XDw}Ar}||s}n~Ei^|~}~}C|||‚}]~f~j~m~֑}}z}z}О|զ|¾}W~[~üg~P~m}ýz}Y~[~^~k~^~_~R~]~b~N~~}|ڬ|^}j}\}j}d}t}|O~q~i~ûf~m~Y~Žf~b~b~U~ۓ}|}q}|o}}||̆}f~z}}K~}z}y}ƌ}V~Y~R~[~S~}|~u~s}ʨ|˩|N~d~T~}H~M~}}}h}U~_~f}p}p}u}֋}ü|P~g}|{{|}}}Nj~}U}R~N~[}b|{{f}x}Ջ}W~k}w}}h}E~\~G~X~l}|j}}l}q}}_~j~ǽ`~ɂ}z~h~GEm~C?]~»`~_}|{|L~y}e~^~L~j}ĺy|m~CM~F~Q}l}h~ÿl~[~|}z}J~M~[}f|b}|=~t}Q}|m|\}|} ||J~N~y=~e}L~n}||u}}R~Ҁ}v}i~d~i}Ǐ||O}M}k~t}}:x~x}j}j~p|M~\~}Z}k}||}G~_~Q}F~\}||L~QK~J~d}f}||`}S~z||{{{{{U}{}҉|t|z@}^}h}q}T~l}f}m}ƴ`}f}||´p|;v|˾}|n|r|zzz||{Š{{{pq{z{{{{zz{{{{{{{~||z{{{{z|{{{{ռ{|`}|w|v|zA}|~|ޭ{{t{{S|{S|s}\}{چ{W~Bj~:?J:E~c}\~D~}|t|`}ǹI~@~M}|H~~4z/@=~8~{||l|Ļw|]}H}]}L}6~Q}8~+<~|g|{Ǣ{ۯ{Ϧ{ȡ{Ø{{9~İN}8~C}X|Q|\|et~ė|X}E~}{]|T}V}T}\~E~U}s|y{{{[|O}R}h|o|~V|W|]|^|{{{d}A~}5~k}Ͼ[}C~d}9~:~8~X}~W|ʧ{]|j|p|{z{||{|}ÿs}\~ƌ||{{|j}d~J~V~Y~|D~|;ùRI~Ɠ|{[}K~@b~Y|q~ST?[}n|l}b~˟|u}_}U}|[~}x|ͷ{{}IH}||S\~w|Ĩ{ت|֔|~}[~IYc~\P~KC~B\{+Rd=z.>69<>~^}]}{}F?g~úU~^~d~i~:z5\>0P~U~J~b}V}|v}V~s}a}ʿQ~I~;~x5~7~D~C~R}J}S}W}M}Q}}X}|{>~[~}R~c~S~O~k~b~[~BAƿh}GUh~֓|Ӛ|}|^~Z~w,x+x*.I~e|R~~}r|ڢ|]~o~H~C~S}w}~G~|{|{{{~}[~z}\}^~D}}j|t}M~x}O~K~ɿb}z>~}}h}p}|>~||J}X}}m~p}\}y}P~~C~S~W~g~F~`~;c~C~S~~5[~E~~:LľU`}{{m|ܵ{||Z}H}g}Y~U~ʼK~A~ƺ]}E}o|ȳ{ʵ{{{{{ɾ{{{{{q|o|w|~|¹y|J}̓|l|{f|v|M}v|Ȅ|{|t|||h|U}K~Z}J~v}{Ҵ{|f}q}i}P}ȫ{V}D~N~T~G~P~o~g~s~[}b}d}h}~A~J~I~E~I~`}J}=~Q}Z|_|ƿQ|<}j|ۘ|q~|u|{_|G}T}L}O}W}DzI}T}_}m}}8~o}O}j}=~u}C~F~B~M}l}ƺ\}z6~R}~6~i}F~I~L~G~@~P~@~l}e}q}o}=~6~j}~;~K}S}W}<~_}||ч|Z}D}W}X}i}c}n}B~C~l}A~f};~ǺZ}c}~D}~|~|?~;~u|{|լ{ݵ{ݳ{n|c}{|||µ[}@~}w}ǽ_}Q}S}T}Q}}|||`|٥{i|Ш{¥{~{{|L}n}M}^|ݤ{{zzz{ˆ|V}V}l}^}^}{q}h}N~y7~D}A~p}{y{Ɨ{}G}ހ|K}M}~|{y||X|}]|f|~G}Y}||}O~B~B~H~>R~u||E~b~\}Z|Â|^}y|{z}t}|ƿ|j}n~]|a}|l|{|{{{zz}{~{{{{{{{|}[}~Y}b}|}`}|~U}k}|a}b}k}~}d}}s}d}e}Q}|͑|W}g}_}`}|ˈ||u}}K}P}}J~o}}B~|}h}}Q}o}|}G~N~m~|{h|w|O}P}A~E~W~e}t}j~\~V~l~O~S~~G~S~D~q}^}D~L~H~˿X~|:=s~Ǿ_~y}{}F~T~[~E~F~L~U~V~W~[~O~P~C~C~R~O~_~I~{}N~J~M~Ⱦ]~B~E~8<j~g~C~z}H~;Da`B·\~^~{~Qs~j}v}q}¾}Y~GJECAw~y~|~}q}P~P~]~A|>T~\~d~g~e~Ľh~l~Z~S~i~h~P~‡}||||||||||||Ͼ|||d}}S~l~EKw~GKORLJHL~i~}}҉}L~t~~ԋ}|S~}}ƾh~}~|~~Bz~u~[~i~CJES~n}||k}|K~Ľt}f}|]}|||{||ϥ|ێ}}q}y}P}}}||ˏ||M}v}R~}{ž|t~~f~ψ}w}H~W~zF~g~}}Q`TYZr~|;>~W~s~ID>O{8e~a~e~|7x5x8c~˾V~K~`}ϛ|́}n}]~P~j~|L}Q~M~R~C~m}V~n~q}V}}A~`~I~žl}x||n}f|܃|P~{3r}c}|A~b}م}f~}ݦ|H~{|g}Ր|}|s}Ժ|p||Y~o}Α|{>~i~_}{j}U}h~q~Y~m}P~f}H~a}^}X~J~q|e}|X~|t}k}l}H~v}||{ߣ|ֈ}W~{C~d}Ǿl}h}}J}Y}W|{{{||{{{{{{Z}P~k~n}V}c}s}ǹ^}E}X}R}X}X}s|x|h|o|y{y{Э{|z|{i|h}{{{|{{{ͫ{{{{{|{||||||{{{{|||d|\|Ƽ[~;}}D}e}U}Α{|k{w|^|˿`}7L~}_}i{?I>H~?E;W~\}C~?~|\}|E~~@~L~v}ʿi}}|T}5ŵJ~u3~=~`}z|c|t|{{b||?~d}D~?~\}M}g|ڪ{{{ө{Ф{ܬ{^|m|{L|<~=~^|{r||h|[}}^}ϰ||||ǃ|ց}^~?~5~|{{e|S}@~6~K~Y}y|ϟ{ţ{o|?~οV}M}[|~E}6~A~M}Q~1S~ƴJ~8~ĵ[}d}\|R}S}o|p|o|{z{{f|^e~Ώ|r|œ|ߣ||֐}c}}կ|W}i}\}N~O~w}||}~}U~b~_}a}Y}}m}y~M~?FX~q|ك}d~a~V}||}z}{}]~n}A~}f}||}G~~S^~g~o}g|U}x5~a~]~9~:D~:@Jy.x+̾BADGHA@E`D9FP~z~B~F~{6AU~@Q@??BL>D|7C~}A~8;^~c~b~Z~C~t}^}žs}z}Y}q}P~h}\~G~F~_}|l|{{z||~}K~Y~<6P~ǻT~L~ǿg}|X~~@~T}Y|Q}t}n~h~A~d}c~m~k~u}}}\~E~׏|x|К|i}e}V}X}J~}u~J~|}L~j}v}ژ|~Q}T}~B~f}G~V~~}X~W}~|j|x|t|c}ڌ|x||b}e}q}|p}H~y|}[}};~`}p|p|^}ƾ{|D~v}͑|v||j|j||M}r}^}A~S~{6~f}ǺW}^}Ⱦt|{{{{{{{{{ϸ{|v|~|}|k|o|{C}P}O}||Y|ǷU}y:~[}S}U}î{Ź{{~|m|x|;~j}L}u|F}}=~E~^~[~~B~i}[}}L~z}s}L~>~U}g}M~T}Y}|W}r}d}l}||T}r}O~U}ڧ{{{{{{{|u|i|Ļ}|I}ĶZ}Ǽ`}ŵX}E}V}:~˿Q~K~a}]}R}O}j}D~R~@~@~C~E}u|n|l|֔|@~c~_~`~L~9~8~@~;~g}z0~V}]};~_}^|h|x|{{u|y|Ƃ|e}M}Q}W}_}i}g}S}P}l}e}q}e}r}_}?~_}V}q|K}i}f}W}{й{{{{M}r||l}A~˾Q~H~QK}Ǿq|H}~\|{{z{|W}{zϩ{~G}ż^}X}M}L}T}b|u{{g|s|{{g|b|~A|>}o}Ⱦy|e|Q}=~z:~H~²F~F~~B}{r|D}٢{{{{zŪ{|||n|V}{}~@~N~O~E~u}F~÷O~83p|G~R~h|n|s|{{{Ò|b~Y~ֆ|{z|}}||Ә}||{{{~{{zz~{{{z||{{||̧|b}g}S}ƈ|w|r|{{{}||{}}r}n}h}`}d}t}m}y}g}X}L}|K}U}N}ƿo}}P~M~c~AV~}o}F~T~v}|T}¸c}Z}P}|[}X}s}^~¹Z~CR~z@~V~K~N~}߆}}|מ|_}S}|}}ڇ}M~L~p~R~Q~O~V~øW~D~S~c~c~`~}W~E~g~l~|:h~F~Y~h~L~M~ĹU~g~Z~M~M~A~V~l~}{}}}F~}}~H~~Sfy7}^~l~y~S~k}t}}P~a~Fy~q}Ɉ}Ń}ߐ}s~}@q~Ŀl~U~a~o~X~U~Y~Y~z~?a~ýk~Y~Q~ԍ}T~O~t}|Ѭ||}||̲||||Ƚ|s}R~R~y}i}`}o}j}}d~DDn~@KQNJEOQTƽ[]TKHJs~FAGO{~P~}|}Œ}w~|~q~»l~MYq~a~k~@Ff~T~`~a~y}|||͸|||j}W}E~y9~P~\}U}N}||u||s}||h}{}V~t}H~Y~Q~}P~N~M~z~|Bj~Ep~]~j~JE@f~m~KE~9ƻ\~L~·Z~e~`~ɾZ~ȽX~K~t}x}}R}|ڬ|J~G~}A~~AĿa~h}K~U~H~·T~6P~Z~{}I~٬||ٸ||L~Bi}j}g~H~||ʪ|||z>~N}ސ|r|{|^}V}U~||h}܌}ݑ}ʑ}}E~u}U}|||т|G~Gd}d|||y|||||{{|||e}{|f}||̀}|}º|Ž|J~o~I~W~f}Ӑ|n}f}|{ʩ|{|]}{{{{|{͠{ݚ|l}K~}G}ĸo|s|E~A~k|~D}>~l}_}|G}yF}Q}^}{{w||{{{{ָ{{{z\|}=}A}g|u|f|F}D}|M}g|ϾV|ɹU||J}|u|a||}`~?~J~J~}O}e|c|w|P}S}|B}P}b|u|Z|M}\|V};b~|}M~AS~c~b~M~R~m~[d[~q~H~Ԯ||d~HE~H~_~ȼe}M}BƹI^};~\}g|}`|ǻr|b}i|ʾr|ɷS}`}\}A~B}z|{{{|Q}}]|h|{ծ{ͩ{ƫ{ִ{Ϭ{Ƨ{̥{ҳ{{n|s|L}µ[}͂|l|ԋ||{{{}Y|\|Ѳ{ʁ|q|x{ـ}ߐ}ų|z|ڀ|T}a|{x|L}U}m|j|{{{r|b|b|=~O~S~:~t0~w|J}ͼT}Q}k|k|ǟ{z{{{{r||{c|g~{>~E\~˿k}|l|ºy|m|M}m~Qw~^}JMÉ|Z}n~N~@t~N~|~d~A~Z~]~[}|m|V}f~b}{y{N~»n}V}ǿ`~^Dy}|{r||W~X~U~\~NVI~_|T}E~ɺP~ĶI~?N~~7?~7LV~}:R~Y~6E~G~¶[~F~?P~=M~T}hR]~}J;l~M~1:8E~<~U~<;Eb~X~[~z5ȻW~c~B\~N~m}u}J~ƼY~M~N~>~F~x}̇|ƾ~|p|h|T|}|p|S}\}m~^~|}B~e~D~~|_}OG~|Q~ſY~ĻT~d~_~g}G~M}s|~|e}V}L}||ĺ]~{8Ƽg}c~\~^~M}Nj|Ʒ]}Y}=|<||{g}e~O~В||{}|D~C~т|ƽl}[~X~W~N~{|{{|ͅ|d|[}ſo}t}\}A~;~b~K~c}կ{{{}|N}E~~a||{P}|X}u}_}a}J~F~G~9~[}N}ƥ{{ε{{{{{ {v|{|||n|{{Ҳ{Ҭ{l|_|_|m||J}R}G}ā|y|{r|k|y|̻U}ųE~7~`}M}څ||e}J}Z}m}A~ľb~Q~v}q}h}e}b}i}u}K~G~w}R}|t}A~v}k}e}l}l}|>~E~o|||U}p}{8~s}d}o}e}]}\}¹d}f}g}w}z:~=~A~D~?~p}D~A~E~O~_~[~K~r}e}Žb}||S}˿`}:~R~{0H~R}A~:~K}|c|V|M}b}W}^}K}m|m||r}~|y|W}R}X}W}7~_}N}N}R}U}}G}U}܊||}9~i}P}w|ԥ{d|̓|߄|W}_}u|ۂ|k|j|݊|v}N}w|=~O~e}V}^}ļm|{z{zzz{{|e}m}R}f|_|`}q}\}N}x|_|{{l|\}\}q|c}G}ه|J}Y}T}o|y9~O~B~d}a}s|i|H}||^|{{{{{Ǫ{…|f}b}C~J~O~A~b}g}i}S}E~~2Ԅ|i}]}l~s~ׂ|^}@~x|{{x|}|{|||||{{{{{{{{{{{{|v|}|Ӝ||{{||j}\}Ľh}u|{z{{|||u|֐|\}||||ė|||V}c}c}W}W}z}t}r}{}|D~F~N~Y~l~@<Q~>~R}\}w}h}Z}R}ǽq|\}p}G}w|Z}zB~O~\~N~E~O~^~ĺ[~}e}V}c}`}\}||{z||ę|s}}}U~U~b~Ⱦ\~j~º_~i~w~ƽ`~]~S~M~R~U~S~O~^~f~S~C~G~ʿ_~Ad~u}H~J~E~P~R~D~D~x}ی}M~c~|~Gm~`~j~Aw~{}l}¿}v}Q~b~}L~X~r~p~~GGs~Hr~r~G}~f~Փ}|}_~¼i~a~|O~_~ي}|ݎ}i~Ԅ}l}ލ}]}||`}޶|ȶ|{}O~R~~}n}ځ}G~Q~\~_~S~`~e~ļ]~R~u~q~X~م}ֆ}b~w~E@|~x~~|~z~~~`~e}}ýi~}}Q~U~n~l~t~Z~V~v~~~_~^~f~ƿe~a~[~T~V~a~}DŽ}\~t~h~}~_~|~ǼX~X}l}ի|þ|\~P~w}}c}|J~a~}}g}W}ʽz|Ÿt|N~g~i~w~Y~X~ǿe~e~y}h}|U~Z~~MHp~s~}~JOx~R~م}L~ļb~P~P~}ſ}}O~S~S~}Χ||ɧ|X}q}j}^}q|ŸN~E~U}̓|t|t|}|֑|X}|{\}X~|{y}Y}|[~z}l}{{ԭ|I~|{||Y}̥|M~M~v|O~|Xj~=OD\~s}|]|K}}f~n}Ǹ{||w|c~n~|~|{||yz||z{|r|`}Y~EŽb~D~[}k}V~ܐ|Ƞ||M~b}r}z|{`}L~}_}ˏ|{Z}Ӿ{{f}y~܌}o}ȼv|R}L~N}ߕ|b|y:~m}P}ƹq|˿v|q|Q}F}}Q}Й||ǿ{{|i|V~|{zH}A~D~f}b}:~v2~k}ŷS}Ÿf|n|W}G}O}ó\|PBz|a}X~Q;`}p|}|G}_~Z}v|v|g|}P|x3}H}e|w|m{ź]}eA^~}G~h}ýi}x|J}w|7~T6^m~ُ}x~H_~]~Z}>~c}<4P~?~>~7~b|_|l|M~M~a}W}I}a}M}C}||S}ȹZ}_}λU}{|^|ߤ{ǜ{Ϫ{ڷ{ٺ{Ǣ{ϸ{ܹ{e|m|x||U}M}s|ײ{ί{y|‚|I}L}N}t|f|{V|Y}R~|~}͹{J~T~<~Y}k}B~ɸW}}|^|P}|܊|||j|}e||o|i|_}~6~T}<~=~M}i|{zz||}|{{||ի|d}t|^}ȹT~7I~b}d}~S~d}U}p}ց|@b}^}}S}a}~~L~K~Z~N~X~=~Ȍ|q~Ep~}QWL}^}S}N~m}i}_~\~M~q~y|ƿ{Y~`}|T~Z}\|[}_~f}N~;~h}E~ʽY~:~k}h~E~c~x4j~<E=}6}:Pq}M~pj~R~p|w|v}=|:Gm}|8~A~{|Y|Ѕ|^~N<O~<>]~?{7Ǽ]~N~r~@Ma~C~~O}\}P}]}E~J~G~H~B~z}z}p}s}a}y|f|˱{\}m}C~9~E~^}m~H6ƽW~T~[~R~9}7E~ؖ|s||||||{||}C~T}|}v}}`}P~O}O~e~{}u}Կ{{{{{}N}\}i}K~K~ƾo}Z}b}S}v}||v}R~S}c}Z}ýb}t}}F~u}Q}q|DŽ|K}}W~Z}w||n}|N~e~{w|W}N}µU}7~Y}V}9~`}Q}A}s|j|£{g|ó{{{{Ʃ{p|~C}|r|||u|z|s|M}O}~O}||ؑ|~j|{ҷ{ô{{ü{{{{{Ŗ|]}u||K}M}|{{{{}{{q|a}|v|k}F~K~u}Y}r}W}|l||`}z:~g}s}{=~I~=~C~g}]}p}u}G~O~R~}m}x}|}}}Dž}r}z}L~]~Y~]~¶M~ǺM~˽M~S~ȾS~d~e~a~ǽQ~L~A~>~E~S}k|R}]}K}N}9~:~^};~I~>~O}m||N}n}s}l}Y}||՗|j}<~˽O~^}|||^}~L}O}`}q}Q}Ļd}h}Ǽ_}i}O}||N}?~ĻO~L}՞{g|c}Y}>~|:~f}|{V}K}k}H~º^}^|N}Ľ]}^|d|ʽY}~|r|{|{~{{z}\|F}L}}G}{H}||Q}ڀ|l|Ζ{{p|S}|F}G}H}o||f}@~{|ͬ{a|M}b}·P~B~8~i}l}^|{V|b|]|a|Ʈ{}{ɮ{{|ۈ|W}V}ǿi}k}`}n}@~:~M}F}v3~z-;c}<~a}l}D~[}f}I~t}e}}c|k|s|p|{{{o|Ͼ{ð{{{{{{{{{{{o|^}k|t||f|{{{^}g}{}|{{{{{u|k}|ߕ|l|||~|{{z{Ŀ}|{{{{{||{}}E~{}}G~zC~L~d}t|J}`}d}9~S}n}x}M};~V~ùV~X~{7N~R}]}l}}_~f~`~H~d}Y}k}o}H~u}Z}|||z|p|k}P~H~V~o~}=a~r~l~s~l~ż\~Y~W~_~P~u}z}~K~{}|}M~}`~W~Ž_~V~b~]}}U~D~z}p}}Z~ƾd~q~AHLCz~c~||Ʒ||X~{@h~r~|~n~}V~}n}U~EQRIC{~x~k~{~Gx~B~?t}|}I~s}^}ג|ۉ}{}Ԯ|g}y}L~X~}}}Q~ƃ}~M~P~z}U~¼g~d~A_~ҿ||Y~x~y~U~R~a~k~t~D|?[~}U~S~Ӕ}|Y~~~e~x~c~u}q}{}v}}e~h~HGm~W~[~c~y~y~[~q}Y~e~X~[~M~½}h~k}^}N~n}u|s}~v}Z}q}L}n~]~\}މ}}p}{h}k}~]}l}h}a}|҄}}|||}pRMAe~S~e~}x}}Z~d~h~_~}M~}ݧ}}Z~|U~d~{F~I~m}{}n}p}F~@~U}L~||||k}B~n}|ʤ|V}U~b~{~f~P}}}¿|X}S}~}|||{|~|}H~O}|ٹ|e}}}}Gp~ȗ}|||||o}}}[~c~|{{{|o}DZ|||{{{ww||{||z|ܔ|Az~c}{{{ƞ|{||w}||ª{ű{˼{h}{|ΐ|f}|ّ|||׃}d~R~P~}_~z}Ľ||z|uG}|T}y?~o}w|E}I}e}N}}|~Y|O}v|{{q||P}|{p|¸d}V~?~4~0~~;}e|f|{؞{{{Z}}H~m}P~|י{q}o}b}|{J}Q}W}n}K~P}|R|5k~~|ϓ{p|\|D}Vd~S~~}l}^}X}O}h}U~M~:5[{8G~x}N~?~¸U~]~==z;~J~1P~F}]|L}v1~B}F}[}Z}λD~:~U}h|A}Z}9~|{{˘{ޡ{{{ҟ{w|t|h|}[|{b|ک{ģ{{}k|˥{{{j|j}?~J}b|{H}ĴY}};~<~~8~\}y3~:~R}a}ƸK~|/7~K}w|9~I}9~<~D~K}÷q|{գ{ݾ{{t|Y}<~|i|{{{~|{|||{|||{{dž|գ{i}a}}X~y|k|u}}S}·_~K~N~^}g}Ŀw}ºp}M~e~R~|{5dp~}}LüTt}R}»[~?~z}Y~R[QDºW|r|]}~R}q|{{ÿ}d~ۙ{{||P}M}P}c}L~x9~L}h}|_}L~I~ø`~{}Q}y|}G~p}~|i|i}f~BR~||{}|Ӊ}½p}K~=~N}m||}{}}=~c~úZ~X~;{7A>c~]~@n~Kp~D~P~K~}}\}e}G~D~A~F~k}z}p}d}t}|B~}l}u}X~ŹS~8|@~Q}U~o~q~i~@~d}u}{A~O~O~||ʑ|||{|}P~~H~T~?Z~o}{|R~y@~b~[~J}h~W~Cƿd~c}l|{Z}[}P~]~zB~k}h}s}\}\~j~k~O}r}b}|Ή|\}P~t}e}l}j}i}ǿg}^}|||V~g~HJ{}h|{{q|6~^}K}5~6~Z}L}m|F}g|v9}x=}f|{A}F}E}h|{V|՝{`|x|R}{9~@~:~N}y9~|}X}L}g|{ڵ{{r|t||i}G~a}[}Y}R}d}c}\}ǽa}N}Ӻ{{{|x|}|_}Z}i}ۂ}Y~d~P~}ɋ}~}||_}v}zA~v}A~ľ`}N}U}º]}\|Ԉ|b}v}D~g~O~Q~g~K~h}P~l~i~j~a~~6e~ȿU~O~\~Z~N~C~O~b~A^~S~<~~8~Y}{y|Z|w||e}A~Q~A~d~Y~<~h}i||_};~d}R}^}W}i}h}b}F~>~Z}n}L}|H}V}=~~?~{;~C~B~ŸN~j}[}w3~B~S~¶N~D~L}S}|H}Ÿ{d|D}g}y}}<~>~q}؀|~K}t}>~~|b|{{k|h}P}p|W|{z|~A}ߐ|{I}a|I}R}R}Β{n||C}]|zz{Ȗ{Y|òO}n|u|e}=~v|s|q|f|~:~m}r}_}7~G~P}{{ײ{Ǫ{{{{k|I}F}Z}H}c|{j|Y}R}ŸW}´V}<~v+3@~b}f}W}:~z2ŷ_}[}T}m}D~:~R}|ʍ|{{{{{{{ҽ{{{{{{{i|e|n|n|Y}Y}G}U}Ł|{{{{{i|ɶ{{{zzzz{{Ϻ{j|ة{{{{{~|||Ԣ{{{{{{f|ǿ||Z|\|F}W}y|z|t|{j|ň||p}|{{{||T}@~ĹW~p~b~Z}D~M}}?D=?d~W}Մ}že~\~l~o~^~C~|}֢||S}{}}=~l}f}D~i~U~U~?V~J~M~W~Z~I~}|}f}[}a}v}w}]}e}}\~m~Ⱦ[~T~Z~f~üw}܋}||ۅ}K~X~r~FN~}}}Ӊ}ȃ}}{}Խ|d}P~}?u~q~>d~S~w}}T~n~H¸SGJFPH^~W~\~k~}T~e~DIq}q}@a~{~Cn~d~\~}P~}]~U~c~r~r~V~W~h~l~Q~t}}z~H|~R~T~ڒ}v}}f~HON~U~X~y}|k}O~v~TSHGDHLORWQUUNIu~\~n~k~p~BNGNNP~ه}I~{}N~x}]~p~C~ߌ|b}u~~W~Ž|||y}І}҉}|||||||}||m}p~cCW~Y~L~Վ}ʍ}U~g~Ŀm~p~Z~R~}ъ}}}r}|}ܕ}}T~ùt}W~r~d~<P~D~Ǿ[~R~T~}||X}_~l}m|{}I~j}}L~[~Ѕ}Q~T~l}~}\}g}G~~T}{{{{{|n}ʿo}^}|{}Z~n}k}|ӝ||||~l|i}|}|||||{{ݷ|||vx||{{|[}h}y|ћ|P~E~N~@~d|Â|f|n~}|{z{T}f~p|_}U}||{L~o}Y}|l}}K~}||x|||Ĉ|l}{}ߌ|L~M~ķa}}@~v}~<b}`~i~nj}j}i|||x|z}J~{B~[}f}q}Ǿq|P}\}O}o|Ņ|}|{b|T}||}p}ޣ|ƿ^~M~Ū|٨{c}F~{||żb}I}Ľg}c}X|˾j|}8i~]}k|D}H~S~E}KHO~Ǽj}[~x:~¸Z}f}Q}ùY}S}ŻM;YO~~}\~7P~H6RP~R}f}I~y3~ϾY}7~}7~Q}^|Z|\|e|Y}N~M}w|\|F}Q}i|{{{{~V|՛{;~B~S}o|{{۩{Ÿ{{{Ҭ{|{L}T}V}óW}u|R}o|}5~J~z6~9~<~лG~v*P~]}_||öG~S~G~5~3~>~8~7O~|3~:~;X}p|^|\|j|O}T}z|{{|{{{}P~M~||ſ{{{|O}r|v|q||9?Żo}J~\~_}o|{K}d~z~z=H~T~V~}n}C~H~|H~?C\c~C~P~r}e}b}e}C~q~S~S~N~Q~w}ӂ{zs{}{}b~e}Y}߉|ܨ{Ǡ{Т{{K}b}~|Ո}e}x|}o}i}}L}̋||q|}N}|Ϲ{«{x||W~?UP~C~?D~~X|}Gk~̌|j|d}?~N~B~o}q}E~K~X~;@;6>z0a~_~=<M~H~y?~@~d}u}B~T~J~G~H~H~S~A~K~H~@~M~C~w}Q~A~J~n}D~H~\~f~8;a~؃}||Q~o}c}h}~L}x|΅}z}~}H~s}ǿt}]~V~e}}Y|G~z}O}u|b}@b>=UO~[~O~|o}Dd~z:~C~=~C~h}Z}^}|}}i}a}|h}k}y}B~u}F~n}{z||U~g~f}||{ٿ{ƾz|O}R}|g|K}؃|c|Y}Ǽp|C}K}o|?}P}5~z/~L}{/~:~;~=~<~B~D~~5~D~E~;~[}R}|7~o}a}e}b}˾]}V}O}R}I}U}b}_}|؋|ƿk}þj}^}j}a}|||z}݁}}W~R~S~k~AJ}|||l}u}F~P~R~}R~}l~˿[~M~}~}h}գ|f}K~J~B~a~a~N~L~P~F~ſr}J~D~x}^}>~?~l}E~H~J~6IX~ƺM~}8~i|{{ջ{w|}}}N~[~b~L~S~z<~=~g}{||I}R}}|Ԇ|[}9~J}V}K}}|φ|ʿe}·O~:~m||o}}U~a~\~οX}N}<~u&J~X~u}M}H}J}b}{~|\}m}s}N}[}|G}}|]}ļa}ž\}_|g|{{{{~G}=~]}U}X}M}U}N}m|W}~=~l|o||I}ם{{{|X|}|~|7~9~;~c}X}ֶ{{b|`|X}I~T~N~K~ĹN~ǸW}<~Z}^|i|p|Ѱ{{L}Z}ŶU}8~z=}{z{{e|f|̓|U}<~G~Q~7?~9~v/|{|{}q}D~M~ǃ|r|Q}ݎ|t|}h|{}e||a|{{{{{{{{{m|T}^}X}d}e}P}|J}P}{i|p|{|{h|{~{{{{{һ{{{{{{{{{{{{{s|Ż`}O~[}C~`~X~H~V~Z~?~źR~A~c}Y}h}^}T}`}=~A~J~b}D~c~}=~|z=~c}m}|J~}D~V~b~;j~^~L~I~S~û`~h~};l~Q~|۠|d}S~{@~|m}ڏ}Z~L~G~F~H~T~W~U~}ā}}}|}|}~}r}|||||g}w}q}}g}|||||}N~Ľc~@G|b}ºd~_~W~m}|ȳ|Ӿ|ï|m}}}h~o}|͵|̵|s}}J~N~݋}s}[~BONDp~@k~V~}}w}e~d~h~»g~a~Q~c~m~f~P~ڊ}N~P~\~e~r~T~}`~k~t~AHLRFJKC}~{~e~l~~KQV~|~l~Y~u}܎}_~CU[XRSN~Af~d~x~DB|Bx~EKQMQM}~C_~}{}P~\~X~Û|ˡ|S~Ux~L~i}}u~{~MQS]]Z~a}||}̍}b~a~FdLޚ}}}}}T~_~W~ݕ}}}}Q~z}΍}ݒ}\~}}\~N~x}X~i~ʿX~G~I~k}l}y||Q~ِ}|v}|w|t|{|Н|ݻ{{r}L~b~»g~o~Z~t}U~`}˶{{{{}|||]~g~j|Ú|DAF~R}m|{||X}e}]}|L~[~}|~}g}|{T|Չ|z|||||{{{{{e}R~Z}g}|{{}|||S~X~|{|׾{|{}v|k||ƍ||{{{m}u}s|}~Y}}Z}d}ɿi}}x}`~Q~F~^~?~|}M}X}~B~S}˾^}|m|^|~|F~_}߀|M}ʿg}V}V}s||N}a}[}n}Έ|ŽTd~||g}W}c}d}٬{P}݄|v}||i}W}g|F~}d~|M}F}xE|W}Ź^}]|\~DHN{5W~`~Z}P~\~w}f}ɾL>M؃}[}k}?<ĻY~H~KA}3@~W~5v*<~K}J}`|{ޟ{{Y|4~ȳ?~M}c|{c|r|b|{d|l|T}c|{N}5~L}O}R}L}c|{{h||{{z|x|o|`}e}x|j|{۩{{d|g|^|O}B~>~ߜ{ŗ{y|L}<~2~B}~K}h}.L~D~7~y3~;~};}h|K}t|{{{||{|||r||w|}V~`~|}ϳ{`|}K}s}Ⱦ[~m}Z}u}d~r}H~j}e}U~s~V~[}_}t}X}}¼c~X~S~Q<~|9~]}]~L~L~T~|}}~y}q}]~O~֎{ɽu{a}{ȯzzzzȎ{J}f}r~u||u}m~E~h}~a}Ȇ|¹o}e}r|{j|Ti~k}~I}Ϊ{ʦ{ݿ{џ|b}L~h~\\~n~̾Y~Bc~S`}}o|Z}g~k}Z}g}j}|Џ|\~ߗ}}b~s}B~]}P~O~D~?ȺW~`~E~_~ƺZ~K~H~Z~H~J~}=~u}B~E~W~~}z=~M~]~x}^~`~{>OBY~t}~}v}P~p}G~i}f}N~V~T~O~G~[}t}c}w|w|EArLP~S~I~P}{|n|a}B~{=a~{E~|O}T}|EPq}|I~E~a~F~f}W}ƿ{{||h}y}@~z}C~~}x}L~O~_}W}ҫ|Q~A~k}Ķ|||x|Y}T}Q}Ń|[}Ż_}c}t}@~h}w}|K}F}ʻU}]}ʾV}{{{Ř{q|`}ϿY}9~@~S~μD~H~B~z:~H~G~y}H~J~}g}Q}F}W}T}i}@~E~k}{}c}^}b}|}I~r}d}y}ށ}u}N~X~]~i~b~ſf~\}ˈ}}}Ԗ}t}I~u}Y~U~~Qr~{}x|n}v}e}|Ǿ||p}w}L~H~t}|p}ÿ{}w}}}I~j}u}s}}t}W~V~]~8)3~4~d}A~9~C}ߣ{ǿ|ƽ\~]~M~A~M~^~@~@~ºe}t}V}Q}c}3~U}X}|{|z|{x{ѳ{{|b}y:~Ʉ|Ɓ|||s}\~f~J~a}пV}D~K~нR}]}6~`}l}{|i}X}L}M}z|c|W}Y}||m}~M}r|m|e|Ļy|l}=~G~@~`}N}\|c||I}||b|s}G~Z}]|zzſ|j}z@}:}`}d}|x>}Q}S}||b|~Y|z|F}µZ}9~ʹG~?~8~=~ǵE~[}T}[}}G}E}t|_}<~>~;~m|ͮ{{{ߺ{{Ǭ{{Ѡ{O}A~i}D}?~S~g}=~ѾW}:~k}e}@~o}ߴ{k|U}C~ʸK~¹|{{׻{ü{ʝ{{z}{Ķ{NJ|͊|W}i}W}Z}N}Ź]}e}N}ƻ`}a}Y}|G}N}b}f}|7~P}{|{{͸{{{{{{{{{{{{{{{{i|h|P}e};~@~B~>~p}@~I}g}A~a}|̫|U}L}O}O}s}H~r}>~o~=w}F~b}v}|g}j}m}H~V~g~N~H~}}n}T~L~Q~O~D~{=~K~M~}S~R~]}f}^~g~P~P~Z~U~M~}|ѭ||ݨ|j}}I~||ƪ|||||m}~R}z}V~a~}ƾ|||}}d~r~YOn}f}^~{~}ػ||{}P~P~Q~S~R~|ͺ||w}x}}b~Z~o~Y~s}Q~k~B}@S^WGz<p~U~Fw~Y~S~b~}u}ł}}}|ϻ|ů|ܵ|h}}]~d~}}V~Ľb~q~h~EȿX[fhbcǾXLBAq~n~t~{~Ay~V~z<LVKHTW~@k~W~q~O¼]W[QKw~W~|}}}}s~@s~~GHJTF}?Ah~h~r~=PúTWJFG}:@JV^~}Z~K`_bGy~l~}a~S~}Q~}V~_~_~g~_~\~p~l~Z~O~S~j~{;~:{7~:f~H~}<~K~M~|I}{||գ|}Ē||{j}ϖ|ͱ{{||{|t||x||{||k}Ά}߮|z~ɼi}I~b}м|z~I]}z}n}FK}T}|}|k}p|h}]~I~}w}|{ƛ|}|ҭ{y}V~Y~ϋ|{||||{|}n}{{||e}||g}h~b~σ|{{zz{{}|Ĺ{|q|{}_}`}k}h}K~Ƽh}m}D?f}e}}t}]~I~g}·V~@b}Ӊ|ċ|l~>;~9~D}>~P~ۆ|@~Z~U~~J}C~K}R}j}X}Q}M}]}||Y}͒|nj||M~}}L~I~f}g}T}||R~{;~z?~w}S}L~E~X}Ź{U}I~ĹW~z5U~}8~B~J~ļZ~@~NM½}i}KCM~S~|3H}E~G~N~s}g}|c}{7~T~VW_ŻNAP~g}l}l}T}<~x3~ĶG~F}B}ȼo|b|[|{{{Ȝ{ޣ{{œ{Ȗ{Ɨ{Ӡ{͢{ݩ{d|~||{|O}d|{{{ž{e|ʺR}z+ǴC~>~d|{̡{{{{D}|4~ӾK~L}W|ʔ{™{ڢ{`|M}@}G}C}:~L}<~?P~S~c}|p|q|{|{||{y|q}nj|{V~S~v|j|q|օ}}S}F~r}L~\}?Ľg~^~N~g}|m|m~:?~[}|Y}O~s||׏|ܕ|q|R}E~H~K~}>~R~o~v}L}P}ûX~[~Uj~v>}J}b|p{Ƕzůzzzz~{ǀ{L~l}[}|}M~@~}}{|ݑ|J~HK}{q|FE?M~c~Y~}ژ|T~]~OV~CI^~L~J`~\~{}ǐ|S}ùz|Lj|֔|q|||Z}}|T}B~y}ǟ|k}|r|yN}O~Ĺc~d}Ň|}P}`}E~x}|A~~~f~x~N~J~~@~T~C~V~S~{}K~}K~s}|ע}{}Ff~|ſ|};J;\~`~g~h~;]~S~K~[~o~}S~|U~[~b~d~m~f~V~n}k}~@~}ļx|w|q}ʿd}F~B~L~~}V~|{{{{{{||]}o}A~{}\}c}D~K~N~w~d~n}|{||̡|֤||{{o||^}_}zA~k}Z}ڌ|J}T}T}c}<~>~X}J}l|Ⱦ{q|||G}F}ɹa|yA}\}`}L}K}|J}M}}G}P}]}ži}m}]}ٖ|`}m}q}n}f}]}||ǀ|w|^}u}M~I~}}`}]}h}\}}J~U~V~U~w}L~T~V~G~e~]~M|~P~E~H~z}ľn}d}q}e}}K~}}m}N~U~k~l~Q~]~o}_}n}h}G~}u}}A~R~=??SDP~T}G}X}g}b}X}[|}}n}[~u}f}O}Ќ|M}]}Z}d}f}w}C~_}g}|{{t{`{]{{|ԛ||ߕ|`}g}r}i}[}f}B~I~r}R~ξJ~E}A}U}w}}g~s}|Պ|؀|O}^}r}^}I}]|S|L}c}ſz|M}x5~?~пG~@~8~7~R}a|{x|d|}{z|J~_~T}y|q}X}z^|_}ɻW}h}W}L}>~y|d|e|Q}|r|d}E~V~b}Z|w=}=~ðD~u4~f}|D}|v|]}λR}{4~W}9~˻K~O}n|I}L}c}Y}f|f|p||{~Z|H}~H}w9~d}|~Z|U}ѿL~{7~;~<~M}s|H}C~d}k|ֻ{h|{{{{{ͻ{_}a}X}ük}d}ü_}m}Q}Q}_}p}H~z8~¸_}X}]}@~D~n}J}~C}H}ʾ]}j}{|{{Ӽ{{{{ʱ{{{{{ɬ{ū{˂||{L}I}b}A~H~J~z:~p}֓|{{{{{ǽ{~|ژ|т|u|ʾd}?~t}b~Ff~I~q}?~H~R~_~l~a~K~D~}r}L~{C~k}|M}źg}¸e}|R}}Q~F~P~J~K~z}đ|J~g~]~M~O~}i}||֞|e}w}{}}e~?L~O~Ŀx}h}h}}_}H~Y~y}^}Y}e}ĭ|||ױ|v}}g~KǾWJ~;j~l~܃}R~N~P~d~}{}K~}s}}I~a~g~]~Y~}v}K~a~M~M~j~ADSWXRd}̩|ʚ|}d~j~w~Eq~^~ƾg~^~~}|҄}W~Y~n~AH|;T~d~T~G~F~P~k~r~g~{8o~i~GƼT[`]i9ig8e]f_ZKGOHSTGg~}˅}Z~CVc`\^fSOPC~=DNCQ~~}[~T~m~m~bCz<A|=r~d~Ŀ}½}΋}ֆ}w~Ua@mJj~`~AHJLz~[~g~X~R~W~x~g~T~T~r~l~T~݋}Z~}J~t}}}g}T~]~O~J~f~;e}|J}U}|}[|{V}~W}|{{|~}r}|~k}Z~i~|}|ɀ}\}m|Ѧ{~|V~CDp}ŋ}Ҝ|{{{y|Ɛ|k~}M~|z|M~VFS~E}^~Ĺ[~W~O~d~M~ˇ|{{{{ϧ|q|||{t|{{{{|||j}||a}}|a}O~l}z|z{{{d||ك|Y}i}z=~L~e}T¼c~߇|p}H~n|Q~k}ý|j}S~Ǿ|zG}|}[}b|Μ|W~{|~|a}m}^}G}?}S|>~V}b|Y|o|yF}`|h}ׇ|q|n}}|µ{{||{{|O~m}Ȯ{z|Žm}}P~[}F~|Z}g}@e}o|j}^~_~q}`}Q}8~f}c~Ak~\~FPd~\~d~]m8:UY}Z}t}O~G~|L}½|Տ|H~4~5z1DbDf~e~Y~D~I~9@~^~|/S}G}Y|{\|{{{ܳ{Ϫ{{{{{{ƥ{͡{Ԩ{{}||||{|{f|i|x6~4~T}~3~{<}l|\|{c||w/~6~7~=~\}Q|ޜ{{{ɿp|e|\|V}R~A}L}7~<~}=}^|{n|{{{{{|ܼ{}\~e}m}]~R~V~t~|z|]}N}U}_~K~Z~F~x~}EEZ}w}øY~J~r|Q~j~q}ф}Ջ|{|t|o}y||M~^}H}[}u}R~J~@~p}Y}P~<f}m}u~?~ȼt||õ|{{{{{ο{ӓ|X}{|o}Ƽc}r|ћ|o}m}j|~}P~ƾc~=c~cT~;F͗||Y}þ|k~t~R~ݎ|{d|_~ELQ߆}e}V~k~}}\}{x||||z}|o|{~j|{ܸ{x||h}z|x|o||a}}F~K~]}|G}P~}}úp}\}zN}|B~}}l}z}||||k}ϼ|||||||s}u}H~M~EQG?=Q\L?H~m~G~J~}}s}H~Q~d}ĺX~T~|>~b}÷c}`}p}r}c}M~V~d~o~ü]~ºp}}Ο|i|{џ|V~A~e}D~n}o};~>~Z}|||d~Zi~q~Y~|L}ʎ||k|x|k}@~Z}V}|}[}d}`}c}T}\}^}Q};~ʷD~@~4~8~˷B~V~D~~0~Z}7~j}e}x7~^}y|||n}x}Ľg}a}c}|}}}}q}W}Ⱦ`}V}üh}^}S}n}w}H~k}z}L~H~ſm}a}o}o}}}V~V~R~[~}7\~M~L~_~d~Ea~Z~Z~y}j}G~O~k~Ed~N~ǃ}f~~ODc~h~Q~|E~܂}v}~}}I~i~:B{5^~LN1~T}||F~~=~_}ׅ|o|դ|݆}v|q|Ђ|v}U}ڌ|ÿ|O}|||[}Q}Ǯ{{{{y||{{|V}a}:~W~T~|ԑ|V}t}w}B~Q~]}ߐ|b}|}I~|}a}}9~l}p}m}\}|N}|x|p|b|ºh|M}k}K}P}z=~<~>~T}A~n}~[|{{j|ց|[|{s|Ճ|Ȋ|a}z}T~H~\}D}T|}|W}T}p|v|ó{{{̧{^}ξM~5W~A~˼Y}s|óV}b}G}y3~N~Ǽ||M}V~;R}[}^}|n|N}`}t}n}f}K}ތ|w|{{{~|U}{{{v|Y}E}V}f}h}J}ʽV}c}L}O}s|{|L}|{{s|Ӑ|N}]}|f}m}v}>~X}_}^}]}>~G~H~@~o}B~O~6~{|m|G}:~H~E~j}µZ}L}o|{{||s|{ӻ{{Մ|Y}L}F}|܂|o|Ʉ|D~^~c}t|u|{{{ٹ{{||||{||V}u}[~q~f~\~C~[~~={=>W~Y~z}ºj}}w}_}g}Ϥ|̦||x||g}{}x}v}r}`}q}X~s~Aǿ_~S~_~a~V~c}}E~h~:BU~x}~<C>Ž]~l}y}n}h}|¾}p~|=R}m}J~L~i}X}r}H~b~?DHTDl~d~<HE{9FADE@}@b~^~]~p~[~~}}F~R~Q~c}L~[~m~GMIz~B|~q~E?~>@GH@]~M~}܌}L~R~d~@EJNFA?9<9::x6k~::<::EGD:X~v}ڂ}f}~d}}d~x~t~v~IXKASRUMe~W~U~FľXdc;@>9;{8XCF\^ZURIE~JKH{~o~z~z~Dg]Rü[Kk~r~Ep~@Nh~}M~s~U~}́}|}a}_}T~`~d~R~h}w}Q~Y~JUj~t~}{}W~}R~}}v|[}a}^}ˎ|V}dž|—|a}\~g}|{f}~J~d~}||i}̠||j}}=H~`}|f}z}~t||}c~~}||r|ڈ|݃|P}E|}>~5̻C~>~f}m|w}M~}r}˗{|ֺ|||Ѷ||{{{{{`}p}ս{Ӧ{p}a}{|A~]}O}ž|||}|ܓ|Ə|p|Ӆ|s}]}A~I~N~|K~N~l~Ŀg~Y}m}}K~Y~žo}υ||}}V}\}}n}{{||~O}K~s~q~¶c}BW~M~t|́|U}]}F~˂}r}}C~E~`}Y|z[|Ը{a}|Н|˦|ÿ||}||e}u}P~}}S~y}^|֍{C~>GYd~O}|v|E}_}̿i|e|_||b};AJu~DZ~N~[9KºRG89?S~||ۇ}_~W~a~D~?D4EG~}N}d}b}?~n}˾S~3J~B~a}a}J}d}e|{g|}e|{޲{̣{Ԥ{Ě{ͣ{۪{ڹ{п{z|{{{q|m|X}~3~}H}{D}yB}b|{h||6~X}p|b|{{ߵ{ҟ{W|d|{Ȭ{{{k|J}_|ȼl|L}P~ιB~M}[}s|^|{{ƽ{{ˡ|||{{{ͦ{U~~z}|b}\~O~X}a~I͆}|}B~W~e}FR~Ӥ|b}~5z5\~I~E~?~EX}J}J~\~`\~zQ}Tž^V~X~VFe~V~J~ɿj}X}n}g}øb}e}?~y8~O}W}ރ|]|p|º{{ʲ{|r|E~W~{9QCm}k~SN{}W~e~n}p}N~k~L~De~\}n|}@;l}@~CŽ[\~˃|T|ER~y}|Ȳ{]}L~d~}|{{V}l~~I~c~Ds~{}{Y}|}|ǚ|e}|{_}[}z}|{{{{b}a}b}|Y}|||i}}|{Ĺ{{t||y||{y|||||~}X~U~t}̀}G~O~I~o~??q~x~|}{7ǻY~f~a~GN~E~C~q}P}̿b}B~z}K~A~^~P~b~`~I~{i|zM}b}|;b~J~T~ʾN~<~N}}|Ĝ{o|{||U~>ĻV~f~GT|.M?;~6[~W~G~G~H~6~3~:~A~8~W}±M};~Q~y/X~E~G~A~ƴ@~>~9~Y}x.~?~_~w0G~]}}}G~n}żY~}}þi}R}b}ƾg}z}|C~{}v}z}ƾf}׋||Q}ƿj}~}ýo}^}|y}Q~|>~}||Ȍ||žt}G~K~I~X~c~]~b}U}ɝ|~W}v}V~|h}ŀ}M~T~c~]~V~H~̄|N}r}R~L~h~j~`~m~=W~b~>@S[I~:~@~ŻN~X~Y~}=~{W||c}h}ш|x|{h|S}x|k|r|o|{Ͽ{ǣ{{{f{g{{{{͖|Z}}J~s}X}{n|ߐ|žg}F~L~d}I}|^}Q}U}~@~H~z|ט|r}A~M~U}@~J~C~l}f}\}y4~3~B~<~}4~ƹU}H}H}k||M}Y}Y}M}C~r}s}p}{~q{֝{d|I}{{z{|И|V}n}]~G~B~T}j|\}N}|u|{{u|{f}L~:~[}~7~ʹW}^}w:}y=}J}w0~ʼW}t|o}R~F~:~J~;S}l|{J}|X}`}r}h}؝||||{{{{{`|f|p|H}{|g}g}P}Q}N}~|{|{{{y|a}Z}}K}Q}||ܷ{}||S}X}N}V}[}N}o}H~H~=~a}@~T~óG~A~=~^}@~b}b}?~A~C~p}O}Ǎ||y|˂|{H}ւ||~7~|6~Q}U}Q}j}]}c|L}^}e}x}¹e}|z|{{|{;{ʻ{{{{{Ș|̓|d}~I~F~P~t~@d~H~?~_~b~J~C~k~_~k}||o}U~}}}d}|ܫ|||||O~p~V~P~k~X~j}||O}D~k~f~V~T~e~E^~G~x}|||X}ȽV~8g~S~K~F~:BL}9ļ^~V~~F~R~d~b~\~T~W~c~m~h~r~t~?q~f~FTTûXG>Q~s}i}l}c}}}M~Q~V~DCBAAFDCECAj~i~s~|<Ž]~k}m}{}|ş|٫|p}߇}H~J~X~o~~@?EBn~}9:9d~ȾX~^~d~g~»[~}b~U~{=EMDa~X~û]~^~p~_~փ}}K~}_~Fagkh[JIAp~Ľ^~\~>v~}P~u~m~\~T~e~w~JRUIId~c~z~CHI`WKOUi~L~Ձ}k}x}}f}Ѫ|a}d}]}f}c}\~CGa~d}¹y}~}Ӑ}w}ԇ}O~{:}_}M~L~R~R~r~c~΄}e~v}|Q}|||{}t}|Q~JUBJ~E~g}}z}M~l~|}}{}C~C~|Z}|{{{w}p~{}||O~}S~D~I}f~97LY}ď}h}o}ė|c}^}|{{{ô{{{{{{|{ȕ|t|{{|w}{Ӹ{a~@O~s|{w||Դ{[}W}R~a~Ę|v|̏|c}_}P~N~֢||]}׊||N}O}yM}Ӈ|m}[}j~K~X}|u|ʷ{}W~̌|w|p|b}@~N}z|K}E~v}{{{{f}K}ܛ|Ȓ|m}P}I~h}k}|Y}߉}_}\~ɿ]~U~D~a|{Ѓ|ƻ\~~6|6WU~ĸR~;~g}H}z|w||s|z{{|m}x5~8~HKg~Ž\~@I^~V~q}R~7D9LBZ~R~j}m}\~MK?ZNQ~y}_}|w|s|W}}<~d}{8~8Y~Y~X~U~e}]|d|{ժ{{۟{»{{׫{p|||{|~|}|v||e}C~z1z/A~B}4~M~оF~K}D}@}{[|Ш{ҟ{ޤ{۰{Dz{޸{{{}|I}ȹU}A}̽T}d}õO}9~~9~|I}{z{{ps|rr|{{{u}|R~`~|||О|||W}`}|p}t~c~T}l}a}[}p}z?~|U~l~f~h~Ǿb}º\~N@Z}d}T~OQ~e}øR~\z~}T}i}V~V~HB~Y}n|]}V}P}G~H~`~E~v}|W~[~I}N}[|k|c~Hx1:ld~K~N\~z~J~{8n~[}R~c~i~ǽV~gNu:~`|g}i~M~K~9HM~{>}r~EU~|{z{y}ۉ}i}͓|x||k~MEZ~Q~z}i||}j~ý_~k}^}}}[~w}S~Q~{6}Ϸ{ش{θ{{{||~~|{|{Է{{Ľ|U}i|r|W}o|ž{W}R}|||Ƌ|ȿo}S~\~m~}}D~O~π}}_}J~]~||`}b}h}K~¹\~ùX~l}~>~|<~a}B~?~j~ƼVFMĽXýQ|}e{s{x|}P}V}\}S}Ѐ|i|Ⱦx|l|p|F}I}N~ȾS8E^}ŹW}K}ĶQ}ɼO~|3?U~S~W~;~=~d}O}e}_~Z~K~H}G}}2~4~C}x|e|f|Ľ{_|^|ȸc|F}Z}[}A~µJ~z4~ȸS}|9~H~;|7[:]~P~x}m}k}w}e}g}t}Z}|[}i}E~}l}x}U~[~N~m~H~K~w}c}}E~o}A~E~K~J~E~v}i}z}r}K~b~l~~@CKLc~n}J}zB}W}C}d|`||ľn}}E~w}D~d}E~=~@~>~E~{B~S~AAa~X~E~~L|l{{|Ń|{{u|{|{{{{{{~|||m}K}ҕ{ɦ{q{|C~O~@~t}@~U}݂|ǝ{z|h}z=~E~9~8~·Y}[}p|f||K}q}\}G}P}Q}Q}G~K~S}G}̾V}ͻF~E~H~I~b}|O}݄|d|l|p||M}]}U}w}C~H~e~m}߂|Y}~F}\|{{z{{η{|{}ބ}J~d~AN~V|p|»n|u|f}i}r|d|i|̿]}8~Z~T~x|i|{Y|||\}K}M};~=~m}{:~R~I~F~U~p}r}w}y}|||||{|{{{{{a|n|х|R}N}Q}G}i}b|{||c|{{{Dz{[}t}K~<~zP|{|||R}o|Ѐ|L}U}e}E~6?~Ւ{Q|]}z6~B~B~L~R~D~`|{h|u}s}J}}|}|G}I}x|:~>~I}|}|N}j}m}G~ɿa}Z}}8~Q}p|r|x|R}^}a}q|u|{{{{Ż{{{y||ך|}||ߣ|[}G~ɾZ~U~˾S~b~R~`~F~c~CLd~~}r}v}T~Z~]~i}|||||¡|Ҡ|||||||g}`}V}Ži}P~?ƽQf~>=_}|||Ѣ|}Ⱦ[~`~S~@~|@~b}Ǐ|W}}=>ĹY~g~P~z}M~b~K~N~S~^~L~B~O~¸Z~Z~Q~{}g}π}a~q~AFBHƽYSED};i~V~^~`~i~DI>?~;j~Y~Z~O~U~M~i~Cu~a~M~l}ֽ||w}^~j~Z~i~Z~N~\~ż_~a~ú[~l~y9AúQDGIĻT]ƼRRHh~S~P~Y~^~؈}I~}_}԰|h}g~W~h~k~W~_~}:HFD=x~n~t~l~}|}H~o~o~~=AEDCb~}}}x}h}{}R}z}Z~b~g~^~c~M~S~R~ۀ}α|h}p}}|ǭ|Ԩ|c}¹j}X~t~Q~p}p}d}n}F~l}e~Bs~X~}v}t~ѓ}|b}~O}k}ǯ||{}q~L~_}V}j}P~¼v~|~z}t~s~X~¿|y|u||а|]~l~U~o}T~Q~d~f}ʟ|{{|v}}X~i}j|{{||X|Y~@^}|y~d}{z||y}ф}o}z|c|{}n}ċ|w|{|{{غ{}|c}S}|\}|i|{{n|P}d}|}{Լz{{b|||Ә|]}M~V}Б|\}¸h}|}Q~r}L}r|k|M|I}|m}g|S}Y~}V}^~m~n}d}|~M~{{S}Żo}z@~}8M~F~Y}^|{{|{e}|||h|r}_}`}J}j|߄|ғ{R}e}R~v}]}N}`|t|MgHr~N~o}W}{|}|}||s|m|{}h}Ҍ|}H~g~]Gd~f~J~_}̬|ʛ|~|D~q}ûU~;~I~8Y~:e>Ab~R}1[{49c~[~T}i|v|||r|^}P}>~J~K~zB}^|@}w|{͍{ƌ{{Ө{{{|{{|t|p|{{||ȗ||?~I}-F~I~9~w/~˻D~ǸS}W}O}N}{{{{q|]|||n|J}Ђ|{Ѫ{T~|w||³||{{|{{}|{|||٦|Ľ|Z~x}|ȴ{{{|~||}g~źy|M~:l}_~B~j~~~h~|w}d}ɻW~A~z||{{o|f}|G~Y}g}ZGj}s}b}D~|p}|zX|g}k}v|p}E}G~øV~F}{ƾ{e|X~ɺR~Z}Q~N~X}I}k~KǹR~u~P~E~W}}x|y}Q~C~{}T}p}Q}Y~úY~W~L~k}y}]~h}}f}|w||f|h|||֚||s||f|W~n~}V~f}ל||}}Y}h|{t}~AL\Ľ`~K}zC~}e}i}r}a}s}b}ƒ|e|m|k|p||˰||{g||ѹ{{›{i|u|P}S}_}b}v}]}~|ʼn||K}}J~`}t}ĶX~M~P~G~J~V~U~d}l}M~ƛ||Ŏ}\}S}E~G~>ʿL5??TV~b}r~Y~{ŵe|}{:~A~h}u|k|g|Ƽs|P}k}~>~M~AVJ\~M~I~O~ɿX~7:S~t}·`}z;~J~=~g};~[}{4~B~|>~o}o}A~=~w2~c}E~˿N~:~E}2~]}u/~ȷB~75Q~V~2>ʻF45I:~=~>~?~Z~l~Z~M~x}F~;~g}~}Q~T~J~@~t}@~H~m}A~v}n}a}X}\}t}C~E~m}n}H~N~D~M~c~c~V~a~=;k~s~{~}G~{@~a}J}D}~E}Ԍ|ܑ|R}z}{}M~S~ºW~d~[~C~r}T}Y}B~T~~6@Dk~y}{ƿzzzz{߹{u|{{{~{{{||{W}Z}S}S}że}n}?~I~9~V}d}[}Z}n}^}h|ی|[};~E~<~H}{u{\|K}<~A~ǻY}8~{|{{؃|\}K~³C~P~O~F~>~f}g}k}Z}|q}^}|r|{^}z;~A~Q~V~o}E~@~n}b|z{{¨{||u}O~P~c}E~F~a~^~=~ׁ{{½{{{{{{n|T}M~P~E~P}o|h|s|d||G~I~n}<~=~C~j}H~N~Y~{}ܤ||||||||||{{{Ѳ{j|DŽ|x|I}{v|e}_}J}F}T}|¼||Q}K}X}H~L}{‰{|{{o|g}P}^}g}ǺZ}X}^}L~]~\~I~Q~?~b}b}P~3w0I~P}j|j|L}j}U}h}j}|4~P}݃|e}|/W~o|{{d|Y}m}I~J~:~T~~3z:~r}k}]}|z|{{||{{¹{{p|x||{{|\}u}R}|چ|p}ƻ[~\~øT~[~|7Y~ɽQ~]~P~ʿh}L~`~s~b}u}|J~f~b~`}|F~g}||Ʃ|Գ|}t}|D~||||s}}:^Y<b~c~_~J~a}ӡ|a}E~N~J~|S}ĉ|`}k}I~H~O~;VE;c~ʿ_~U~F~Z~X~I~]~ûa~f~c~_~|=x~X~h~<KEJIJGA{;t~;c~l~k~|>DKF<>CD>AAw~W~P~}N~_~e~U~N~}}K~[~p~m~\~g~g~;BGR<h~^~p~}:c~[~P~O~X~^~ƼY~q~u~u~N~ʯ|u}i~ļf~\~S~|G~}}L~y}F~J~Q~t~BKw~M~}]~}E~ľr}l}d}`}}L~v~g~T~]~f~}APOGN~O~T~Kr~CKH}~j}|ħ|o}Ψ|ү|x}}R~R~N~n}q}ݩ|]}K~|}v|{|t}g~K~}W~n~`~g}|ԁ}I~{S}w}}a~`}|]~Å}[}Hv~i~}|Z~r~q}||||||f~܈}¿|t}ʤ||r}͉}i}]}o}R~X}{{{{Z}W|}P~w~h~n~}|{z{|d~f}{}A|=Ec~ч}̥|z|j}|}g|N}؁|c|_||~m|{{q|ĺs|ۂ|M~֗|^|i}|h}|{^}ʽi}K~{P}Q}o}~Au~y}ºp}S|W|D~E}}S}i}N~W~}k~ȿt}Ӊ|ǽX~>Y~}T}{{|c}X}w}m}}`~d}[}{||||||y|֐|zJ}ń|b}x}|L}M}T~l}m||e~P~Q}yE}|H}F=h~N[~_}a}S}||||||V}b}]~D}j|`~8EZg~G~>~h}Z}|||ӏ||l|q|ֈ|R}H~_~H7[}9AW~e~>^YP@A;Y~W}½||G~I~J}^|{{{ў{ʛ{Ι{̚{ԧ{{{z||{||{{{}||||V}7T~w1~~4~c}S}>~:~L}{|y4~C}z|p|x|Ǿ{|c|{f|p||{{{{}||{{{||{{{|||||~|ӡ{s|P~|Ĭ{z{{{{{p||}U~R~S~x|o}^}CP~ف}|ۿ{||W}}Q}\}Z~~|q|}|y}F~[}}N}ߐ}d}k|Z}`}W}ی|ݎ|p}KF};Q}{{Τ{}R~M~|w|ŗ|m}e~W~@>C~K{:K~F~_}Z~`~]~^~ȿ\~}|]}[}P~j~W~S~ٔ|Σ|¾v~~zP}I~JP^}a}|s|}a}z|y|}|_}|U~b}o~ߓ}k}Q~V~֞||݌}||x|j|o|q|u{}{Ϭ{A}ͻK~]~{9~v|`}{Ǿ{|y|o|G}^}|j|||Y}U}V}w|^}Z}J}|I~@~M~j}X}Չ|l}`~FHm~Bo~n~j~]~G~DGD?@d~Je~k}A~R~ùNP?U~y5~<~K}x{v>|W}n~VW}9_~úU~H~]}W}V}Ǽe}H~@Da~T~I~P~P~a~r~P~~c}||Τ|j}O~O~T~7R5SU]UZQ>G~e}u|K}M}y4~E~H~`}s|Z|Y|[|@}̽O}7~D~@~f}b}B~x}c|ºc}N}|m}E~V~H~~@~\~T~j}]}x:~A~j}k}l}W}S}||}Q}T}e}h}x}K~L~I~B~W~[~<g~O~ļU~A~O}[}b}r}T~h~P~_}Š|W}X}x}{?~I~I~S~R~I~L~;~U}4~C~ĺS~ŹO~P~]~d~=~S}|[}Y}q|{{{{{{zzd{t|ݚ|U}`}a}<~~;~D~O~S~9~8~l}Y}S}T}g}||Ț{x{{ȡ{c|؍|b}N}|X|_|>}S}~9~n}_}k}i}N}h|q|T}>~D~D~=~P}G}f}]}x|{q|Q}P}ºb}ƿf}@~?~q}p}=~P}¸c}m}y}z|¢{Q}ļe}r}{>~w}T~X~`~B~m}O}H~ļZ~KJ~؊{b|e|{y|{ɷ{{w|Q}?~ɹN~};~|||H}l}H~M~L~H~F~~;~E~~=~N~|||||||||t|h|ì{{{{~{{{ĵ{{ǻ{{{|Е|k|ͪ{Ľ||]}t}l}}8~B}x{w{{{{o|L}i}w9~o}q}ɿa}@~D~B~J~O~U~W~[~h}c}P~U~F~k}m}|n|y|σ|Ӏ|6~>~ͺI~}5~˷E~2a~=~l}e}p}y7~^}D~ijE~ʼK~I~{:~I~`~}4M~M~=~^}||w|v|{{ǹ{î{{{{Z}ʼn||T}^}|l|ȿ|_}a}<~P~;7Y~G~Q~U~V~h~I~h}v}w}~G~J~r}R~g~AX~s}L~Y~U~K~z}e}r}m}\}ºj}I~b~@:T~F~|}s}~S}}N~B~=~úf}|>~|?~|}Z}Ø||U}p}a~~9I~x}h}݄}V~S~Z~r}a}z}R~{D~}o}d}ǩ|̫|ѭ||ǃ}؊}֊}M~S~I~}F~U~]~F~ԁ}}W~]~Y~S~V~_~}h}}}k~J\c^NNIKt~>>m~Q~J~Ž_~g~g~?Ai~~D~E~t}u}L~a~l~}:=MVJC@>EFCg~Z~CRNt~o~r~GKQKA?A^~f}̧|||}|||}V~c~T~}}U~b~i~i~d~Y~]~k~r~<h~y}|||ԧ|d~=>S~g}E~F~H~T}¹^}`}F~h}p|S}x}a~Ŀy}[}Ʊ|s}q}X~\~~}ԁ}ž`~~F~g}|Ʌ||D~R~}}\~T~R~i~p~T~j~U~e~}˼|©|||||_~KĹZ~ԡ|||{}|ͦ|h}`}f}s}g}v|{{{{ͤ{s|}?ƻ_~V}و|G~}E~Ԇ|c|ď|yQ}p~{=}>~V~M~7v}|{{u|S}K~F~\~o}n|z||P}ބ}{s|l|M}d|r|T~O~|o|w|h}s}a}m}`}G}G}|b}J}Z}X|a|b}Q~|N}Õ{[}t}o}ܤ|}žx}e}v~}`}U}d}ן||{|{|{e}|c}||}H~t~N~f}K~@~Ĺ_}U}m|m|W}r}̿i}ƺa}ŷ`}@~p}K}|>~e~{>~h~}h~O~I~Kݙ||g}˕|X}h}}P~M~\~ʾ]}G}R}cN;D~u}}}}r}|||||~||e}ȿX~ĹK~d~8üV~?~»g}H~@~T~E~H~I~g~T\I=?9.D~W}t|j|{{f|u|{{y|~|{{||ð{{~s|x|||||~|W}\~L~8~X}U}I~K~M~Z}~9}5~4I~U~b}R}U}j|p||ב||||{{{{{xw{{{{{{||{{{||zz{{{{{{{{|ŷ|Y}{}f|z}nrHd}Ф|{{~O}KQGƾa~}ӗ|~{~D}e~\~\~Zu¹p}^}L~I~H~}|F}ANET}º{d{f}=?U~{|^}]~}ȿh~b~Rh~j~F}~ۓ}||i}n}S~A~Z}e}g}ف}W~y}K~Si~EJ}~}u~R~~~~r}܎|́|}V}j}j}{z{||ܵ{{{ԭ{z|ò{ʓ|Z}}Y}l}ۤ|l||¼{{{x{w{[|b}t}`}q}h~n~M~ۏ|]}Q}T}]}z>~q}W}M}k}y;~}?~x}}w}w}y}D~w}K~Ղ}M~T~W~`~n~T~JK9F<MYjm~<D4^ǺM>;}e}G~P~V~M}Z}~5X<>?C@77}4_8??FL~v~x~z~Ŀj~Y~X~}Q~s~}~~~~HLJ>z5`~H~e}Q~H~\}|j}d}i}ُ}z}d}W}t.~\}νP}W}i|{[|A}X}H}5~k}?~\}b|_|{k|P}x|i|Y}Z}`}V}Q}b}|Š|^}ļc}||a|z|M}K}Q}^}I}Q}^}d}g}`}O}S}r}@~F~{}D~H~N~v}W}F~T~|}A~y1·V~R~R~J~K~Q~L~F~M~C~C~ƳA~9~n}g}E~E~;~B~m}i}h}[}|b}n}m}}x}S}V}||||Z}|k}Z}w}?~?~;~a}T}9~=~\}m}}<~\}s}n}P}@}O}u|÷{|X}[}Y}X}\}R}T}`}a}k}d}m}c}[}i}r}Ļ_}W}|H}G}N}g}V}?~{8~F}؃|~G}|N}S}S}]}=~u}}=~^}^}k}E~?~v}V}P}ɾV~L~W}|>~D~O~L~=~E~p~p~>>i~F~ތ|{p{ج{t|z|˽{{´{j||P}B~@~q};~Z~ɽQ~N~ĹN~öN~q}B~@~R~P~~D~\}||||||||}|z|w|ո{{{ï{{{{{{{{{{{{{Ʃ{b|¤{~|R}N}P}{{s|ˉ|V}|M}̓|M}N}P}ƺ`}l}l}x8~j}O~?~A~z/Z~C~e~\~8a~|u|{|g|e||m}8~H~?~_~F~Y~ʼP~m}Ʉ|b}F~S~Z~_~9X~N~I~x8~\}r}};~p}i}K~F~`}j}||ۏ||{ɷ{ʶ{w|Y}z;~;~[}k||||~|||?~Z~9?<?=v~i~l}}J~r}m}H~}}V~<U~S~h~C\~D~t}o}b}s}]~b~N~g~øR~Y~i~J~w}i}|||m}q}P~J~[~8<c~Z~f~c~]~]~¹]~T~{}˰||Ӯ|Ԣ|}¼u}ӯ||׬|ɧ|έ||в|è||ǭ|ʰ||˄}N~M~\~Z~{:BQP^~c}g}V~X~}P~P~{~Pr~T~ԉ}}R~m~FWNH@S~F~H~ż[~q~z7<8=f~W~M~I~a~ǾY~F~@~B~D~L~O~B~`~;=g~ļX~I~}n}J~`~R~Q~K8|3_MB@<h~e~_~q~i~[~_~X~|~}a~l~^~T~Ձ}h}W~R~k}n}}L~M~[~V~R~V~}}}}؀}ī|||a}[}Ǣ|ɧ|մ{ɛ{{{Y|Q|o|v~_}_}n|g|Z~y}W}њ||z}o}s}\~}Ža~U~z}L~d~m~>^~v}l}y}|F~B~|L~x|s|S}h}P~}Y~g~i}_~ž^~F~P}|ũ|u}}v}Ӓ}|}Ѐ}{W}g}¼u}}S}|}OD~t}b}q~q}P~P~B;f~U~d}V~t~r~~U~|}~w~V~ؔ|{Ͷ{ƈ|Ļr}n}||{{{{{{v|y}t|½{v||o|n}P~w}s}I~v}yE}Z}TK~e}j}|`|ƺ{|I~Mu}K~ɾ_~C~}|CE~j}u}F~F~i}`}ь|j~b~Z~Ŵ|{ӵ{S~z|f}}T}|ɿh}G~H~[~<W}n|h}]}zX|{{m|}Q}d|Q}N}}:T~G~dDC>F~P~~~i}|u}Ǽ|B~K~AB8h}S|ٙ{Q}D}|9FNPo~\~C~{}V}‘|||||Ք||]}}n~C~y|j|o|m|f|n|x|{|L}@}c}IXCDI:@66Q~=~>~c}N}o|q|q|s|{͵{޿{{{|||||B~h}n}x8~S~U~_}U}o}?~´C~óA~ͻA~8~F~r}~;~{9~Z}úd}ۈ|k|o|||||||U}ߓ||r|u|{{{|||||{{|{{{{{{{||{{{{ļx~R|Q}Y~DP~{|y||{{{{|՛|[}W}|Źi}Y~M~;AƽYM~~|||\}|U}||y|L}m~e~S~q}u|ÿp}M{~ڛ|q|X}b~[~a}ԑ|T~w~s~N~z}}}t}]}j}`}||n|x|h~}͑||]~?}B~~?~z}ϔ|~e||ϴ{u||a|||xN}{T}e}a}ý{{{ī{o|u}^}w}T}s|ӌ|}q}n}Z}V}c|;~ƺl|d|q|p|{G}i}J~B~e}K}L}U}<~ĸR~õO~=~e};~÷Q~Q~n}V}n}i}U~R~R~}}_}a}S~q~z<f~d~`~Ļ]~}R~}s~l~Z~a~z7ƿY[M?~G~|2z3b~F~N~?j~D~E}ƱT}>Tb69@>8<>=E>G87HLONCi~~N~j~i~AQ]MCx~a~I~I~V~Z~I~޴|e}i}{}s}z}k~f~F~I~V~ȼL~;~K}E}1~ѽD~4~|0~7~N~`}{_|V}j}q}T}|T}{w|c||o}|c}E~I~E~j}a}U}}|o|f|{{b}}v}d}R}R}e}}}F~H~c}]}b}c}^}m}|}żW~~6T~:~X~[~\~6<6]~_~c~U~^~B~G~e}H~V~ͿQ~X~m}`||e}|B~E~~}u}{=~B~F~ºa}ֈ|{{|a}u}y}D~?~N~M~H~Ԗ|}F~ƻT~J~;~=~h}l}h}{;~h}d|{n|Ѭ{ſ|s}_}Q}|Y}l}s}E~u}<~s}W}ǿb}N}i}e}m}h}e}n}C~ŻW~i}d|j|P}z5~]}c}T}`}>~F}Ǻs|L}@~G~D~D~}:~i}j}S~f}A~z7M~o}D~L~¹V~S~n~@^~f~}:o~c~O~P~B~|k|u{{{{{{{||l}Y~?g~Z}w}H~]~v}P~k~Ǽ^~E~|||||||{{{r|l|Ƕ{{{{{ͳ{Ǯ{{{{{ɲ{{{{{{{{{{{ {}a|ͮ{̇|zH}W}}J}zG}{{f|P}||P}R}>~\~j~g~?}5S}]}C~6c~A~P~zP}|`}j}D~J~[~Y~N~T~;~B~{:~\}h}i}|=~G~ȼV~^~764y0W~O~t}˿d}{<~F~J~C~C~n};b}<~v8~;~]}^}>~m}i}<~`}V}|v|q|o|||Q}|?~d~J~W~HUVg}M~P~S~\~|=z}||}P~Y~q~}}s}f}u}|||{|||݂}~}÷[~@78|6O~|?~T}||||||U}x}c}p}g}g}J~s}A~s}|||ܴ|||^}H~l~HLc~i}m}p}p}}}N~V~e~[~U~j}|d}]}]}ބ}Žc~CLg~MSHPǾTKC~=q~g~}C~l}ـ}m}x}_~}8;?GDGAK~W~I~K~X~U~O~}@~v}}k}{P}}}N~u}q}D~~A~J~X~;EFHYib[Ee~E~q}e}z}}H~S~P~|||k}݇}J~i~o~v~Z~S~K~U~K~w}I~L~q}}ڃ}}}U~a~a~}ԣ|Ÿ||||||||u|||g|·{z{M|Ӂ}О|||u|b}Ŏ|m}Î|̈|H~Ԃ|b}[~L~O}P}s|||¼|j}S~H~X}g~y/V}v<}||}K@w}{{|}ސ}ơ|Ϡ|e}l}Y}||j}{}||¼|ԕ|_}u}g}JTùT~X~L~[}d~`~[<FK~}9?`~|N}ɾp}y}[~M~m}L~ܖ|{{{{}}{{{z~{{{{{{{{{M}@~|Z}{}u}e}U~C~|}W~|}{}R|N}v}k|{y}׈}Ǥ||˾S~v}y}||O}L~9C~R}}K~|_~\~آ||Z}|j}O~y~}v|S~|ĺQ~R~O~w6~f}?~:~z9~_}~}ùc}V}ŷZ}}M}[}ȼ|v}F~]~\~s~T~`}u~U~;H~M~K~V~Cf}|r|u|Z}i~P~]}b|}~F~x|K~Ii~XKS~`~{5BúW~[~>~|Z}\}f}\}}}\}R}|؏|Ձ|z|I~A~f||b}·X}f}H}I}k}L}˾V}h}µR}C~RWMĸIͿ_}o|{n{{{e}{ɉ|ľY}P~EI~h}|̟|W~V~H~U}V~O~[}C}N}H}c|_|ʹR}Z}5~˺A~=~S}w|n|||~|l|||u|{p|u|{||o|׹{{{|||{{||||{{|||||||{{{{|}n}ſh~GJD_~ƾt}^}ɿk}m}}q}}w}`}}S}Żp}c}\}z}ڠ|k}|[~}||o|_|n|d|K}j~U~d~j~a}ԝ|c~V~a~z~k~ْ|c~~^~]}`}G~s}ùo}|b}{E~R~L~j}p}n~JP~Ȍ|q|d}b}d}|^}ڛ|{{{Ċ||g|j||{ƻy|F}ƺj}m|Z}L}S}|h}l||c||]|||p}Ʒ]}g}A~N~q}l}o}C~J~A~o};~_}}4~;~L~n}x:~m}@~B~y7~=~i}z7~k}k}}<~L}G}[}K}B~H~Z}|:~y<~`~K~U~K~|H~o}d}}W~_~Y~Hh^]_~>~]}^}=}i}ȾX~Y~c}i}_~6[~A~c}H~<~A~J~c~^~\~k~9EHG[`c86ȽQ_9::6dkA?fWIt~k~]~Ƌ}Ғ}j}R~ľf~V~|˪|v}M~O~u}W~u}g|{֪{{]|}|v1~<~4~[}9~E~P}{|]}R}l}g}M}U}|{P}H}W}P~g~=7:|6G~~}{}||r}Q~U~Q~T~ù]~g~`~Q~b~Ǿ]~Z~L~H~N~\~m~{:F^~I~I~U~S~B~A~ƷI~\~J~Z~ȿV~ȿU~B~J~@~X}]}s}b~DI~k}||[}}C~p}T}ƽh}ƾd}b}|ٵ{{{o|M}{}Q~R~^~Y~I~}b}h}K~ļd~V~|}ǽg}b}a}{;~B~e}|{{{{|ܝ|S}\}b}]}E~I~D~H~E~F~}:~{7~@~|;~G~{<~S}?~L~U~a}w|M}]}g}i}f}p}c}d}e}K}|h}g~c~Q~|>~n}g}_~>b~}9l~ƾ[~i~AMG@=r~d~n~\~[~@~B~c}V}L}B~b}ʼ{_~7Z}l|w||||ƌ|||||}J~w}a~T~J~k}J~ͥ|||||||a}q|x|N}e|{{}g|{{Ѹ{ī{ͳ{պ{о{ϻ{r|{޸{˰{{{{{{г{f|}a|{}J}l}F}^|{߫{ڱ{Я{ʰ{{{f|d|k|b}˾b}N~F~x2>Q~e}V}X}˼Y}´S}9~d}@~c}S}R}<~˾Q~B~V}b}@~G~>~<~x:~|ɿh}a}k}i}J~X~}264z1_~D~Z}|ǿ||Ӊ|û|V}v}B~C~@~C~Q~F~@~P~Z~T~Z~H~Y}u|ك|@~X~~7i~d~\{59m~M~^~Ļ]~k~c~w}ײ||j}}M~ˀ}K~R~W~}K~K~b~½|}|||D~M~ǻT~]~EE@f~}^}|\}ݚ||||Γ|Y}v}H~C~q}l}K~S~\~]~S~W~E~i}ýt}I~m~OQMǽVF{9q~n~=Rf[~>m}}M~c~?CCWHI?q~p~BBDCA=B@Z~k~g~y8;9l~I~ɞ|ק|ޟ|}p~;m~A=H?[~W~ƾ]~C~D~P~_~e~g~ǾW~T~Q~V~f~k~m~_~z~<:j~?;l~h~~}r}|Ǥ|b}||i}}݌}|{}ǿf~Ӆ}|||ܑ}`~F~u}C~s}O}l}Z}`}\}|l}}}v}|­||||]}ɚ|Z~m~N~_}܀|Z}w}Z||l|o|Ƿ{{U}֥|s|v|՞{yH}j}\}q}}t}~}}p}|M}w||{n|Ϗ}}`}Ώ|e~m}g}L~Y}N~OX_~|T}m}}W}o}||Ǿ|Ć}T~||ſ|}^~V~K~M~~<N=j~{I~Z~Y~l~<G~;~<~i~emy~M~ǿk}f|w|\}l|g|q}ғ|ü{{{{{qr{{{ܱ||ɲ{{q|{{{h||ג|ے|Օ|d}}}d}K~|}V}|g}S}w|}}¹n}X}[}}P}|Q}O~w}}t}H~E~h~h~K~d}r|T}L~x}S~W}X}R~}o}q|t|]}`}|r}b}9~˹F~B~c}x}v}\}øb}̼M~C~;~z|r|w|||U}W~\W~׆|m|ĺt|A~[~FFz7I}d|q|r|x|]}A?o~>P~}R}f|x}֋}X~_OIƽLJ[~<~w}_}R}|u||o}K~`}\}d}_}u}E~c}K~S}e}z}A~V~i}υ|y|l|ȼe|v|ƺT};~B~=~{2;E~ǾU}?~||:~X~M~B~<z}q|8J`~]~`~[}`~M~3:ȺK~{B}G}z3~νY}y|{||}c|j|=~[~G~O~o}݌|g|r}Hy}{{|||}|׹{{z{|||{{{|||{{|{{{|zz|{{||||a~l~{~C=k~`}c}|r}S~y~Iٍ}Ҍ|d}f~}7d~;z7V~i}x}T~I~Q~R~Y}|n|}f|r|q|„|t}T~t~l}v}v~j~_}z:_|=i~N~L~E~ĺd}K~z9c~q~I~C~G~o}˾`}C~H~?~b~÷S~F~[~|?~@~h}{|L~}}[}U}}}؅|[}a}|ݐ|g}¹e}L}S|a{s{q}s}]}m|jzêz||k|u|ƸZ}G~e}S}<~;~~9~d}P}ɻY}K}V}u6~ʽ[}ɽ^}|=~t}~@~˾b}C~C~A~@~{=~y9~j}r}o}c}r}o}G~S~o~l~Oq~O~Q~]~@W`h`ĽR;:~m|E~W~ǺM]\IøR~T~|ƹP~ĺF66U}N}_~C~O~Q~O}A~=~Y~:8h}@~D~O~C~A~U~`~Z~<Cr~<B;8\6;DHDF~A~T}{}|x4~ŸN~R~L~Y}||؃|U}f}<~i}?~=~?~K~:K~=~C~H~E~ȿW~a~a~}8==K=b~U~T~49W~<D9J~Q~B~n}W}k}˽O~Y~V~K~P~ǾQ~C~<~S}J}n|{{|Y|n|O}X}Y}|Ŀz||~|ƿx||W}b}]}J}Q}¿v|Z|¾{e|w|n}N~M~\}F~N~I~}a}ľs}}J~X~`~^~E~C~B~o}}}}Z|{{w|`}n}}C~H~C~C~h}@~B~>~A~D~ƿY~G~B~9~d}{9~u}|}I}p}r}i}T}R}Z}]}d}A~B~|}Z}V}J~G~ŻY~j~X~a~L~R}f}f~R[y4]7If~K~H~t}Ǽ[}K}ǻV}G~e~9@@~π|ݫ{ߴ{Τ{՜{_|q|]~a}t}t~ڙ|U~Y}}}n}||||||}U~_~K~`~\}||Ů||||g}B~l}|}|m|l|j|m|{{{k|{{{־{϶{ȴ{{{{{{{³{q|۵{|W}Z}p|Ѯ{ɨ{ܷ{{{{{{{Ѣ{{{t|܂|f|E}9~V~{19B~O}^|p|T}X}E~W}`}M}q}V~O~;~?~E~G~j}_}ɽ^}W}y}|?~e}π}ſx}}}I~?C\~[~Z~E~|s||ܑ|Q}p}>~:~B~ƹ\}}9~9~b}Z}T}^}:~q}K}t|żw|˽`}z9~~<~[~^~j~Os~\~r}zB~U~Q~||¡|||ֶ|Ҵ||֮|a} |||ڱ|g}b}|Ϲ|l}|ޯ|||i}ȽX~}5|4_~m}_}\}||Z}¹e}\~Q~øS~e~;|6P~j~978=h~<B<m~|59=h~Z~b~P~}}X}q}}C~V~U~U~M~}N~}|}K~L~Ļ`~|?žd~b~ûZ~V~M~Z~e~o~c~O~}T~V~}H~w}o}f}Ƚj}R~T~L~t}|˛|V}z}P~K~E~B~Q~W~K~?~;~ķN~_~U~Z~M~G~}Ľm}p}}G~ǽ\~h~n~\~Z~b~o~|<p~¾c~L~g}|||ߞ|{D~U~t}L~g~}J~r~s~U~T~|۠|}{}p}~E~{>e~g}L~h~u~o~X~i}v}~I~}E~N~a}݄}~J{Bu~R~ôV~[~R~|m|׵{ѝ{N}v|`|||s}}J~U~ƻm}Bw~r}y}w~ƺY~Q}d}üg~g~z}}}ǽj~T~X~}װ|]}~K~]}d}|q}V~H~]~f~zD~z~XH{E~~}r~=\~p}b}y~>EGƿm~c~̉}Y||n~Y~<~IZ~k}|||x||}m}w}V~p}Č{s{{{{{z{{{{|{}֔|İ{ð{{{h|ȸ{~I}w}K}F~~?~f}z|f|Ȼs|EBp}ι{{j}׍|[}T~F~}|Z}y}x}V}P~}E~o}||}t~Ⱦ|ȁ|a}||[~{}|}||n~҇}|Н|]}N~T~q}L~@~h}U}~;~P~J~`}}P}͞|{||Y}φ|\}LP~J~\}ȃ|e|`~HZ~I~`}v|z|ƿ{|f}b~l}c}T}L}L}A~¹b~S~߆}~F~@8y1QA}Q}||||F~źR~[~[~@~\}|ԋ|F~Y~z0}:~O}y|ł|y||׋|m|w|||zC}|>~K~S~A~|{|k|f}?~[}I}=~¹W~{=~[~?Y}Q|~6~f~@D~_~?953ŵV}{l|p|E}t|r|f|j|j|v|}|u|w|u|ל||L}{|w|t|q|||||{{{{{{|{tv|{|~|{su||{~||{{{{{{{|[~~y}}}S~}m~X~}O~I~}a}}W~}p}}Y~?[F8g~|8T~q~G~}Q~P~i}[}q|S~O~CgH|7N~g}|s|b~<n~LG~~5K>P~s}Q}e}n}e}N~żU~:c~O~A~·P~|6~8Y~[~h~f}øV}y9~R}Z}}v}}]}}}|h}}s}O}[}S~H~k}|v{ن{x@}нY|N}L}T}k}|Q}t}l}õQ~H~Ƚe}a}O}d}G}Ź\}`}:~~:~X~Z~N~]~·Z~ĹV~`~;m~h~o~N~V~j~HMC=}7:[~C~|>~P~}8X~`~<~l|};}H}`}\~Ļ[~;IBO~i~XZGȾQ~ɽ`}r}z;~B~_~T~V~K~Q~g~D~_}B~̺W}d}A~>~Z~g~Q~X~[~J~^~c~ʾX~g~`~@ĸ[~>~J~b~I~9~@}S|e|?}e}X~IT5~E~b}m}K~~=~h}J~K~S~H~=~P~d~[~W~T~q~r~Q~Q~:Oz7M<˿S~<~;H~Z~J~?~L~L}X}B7ǽSi~L~W~u}r|{{z~m{{{j||w|c}i~\~y|{{|۵|||||°||j}|}K~`~Y~~<||c}|V}m}?~:~|m|l|p|n|n|j|i|m|n|w|s|y|{{{{ɸ{{̵{{ٻ{Ӱ{ϳ{{Ɔ|ͯ{{{ȷ{ݽ{ܴ{ײ{{{{¿{{||b|{{{ƿ{՞{ϛ{b|L}y4X~g}{|X|Z|F}=~d}=~e}M}V}l}o}x9~L~D~=~@~=~}:~h}n}Q}S}z}ºt}j}T~{5^~R~X~W~[~F~t}z8~h}ȹX}e}^}?~i}z7~8~x4~U}z6~C~?~οL~:~]}n}d}k}z:~C~e~JYN~j~OSFH~ϡ|Ğ||o}}}j}`}|c}}R~r}ȡ|ɝ||h}R~Y}ܡ|||||w}N~F~P~e}l}J~^~N~|b}G~q}K~6D=?<h~9=<d~¶Q~\~e~:i~O~H~h~~8B?j~»b~H~N~^~d~»g~ýi~R~F~o}v}[~¹W~u}|}I~h}F~i~z9a~V~m~f~b~a~e~X~Y~X~S~B~K~T~H~H~P~}|||k|{|T}z}l}|||¸\}ƻ^}d}S}X}||֗|e}u}C~~C~u}c}z}L~}>U~C~}d~d~l~r~U~F~ׁ}K~^~||||݂}~}f}||X}U}c}l}Y}ƹf|B~z~V~}}i||N}۟|]}z|׃|Y}Ǿf}K~}L~|}|}n~~}Z}ĺX~V~S}҈{M|÷i}f}w|]|s|{|K~P~A~r}B~W}Q}߀|]}x}X}v|y}|E}ǿr|h~}~~Q~q~N~R~l}i}j}L~K~w}j~K~H}}܀}}S~}[~S~ߎ}Y}vB~n~Qp~M~|CPFg~ļx}}}h}G~E~U~]~Y~u}_}ʬ|{{{{{{Ά}^~||Ȧ|{{Ľ{{{{{{|ŀ}~V}}_}ú|{{u|W}O~V~a~}W}{w|û}|p}Q~n}i}|x|{{t|v|~D~U~C~ºe}Ӊ|]}L}c|z|{{[|O~d~w}}||Ζ|y}}k|Χ{}f|ˌ|e}}Q}}I}Z~Ļp}{{g}·S~ŸS~I~H~G~Z}|f}ג||e}d}c}}U}P~k}||}m~y~`~|}q|A~g~X~ӕ|ʂ{J~E~||،|ƽ||g}l|}}T~gm~g}|i}L~=ĽTƿSƿy}P~D~S~V~]~Z~n~\~j~V~k}ߍ|w|Ӌ|?~ƾT~w}W}ƾi}Ќ|{|N}q}|=~}|]}G~F~Q~}>~Y}ʅ|x||ƾ|R}B~>~{K}t~n}O}>E}S}M|R~źIBA?@>³G~e|`|l|p|B}ŵL}M}p|r|C~u(@~4~^}a}a}v|q|v|v|z|{{{ż{{{ps{{{z{{}|z||rs|{{{{{{{{{{|||R~H~M~}}b~n~ɿ[~T~U~v}|A~}l}a~`~?L~d}v}h~a~~CĻT~JCk~b~L~t}}}a}g}g}~}r~DAv7~f}?HBo~W}S@MPW~>E[XHF^Q~H~r}Z}?~I~E~z=~[~`~9>L~ȻR~C~O~`}ƾ_}r}g}\}]}]}v}D~R}Q}r}ƺd}k|Z}D~D~ķU~D~B}=~=~b}a}w4~>~j}7~A=x}~||\|{i|z;~<~g}H}K}o}L~`~d~a~I~O~C~ǺT~H~F~B~=~:~οX~ŷS~ƹR~K~{9~g}A~N~O~A~a}C}l|q|zM|_||L~ʿX~Q}|G~z8l~o~>~|r}~B~W}z@}L~i~G@@g~f~^~d6Xb~E~F~j}v6~=~>~L~{}s}ĹX~^~R~e~T~I~b~<N~[~B~f~B~h}G}C}C}>}u3}[|9}5~S}=~[~P~K~S~k~B[<_e~ՠ|_}V~x-u*J~ȵC~оF~w-J~@~9~9~h}@~Y~m}|}R}`}r}a}e}`}݀|\|U|f|[}R}̀|ه|e}m}n}:~>~=~m}@~C~>~F~A~L~r}C~T~v~f~m~8i~=K~>~f}Z}O}Ǽ@}H}j|{{{{Օ{ŋ{{r|c}{}{}>~|:~z:~T}w||J}|}\|ի{֪{a|†|`}}r}d}q}q}P}|M}S}\}o}K~I~[}i}p}T}Y}f}Ľq}x}}}}M~b~f~e~R~m}F~<~@~C~f}i}W}[}Ża}n}f}j}>~n}G~t~Q~A~}?~f}h}G~E~Y~q~}:b~]~b~ǿ[~=u~W~]~g~h~S~b~C@l87:;ac}͹S|E}N~FIC;_~Z}K~`~W|t|{{{~{zzzh{r{Ź{ѥ{{v|||||{{{||||||ؿ||žz}Z~y7N~AAՁ}||]}`}g}N}K}M}ƽ}|||||{o|x||~|q|{|t|{l|n|n|p|{{{{{{{{{{{{{{˯{{{{ɪ{{{{ý{߫{~H}Q}{|Ѭ{ɂ|v|؁|S}x7~H~f}R}ێ|{{Ò|Y}s}~|y|||m|M}F~M~k}g}Ĺc}n}^}{O}}Q}ȍ|ߗ|}Q}{M}n}?~Q~D~m}=~C~:~h}j}G~L~n}O}V}S}Ļ||zL}̽c}Ϳ_}X}S}^}O}P}W}t}?~ʿX~`~h~`~;KƽTaKa~`~F~zC~N~N~Z}_}Y}ߌ}~Ob~h}ݢ||m|||p}Ϣ|p}}}h}Žs}\~}7żX~F~zA~J~d}y}D~Q~S~>~L~]~=>j~C~D~Q~J~O~W~J~C~D~S~M~D~J~t}΄}W~}|}Y}|g}f~`~q~n~b~\~M~O~i~@FA?^~h~]~V~Z~9;;|8j~S~I~@~G~S~|||ۦ|ƿm}N~y:~W}U}Z}ؤ||||||{{||v|h|n|r|z||q|w|r|w|v|Г|K~I~G~T~ǿ^~`~I~}d}|l}}p}~\}ɥ||ü|||ѱ|r}}}U}|j}֕|T~l~n~N~҄|}`|֚|h}w|o|I}b}g}z}U}ø]}_}L}m||[|c|]}d}J~öb}Žn}Y}}E~~?~E~T}||p}g~H~|ޖ|J~H~z>~k}|T}w|~G~^~N~o~»c~r~@}=Gy~n~üa~w}r~r~t~n~s~U~øZ~Eq~L~M~ц}|٧|RVapdVg~i}l}|K~Lu~Gy~m~c~r~O~~F}ǿj}Q~M~p}{{||{{{z{|}~|{{||}||}w}{۞{L}X}|o}ľ~}k|{{{{{r|żq}]}N~J~G~yM}`}ԑ|r|z|^}r}}̽]}V}{Ȳ{ù|ϔ|΁||Ԁ|{{|e}Y}O}z||U}]}f}[}A~W~^}ˆ|s|r|Ey~}}}|f|X}}U}j~R~P~h~i~@~L~H~t}ž|j}Ղ}S}JI~y}b}_}|x|{c}ޅ}{}y}I}e|{\}W~|9j~<S~ZJ~j|u|S~Pm~Ԅ}і|[|R}¾iTS~W~c}|ѫ|˖|Q~W~ýT~B}6XDU~a~C~l|y|l}Z}|||Ŏ|t~I~L~N~`}O}V|m|j|t|o|||}Y~K~t}|m}w|e|m|l|L}a~I~S}Q~69<ŸF̿GO@P~A~ɻT}F}f}X}m}?~ŷK~B~G~R~7õG~~9~b}^};~?~:~<~;~|ľ{μ{{znq||{{{{||~||{{{{{{{ϭ||Ā}L~|i}V~H~O~|}u}J~l~i~A~n}Žr}f}}}l}|@~b~`~k}l}ֻ|Ϲ|k}Å}`}P~@\~f~W~\~¸W~^}ϝ|ܪ|~S};{;Gy4P~C~}?PMz~T~p}@D~>~;[e~UMa}:>a~}}s}A~[}Y}U}o}e~~:~S~D~Q~k~O~A~N~B~_}x}[}źi}b}V}a}F}Z}f}H~ö_}~;~A~^}e}E}p}C~[}ĸt|́|z}L}e}k}m}X~Gf~m}Ǽd}}|D}D}G}M}b|d}<~o}u}=~F~M~O~B~D~>~h~o~]~G~o}M}J~9E~F~}<~h}Ź_}I}s|j}[~e~=@i}<RQ~e}:~D~z9~]~KC~:Z~f~Hg~;E=6Z~;F7>4I~@~V~ǹ^}>~ǽf}f}Z}V}y@}|D~R~w}H}v}V~k~|;J~B~K~{:J\Q;~ĻX~S~?~=~?}y<}z|w5}5};}O}?~Q~>WJG}9x4T~9~f}J}~|m|_|j|D}L}X}[}|D}{{b|u|||u||||º]}p}Y}M}~<~F~J~ȻM~P~H~\~X~S~R~X~:~\};T}õO}G}z1~T}r|w|Є|R}l}z>~t}?~C~l}Z}Y}R}d|{f||c|p||Z|h|Z}:~=~F~w}{}m}P}o}h}_}`}K}_|{{|ƾt}V~߁}E~x}r}}}{}c}ƿj}`}e}y}||^}}V~N~J~M~R~G~>~O~e~ûT~9~m}|<~m}k}C~A~G~q}q}i}c}`}k~n~q~P~m~\~h~o~q~t~DMNLh~N~?~>~p}C~@~G~B~O~o~Y~c~=LT37975Iz7~H}|:~n}`~H=_~Nd}g}{{{{}{{{{~{{Ť{{°{{{{||~~|{|~a}|||l}p}h}Մ}߉}Y~T~l~IMs~`}|c}v}||8~_}\}>~A~p}Nj|{{||lj||}|x|~|{k|q|x|w|{|||||||||{{{{{{{{{Ϸ{е{{{׼{m|u|{̔|{{{{ٽ{|p}~?~W}z|||p||ћ|Ė|t||{{Ą|ن||T}O}\}f}@~f}|ʍ||{~|||{|y||ľ||||Ζ||||{{||{{||\}Y}`}a}|ɿu|P}k}G~?~D~\~9KT@i~Q~h~=?|}ϣ|||O~_~Ⱦ[~S~N~e~{8;?L9}6z6F;;><?:P~F~ĹT~@~E~~>~B~O~F~A~I~;EY~]~k~Y~[~P~c~j~D~}^~o~9W~X~e~L~F~a~EBHTGO~X}c}o}@~I~T~r~::c~J~L~P~[~b~a~e~e~ɽT~O~ȻR~U~S~U~Z~S~˿U~ŹR~A~y|||||d|e|l|||e}P~s}է|t||s}||||x|k|t|{|j}̾O~R~y}|ɟ|ƞ|r}t}}Z}~}ި||X~P{~~z~T~ĺm}ܘ||I~ƹ\~S~öb}S~BYI`~b}x}k}Ǻe}e}Y}R}ij]|G}|L}ר{X|N}s|zD}O}X}N}<~O~ϾL~;~Ź]}y|H}X}]}~E}F}||a}k}lj|Ŋ|V~a~q~a~G~P}؜|l}|}}X~d~s}z}p}ùh~ȾX~S~L}J~THHVf~JZ~6L~j~k}j}c}{|~K{~~L~}n}|{{{}\It~}|z||w|c|a}ߤ|ɝ|||Ջ}ה|n|{||{{}{{||{µ{c}z~L~o}n~ĺh}߉}n|{|{{{zpp{{°{t}p}Z}j}p}F~L}]}Źf}C~z<~M}Z|b}yC~y}ôk||[}`}y}[}y|j|_}̾`}~<~p}|ڑ|r|[}^}c}||h}ѕ|׫|f}`|k|p|q}{|~z|Ǯ{l|̖|{}H~G~w}z}Żn}}o}D~n}b}W~^~6~W}|||||X}d|Q||{;~̿VCu~ƾm}h}W||u|j|p|ºk}U~L}]|e}H~t}}d~SxL~ž||||L~~m~S~d~J^D}5EK~M}V}|¾||ѕ|_}J~X~TR~||]}}|}|{|܂}}:P~k|{å{{Β{ͿJ~/=AT~J}ȼv{ȇ{s}F~N~]~<T~P~D~O~Q~̼K~}6~a}R~B5|2\~3=ŹO~h}?~G~a~S~D~ǻJ~P~@~I~K}ݰ{ͮ{{{|||΢{{|{~֮|{{e}~k}{{|||}ýk}Ɯ|~`}ē|v}_}Z}Z}ʽ`~Q~}ǿa~~K~֭|||~||||ʤ|z}W~c~\~t}p}^}Q~R~J~R~K~a}ݫ|޴||ژ|f}O@G~N}K`Qýg~N~t}R}c}k~T~ID~?\~;~m~DpUL<g~j~C~E~̾T~I~y6~A~H~`~f~Q~N~g~Q~}u}J~{}p}o}`}Q}c}X}L}R}]}s}J~A~>~E~M~E~ĵY}O}{}X}U}O|M|D}|>}k}I}`||p~W~{}J~S}P}S}]}O}b}[~W~R~u}X~}~b~P~k~U~o}F}ʺ_}J}u}c}f}B~K~N}Z~GEFE:K~r}L}Y}ً|K~c~L~l}e}j|ۗ{Q}A~I~N~N~K~~?~j~o~M~@U~{5I~c}F~P~C~]|z}F~g~JǾTN?d~b~G~}|ķf|F}j}A~ǻb}{<~@~_c?>DHVA~i|=}H}S}_}C~O~G~R}D~{4MXk^F|9G~?~F~ŹO~X~b}Q}d}S}Ť{^|{|~|H}D}^||`}B~@~o}K}@~c~z1Q~c~S~[~y/F~8~8~^}||o|O}ŸW}Q}|8~J}i}u}g}g}E~R~V~^~]~X~¹c}^}`}R}؄|T}p}[}|Ə|}}@~>~Y}m}C~G~V}|I}|o|~|t|b|{{Y}x}m}q}e}z}I~C~@~{}u}i}\}||j}U}ǽg}z}v~k~ùU~]~a~]~M~G~e}^}}A~C~G~X~K~j}C~i~}@C?GLO<}7BRXC_~[~Y~D~K~a~8@~Q}\}b}a~@d~?~8_~Z~I?Hz03\H^~e}˽q{H|u}ƿr}|C~;÷KTFi}w:~s|Ⱦ{{{Ҿ{{t{ã{{ɩ{{{{{Ƭ|}k}{|||{{{{{c}K~O~x}w}|j}T~?e~D~ǺU~H~|{H}w7~<~>~<~K~D~;~Ն|…||v|m|l|p|û||||{w||||||||||||||||||||||{}|||||||{{|{}|{||~||g}|{{~|[}]}[}w|n|l|ߒ|~M}|K}|v|v|N}s}=~A~`}|||||w|{{{{{{{{z|{{{{{{~|v|p|~L}q}w}_}Ĺf}B~H~Y~Z~}378Hd~ĹT~@~q}o}i}y}F~u}j}g}X}u}C~ʚ|Ŀ|||ר|}}p}Z}ɉ|t|~L}h}>~øR~H~Y~9B:d~Z~Y~}?~q}{=~@~E~P~X~f~[~M~X~P~V~a~>AU~T~_~:Cp~?D=`~U~N~O~T~¹^~p~>9\~H~E~^}Ǣ|Ş||b}P~Z~N~M~L~a~]~\~Y~R~E~B~˼M~R~E~F~T~M~H~{<~\}|q|q|||||Đ|Ɂ}}|Ǜ|ѳ|||ž|R}}N}h}C~J~R~øR~s}Ļe}Y}|||Ŀ|l}j}m}s|֋|{}ׁ}úb~z?Bs~i~i~L~K~O~i~\~LFI~L~J~~I}}>~?~T}T}ޛ|z|t|µb|z||b|z~zz{{e|Q}F}Y}<~7~ŲO}|~X}b~_~n}~R}e}]}w}s}|K~i~B}9x8`~W~ڀ}~}Z~G~Ź]~q~W~M~}¸n}ό|{}|r}w}n}|x|{{Ʃ|o~F?}|z||{z{{{{zzz{|||گ|{{||||h~Z~`}e~Wcle~t}ϑ|Z~j~R~o~}q|ül~I~X}~}n}M~Z~n~|}M}|{~|{{{st{zzzwv{{ծ{_}W~?~H~>~U~Żj}ؕ||[|r|z}\|Ǻ|{z\}z}ܦ|n}m}{L}V}e}^}t}]~L~¸g}K~J~ü||r}T~{}z|Ĕ{_~}~`}~||{|{||H~=Ey8S~{}r}H~<~I~D~F~C~S}U}||||w|||öx|οn|O}@~fKq~|‡|y|}||f|z~ڃ}ӂ|U~t~p~Z~V}Y|F}oR~`}|ذ|~~]~I~R~c~n~~:b=Ĺ^}f}̄}k}y}}|͙{˩{լ|\}aMpe}p}¼g~Af}|l|{i{u|}G~~C~}n|~}C6{5i}z||_|{|c~N~x}ѿ[}l};~D~̼W}U~L~e~r}U}]|t}<>z2y0`~R~F~H~B~D~F~G~S}<~R~k~u}~|w|ѹ{x{޾{U~lFT~}|{z|]~|}l~{I~?c~I~üo~||||||u}MrQ]^~||||||¢|f}c~o~`~R~?~@~{}j}ؠ|^}i}Y~R~|ʦ||||‘|z}}F^~M~@_<}:aC~q}=lD=:kCg}l~BKgZkD4z/6FWBz4X~EBF~F~~6~J~m}?~W}b}i}~>~G~n~Ǻ\~h}?~D~Ĺd}f}=~E~\~D~=~ȻY}s|D~j}\}νh}M}Z}{L}Żt|[}o}]}G~p}`}z|[}Ż[~^~p}[}ʽg}΀|U}K~I~I~ʻP~a~`}v}K}_|׎{<}I~\~F~óQ~:PJ;:<;X~Q}Z|]|q}|u|zG}g}Y}J~=~x7~A~A~Y}Ĺ}|e}D~B~>T~ŹT~a~b~R~^~8Z~c~U~N~OVaúVG|:~:@=R~̽h||s}k}};~W~P~S~E~g~:]~r~=ɽX~{5X~F}b}?f¹SYIz7~ïI|z4}=}:~G~58úOOg~D~f}ӟ{{V}}L~M~d~s~~;DǽT~L~D~r}t}o}}_~r~}~DLAID^Zaag;o<BZ~;F;Z~}9~N~M~N~7>{<~w9~9IVRHD>=>:6f~Y~A~x9~U}X|R|{{Y{w}EDC~w:~C~R~}D~T~r}Ⱦ{{{ü{{{{{{{u||ʇ}}ÿm~V~X}»a~_~ļ}i~u}w|||}|||||߽|}}}W~a}x|j|J}<~ѽF~s(}-Q~w:~|}|{|v|s||Ã||:~}8~T}m}Z}~Q}j}u}|Ķ|||||||m}}~}]}ڬ||||||||||||||||||||||z|{|{|||}|~||ؕ||֔|u|o|ʌ|ǿ|d}v||||\}M}^}n}[~~}`}]}|||||||{r|ג|||w|m|m||ĺd}>~d}O}q|؋|||}K}W}c}n}T}u}G~=~Z~>PDd}S}q|º|̋|U}}}X}Ԣ||||֙||`}a}||^}|}||Y}]}R}݌|b}d}b}m}f}_}y}F~?~u}?~r}u}k}g}t}{=~m}g}D~e~b~^~~45b~{0z1a~6a~v}Ļe}k}p}p}P}ŋ|||~N}]}r}B~H~|}}}y}żq}G~K~úg}x}C~D~\}[}\}O}K}_}c}ɺW}d}c}k}ɼ`}g}]}a}]}~|||||||i}}|}P}w}|Ԡ|C~G~V~|361\~R~=~r}S}|a}|||q|g|œ|Z}z}u~l~W~@~P~O~Z~:>T~L~A~F~>y4G~V~p~|E~]~g~_~]~}d||Ⱦa|R}L}h|ϲzԖ{f{_||G}L}j|G}|X}|z||}}||֧|v}_~>:?EGI;g~^~I~A~B~i}z}L~F~G~E~j}X}||c~م}t~{O~Ľ}̩|||{U~FP~Ǥ|{{{{{|ܖ||{{{{{{|ۣ|||i|Z~|~P~s}|j}O~X~<?i~e~D~ƾn||Ig~g~}l}s~p~~ATL~_}Ҟ|{{{{oo{zpp{uv{zzzut{~~{{{}{?~Ʒa}Z}}|}e|||~|Ԛ{v|a}|ˉ|̃|W}z}ź|||}|G~k}\}݀}R~ƹ\}~H~|o|||{{ˑ|R}l~M~||||i}e}{}H~I~J~J~S~Y~w.F~w|r|U}q}i}ȼv|y|z|||{{{w|T~ހ|||O}h~\~h}~<z}H~}o~d~žm~g}{\}x}Ig{@}J}L~`}h~\~~}nj}܌}u~z9H<J~z:DSZ~}p}ľ|j|ћ{|Z}y}}x||Bk~q|\}l}DŽ|n}~Z~n}ø]}j}Q~CM~`}f|w|ú[~W~Q~|B~E~q}~~}j|ɺm{x}B~[~9˿N~U}ķX}K}o||͉|W~X~c}@9b~|3_~f~X~J~;~u}ýq|[|d|ǽT~z7P~M}l}C~z|_~oF~B~o}S}U~J~||||Ə}|Y}x~=C:d~N~y}l}|B~n}tMCBƿ^}~V}w|h}}d~v~h~d~]~m~e~ú_~|}_}^}֜|Ğ||Ê}x}ǧ|||ȳ|||}½}b}t}}^}~úh}_~NK~`~}^~v~JRf~`j~^~J~A~ALcFLMAk~c~\~T~i~ǺP~I~}7?~˾V~{7>{:Kg~G~?~|8~H~C~I~q}W}{;~z?~ǹY}}>~v}]}}}Y}f}n}Ⱦn}m}k}O}[}}h}m|zH|b|\}t}k|ۊ|[|i|{ڙ{||c|F}s|{C|Q|\}R}J|_~~~o}{j|{^{ʈ{_}B~bYFZ~}O}~|P}^}Ӈ|t|Z}öS~SFa}8~A~V~>6{2\N`~j}^}]}k}y7~;~j}n}C~G~V~7K[~q}=~N}y5~y18<=BJ~h}>C<;Do~h}ö^}h}E}W}d|O|~P|{{T|͂{N}}D~C<I~y4~y/7>5ɻK~A~Y}L}?~R~h~EHm~w}g}ża}m}B~A~8~6~y2~@~8~T}^}?~̾O~c~S~¶K~7~=~C~~5~;~e}e}Q}~b|e|K}[}\}^}B}W}^}z3~d}a}Z}c}|8~>~>~u}I~Q~<~[}_}`}b}u}D~s}|@~q}S~I~O~R~g}x}h~g~_~D~D~G~=~a}P}üf}y}i}a}q}n}}\~J~B~J~z}E~>~<`~N~C~q}h}Y}e}D~S~Z~U~W~d~CH@A=6Y~~6@AAELIBIb~U~B`~S~@EFJHF>LXVUE>9[~F~@~@~C~u|V||k|U|֌{W|P}T}q}N}ʼe}a}R}d}n}{}|e|U|t}M}\~[Q}{{|ľ||R}F~G~[}|~q||t}O~u~ž`~O}P}Ն|}|}ج|u||o~e~w~P~||||||ʥ|m}h}ޛ|V}7~/y*4u,;~o}m}n}V}m}ޟ||Φ|٭||f}}q}^}zA~\~~}k}O~|A~z}~}||H~O~]~f~øOO~¿|||||||||֧|i}|||||||||{{|||||||||{Ϳ{t||Ȉ|||W}X}\}Ļf}q}G~P~A~ȼO~f~ER>g}˪|ѳ||ö|||||||||z|{Ř|i}P}ǽi}r}{<~d}{{{||[}\}b}||ߡ||Q~CE_~V~D~D~F~s}C~ǽX~a~r}ü|||ϒ|Nj|͊|l}}|v|||_}Y}||||x|w|~|}||ٚ||Δ|Ւ|V}|T}U}ڕ|Д||q|s|u|j|n|u|}|P}_}O}~|~O}?~b}||||||||x|t|{||Z}y}M~H~D~P~|}|Ɠ|ʗ|||||֐|w|ȿ|K}X}O}|J}R}e}e}}J}ӏ|w|ǂ|w}{@~h}X}Ж|Ν|||~}}g|P}[}Q}Y~}3w0X~M~M~O~P~?>@Y~G~}m}ơ|ڢ|ы|s}m~ROI}~a~D~LeGµX~E~Z~V~S~²a~|}x@~w}V}||f}|ǟ|ԓ|^}}A}µi|P}}÷m|_}V}͊|V}|||{{{{Ġ|V}ғ|{|Ȟ|i}Z~{<{<l~:GBEZ~ȻW~l}|}I~E~[}H}o}o}Z}m|x|c|{{||n}|}߄}O~d~Z}|~`}v}j}r}͛}Ѩ|||{|q|}_~`}`~V~g}|s}}K~}n|ـ}z~C{>y~S~Ib~M~M~^}B~}@Tq~|H}a}NMp~zI~[}ǀ}w~C~|{{{{{}{zus{{zuv{{tv|||{{|s|j}߇}|b}ˉ|{~|CFZ~x}|||{d}՘|{j}Lw}r|s}w~v}T}K~u}c}c|j|s|l|||s}T~z~|}||e}u}Z~Ĺc~s~T~L~o~@;^~N~}K}{{r||C~>~\}s}˿k}|X~Y~y|^}F~x}t}Q}Z}J~T~|kaĿv~e~՘}ڞ}l}||}Sj~J~Б|e|D}I~E_~`}t}|Ĩ|u}T~}BJV~ވ}Hl~b~X~}W}׍}~K~j}d}n}|}d~_~?~Y}h}x}D~Q~]~|i|D9I~a}Z~[]|~Z|s|ün}d~|DS}ź|N}Z}ƿh}i}N}N}R}F}`}_}T}d|}G~v}|}j}Z~b~?~r}D~L~M~A~y}|ٚ||H~h~h~q}N}IU~^|y~lV~S}a}S|m~R~c}u}›||l}Z~}r|́}M~|>C;p~p}ɾb}ƹR~QK;=Eg~N~}b~}k}S~?:X~m~Ee~b~s~ك}΂}ٽ|}}|ӽ||||Nj}y|d}|v|u||ך}|s||ԫ|X~߆~ޓ}П||{|ř|e}}}H~T~{}H}d}}@~J~JZa>BD@>9;a~R~\@|6KX~I~H~z:~k}I~x>~S}_}{@~_}P}b}~C~Q~=~r}j}@~<~O~F~J~ǺU~K~A~O}S}K~z;~c}C~u2~{>}ƃ{ʾ{{a{zze{p{Ź{{A~Vklj~~}L~Z~Y~v}P}s}G~r}w|v|Y}~>~::Y~[~]~c~a~3V~̾T~=F~:A|1GL~}5=6{4b~]~F~C~L~̾R~O~t}\~N~H~^}y4~@~z6~;~`}O}h|H~;ƽNIHOM~·V}w}h~E~g}n}z}}O}X}{D}Ǽ\}@~d}ſ^}R}Z}o}[~LA:~>}K}V}{@}M}?~=~j}B~L~E~j}p}g}H~;~<~;~W}v|ûw|ɼ\}x3~=~}4~i}k};~I~9~ƸS}E}o|d|j|K}z2~>~~2~C}I}J}~|ŵX}7~A~T}K}J}F}P}~3~8~j}@~F~~I}u|΀|{;~F~P~<~:~Y}V}|u|~E}ڂ|~e|W}[~=n~H~s}E~L~M~q}k}\}º_}i}?~<~\}E~ſb~o~JFc~l~T~i}|g}}E~~}C~Z~h~{7k~?[]G<5;\~9d~b~;y6C>KD>GEPKB9z4:6|5X~ǹS~A~n}q}{:~q}b}X}J}S}s}<~Y}ɺY}X~l}}|V}z}ي|K}C~z}||j|||վ{{U~EFCi~u|Y}r~i}p}||d}?DO}ù[~Ka~|{||đ|ަ|`}ӎ|ƺh}T}|U~G~K~|G~R~||||||||Ͳ|b}n}J~S~5}355I~g~W~}c}||||||||ʔ||R}t}|٠|h}]}^}a}q}z}E~<J\ƼT;i~}}|{|||||}K~|||{}|{||{vw|||||||||||{{{{{{k|Ѷ{{dž||P}b}j}ùf}K~a~?B<d}j}ȟ|||||||||||||v||^}|e}e}||||||||||||Ũ|}l~d~~89=9e~}6g~;y6]~N~}:~y:~J~]~|6]~J~l}Ϧ||||||||||||]}c}T}U}`}Ӛ||||}||||x|w|t|{m||~S}{S}~V}C~S~}}üt}s}Ь||δ|Ϋ||||||||Ԡ|a}j}f}l}zB~r}|}||Ô||||}|q|h|{ί{ϯ{|b||{:~K~^~~:Z~´O~ʺS~O~Z~L~r}c}y}~}~?~s}};~ƶ[}p}:~F~ϾJ~J~?~=~k~|=j~c~Q~R~r}}E~\~Y~N~ۇ|{˪{{|||d}P}e}Y}s}n}W}ĸa}U}Q}³d}v}V}s}e}O~q}]}i}}{}׆}b}}|Ҹ{|V~}|F~S~e}W~Q~O~ч}|}²|||~|o}\~Dn~W~P~`~d~g~\~n~b~^~ƺ]}M}c}t}o}s}]}U}R}Ԅ|ú|}|t|g|}|k}~Z~d}Y~y}Ĉ}r}}Ɂ}^~||u|}]}Ί}}ɗ|r}Y~n~Ÿi~M~k~^~}c~cS~U~v}f}d~^~T~w}O}z}}}Xaawy9~a|n}MP[~M~c~R~_}¼v}}G~}ʁ||r{ľz{{{{y{z{|{{z~}{||_|e|b|S}n~\~j~O~Ѐ|ʉ|ڀ|^}m|w~O~a}ч|r|||{n|Ӑ||Ο||w|̕|S~n}üt}}}F~BgYük~p~ߒ}|{v|u|T}f~\~H~B~Y~E=Ż]~C~Z}j|{{z|]}||N~S~yI}k~Sw~ü||v|y|ȉ|ú{|i|N~CPP_~g~b~U~yH~V~p~p~AL~Շ|{{W|u=}k~jq~q}Е|[}9;OR~щ}z}f~|I~V~ׁ}e~[~z~s~g~G~E~C~s||x|||p|Ԛ||||r}}}e~O~`~y}|V|{H~]}|I}ف|s|z<}U}R}\}a|Ͽ]|p|݇|W|D}N}F~d}`}K}E}t}f}h}o}f}i~x}|X}g}g|p|{{{ޚ|]}w|u|l}\}[~UHt~}ϕ|~I|~S}|:y}m~c~^ø\~d~`~zA~Ŀ|}b~ύ}{}f}^}̀}O~;M~e}ƻl}}j}ƽn}}C~U~c~ǿ`~j~}}j~U~Z~`~|~p~q~˾Y~ƺY~P~~c}}V~w~d~}k}t~SviC~z~V~ϗ|¡|||||{|||o|t|ޫ||b}S~µLHA]~A~n~a;]}t~lG@9`^ZM=z59DDP~Z~CA_~[~>38d~v3~w:~\}u}}>~e}V}v|u|p|}|S}t}s}@~B~?~N~=~N~Z~D~J~}5~V}u/65P~P}|M}X|zX{}{m|D~}A~x}Z}ƸZ}R}Q~E<LGCh~\~G~a}d}[}{{؞{ʏ{I|^}P~[~HPc~`~Q~K~V~>:}6μT~Q~z~D~{2HUžT_~]}B~S~B~<~}8~8~i}y5~9~>~B~y5~n}^~c~o}x|L}ùW~?@F=o}S}ƻZ}N}M|_|J}~J}|Ǻ`}i}K~C~W~Gh~a~=<]~W~j~}>?t~CHCm~ŸS~U~\~ȷL~x5XfPAm~a~]~|:C\~f}ļn}a}O}X}~9~7~f}a}x4~_}a}9~7~b}j}N}||I}g}l}i}>~̺G~V~4~3L~;~g}>~`~a~g~N~A~E~e}m}j}Z}o}y:~f}g|{{|{{{֦{]}}}S~k~j~m~S~R~V~úY~ʿX~R~úW~F~{}M~l~KMCLQ~[~t~º`~i~O\lmp:GDAUMKaFF<L\`ûSHe~l~m~d~c~^~Q~P~H~@~Q~e~a~J~h}H}S}F}z;~x:~R}j|[|g|\|b|o|]}ƵZ}t}i}h}a}c}N}N|p|e|}E}ø_}G}}E}e|||Ӓ}|||o|o|p}t~}b~V}ºm}z}|}s~|a}E~{}q}t}||{{{|W~I}||{{|l}||||||~|{|{||b}_}E~Q~d~Z~G~}}z|{{{{{{p|R}w};KS}ļ|||||_}a}K~Y~g~;B>JW~b}ʿX~o~b~Y~||}}g}l}||||||{{||||||||||||||||||||z|ȕ|Ϝ|y|{|٠|l}g}}O~|7\~v}ǻW~d~̿X~x}||||||||||||J~}d}|v}}c}a}փ}k}͸|||{|_}]}|ט|T}O}N}a}I~i}q}B~@~F~W~a~~4U~[~_~T~z2B8M~x}q}|}H~p}ɫ||||zO}U}||g}I~c~]~H~}9~A~C~W}|}|}||}|z|Ҙ|a}U}ז|U}G~O~O~H~E~x}s}q}}}~A~}}o}|Μ|h}~}K~}=~]}}}G~U~Bf~s}o~Nün}l|ü{|j}A~A~C~O~I~S~6462\~X~T~M~M~v}l}W~[~S~K~L~p}Ά|w|r|p|[}|z|{{~|||||||Ȕ||{{{}P|`|e|L}K}T}{E}V}O}øe}`}|Z}{I}K}^}v}M~F~B~F~Y~T~J~H~݂}ځ}u}}T~Q~i~`~X~b~w~m~I~Ԇ}ĥ|||||e}Y}я|||ț|Ǔ|ԍ}`~}ϊ|d}S}m}P}V}U}ɿn|ɽw}n}ڑ|V}|y@}x|ʻj|H}M}|p}c}|Ȣ|{O~a}m}}y}Y~]~V~Z~Ӆ}a}|||||c}ڏ}|lj}P~r}~}|J~_~S}}i}S}}G~@EǼ^~p~q~]~AR~b}\~PN~~z~Vj~x}{|S~M]}p|W~|~Q}ԕ{||{||{{{v}u~ȼc~n|ψ|Y~P~e}l}|è{{{ͪzv|`gE^~{ܻ{||{{y|{ȿ}t}i}K}|}h}}L}M}f~J~w}M~[}~|{|||}k}||½|a}f~G~R~r}|{z|q}f}Ҝ|||p}|S~{}D]~[}s|t|j~f~}:OHa~O~O~b}~w}o}o}^}w~k~]}{|˜|ŕ|_}ۂ}X}}ـ}_}k~BI~}}|ӂ}Q~d~_~~A}c~Ļg~KJt~f~|z|׋||M}}|ߗ|z}÷`~B~´\}·[}X~o}݀|x}P~|{p|{{{r|P}i|O|L|V}A~x9~}U|H}[~E~X}W~|?~D}d}`}u|Y}y}N}ߏ{p||v|||~e}n|{|H[w~zD~F}|\~h~D~t}ُ|Y}]}{|x:~ȾX|>o~LJc~b~R~~E~||T~Nk~M~}yP}|c}u~r~v~¸Z~ù]~o~e~L~w}e}}s}p}o}R~üe~k~o~\~D?;gPTpaG~>Hp~`~~}ſl}z~{6~VGY}|{گ{|MnQL`~|{ݪ{p|y|̎|W}ǻT~c~Mgw~}w}W}e}Ⱦc}TP\TXcLYz3fXM˾X~J~F~A}f|̼v|\}l}P}P}xB~M}L~Q~K~V~T~B~F~C~T}<}i}P}R}Q}m}}:~>~J~Q~C~S~9~\}ĵQ}@~B~P~>~óJ~N~T}z|N}I~ƽ\~K~U}w}A~H~j~l~DBY9V~Q~U~W~^~~J~Žp}e}ؠ|||o|}EŻZ~=~ù^~P~B~]}i}|D~K}{||J}|Ӊ|Ǽy|ޅ|_}{H}W}O}j}}D~q}j}c}d}:~}8~i}|:~E~X~öV~k~{}h~CIHc~W~J~5~~6~¸U~K~[}{D~R~@eO_~?~h~M~\~»aDiQT@hCC>9h\ZûRE@~i~{:E~DDV~E:K>da~469747|1_SPG;;?P]RCǽSU\XY]`@G~v}H~P~c~K~N~Z~h~e~ùY~A~r}J~U~g}R}^}|V}k}y}s}]}|—|w}Ǽ^~s~OVHEa>@@9g]h[XOXYZ]YŻR<>;7Z~8:=]~<~E~?~H~C~R~E~F~Y~ǽ^~Q~M~W~K~T~÷W~A~Q~ƻY~V~I~ʺ_}>~D~O}Q}U}Q}y||R}P}Q}g}{}|݂||V}P}R}żz|e|s||ȿf||[~c}l}n}G~`~}|b|f|{ΰ|||w}P~|h}p}t~j~}|e}|}]~h}|||i}|e}|g|]|\|{П{}X~U~\~[}ýz}}K~X~}||߮|]}}S}{y|}{||{{{n|͸{˸{Ϸzz{v{q{zģ{ȡ{{ـ|}|k|p|t|y|r|n|r||@~¸U~7=j~ӂ}^}j}e}F~|8Ǿ]~h~U~I~`}ºp}}s}|||||||||||||||||||||||||||||||i}}j}ހ}M~D~}|||~[}e}g}|||||{|||||Ƞ|x}L~ºp}X}y}d~e~S~h~<M~|||f}u}I~N~Ξ||x|{{{|||ޗ|א|҉||N}A~A~D~ɼQ~z1H~@~U~c~57}25:i~E~y<~Z}ф|~K}Z}]}p}e}L~};~Y}U}[}Ә|||||u|ƺ|׍|{J}o}D~B~F~P~b~R~Q~W~_~x3b~ʾV~·X~Z~}Ò|x}ƿj~t~[~k}u|¡|S~l~Mg~WPs~P~üa~H=d~U~@~}4~5~9~:~7~5~]}a}|7~<~G~\~x/x.|0U~X~T~=~9~\}Q}x}Y~ƽg~V~l~V~{|{{||W}e}y}R}|K}X}|ʿl|øn|ɻh|[}e}K}C}f}R}b}ҋ|֔|c}þu}h}f}d}|њ|[}||ڕ|]}{}U}k}w}t}T~~]~Z~c}L}}H}ʀ|e|̠{{ܩ{џ{{s||c||T~@Qt~q~v~p~g~U~W~V~K~j}}D~n}l}c}y}K}|~|N}i}`}֎|{]}|{|||||h}k}c}p}R~f~d~_~`~L}~]~||||||x}L~{}]}c}u}g}{I}W}F}\~p}˿p}\}}}m}|}u~·Z~G~R~a~}V}l~QN~|½{z{{{{s|}o~V~}{Y|r||{|h}_}s|{|o}N}ɚ{U}Ʉ|Ġ{{{~>H}P}X~g}[}\}ʾ||ڭ|{{{·{{{{{{s|_}m~\~·[}H˽e~_}}|^}}˙|e}V~~}R~}E~_}|e}ӟ|Ϫ|a}a}z}ו||ϰ||e}w}{~V~|Z}}}r~d~S~ŀ|]}^~=D9Q~V}\}K~V~}c}t}W~[~{}|_|K~g~t~]~V}[}ڧ{m|݇}K@n~Q~c}W~J~|~CvL݃}E~C~Q@Js}ļ}||}}|M}Y}R~Z~q}i}N~~@~X}zF~X~i}{{{{zl{o{|z{{W}l~B^}q|]}d~J~q}k}Z}r|S|}|h|e||~W|ǻ{ɍ|V~y}ג|e}LR~i}}Q}T}_}ƹU<a}â{{y}Ĉ}|||~]}Y}~H~J}Am~QWDq~V~`MW}͘|||o~s~p~n~g~f~V~X~X~Q~Z~Ľb~y~}~żh~S~Ճ}_~~}|϶|i}S~TOEMUC{@Sr[QMb~|@s~Eg99d~H~ɼq}ٔ|y|t|U~YGGVtz~h}|i|{r|L}9Jr{BJ|Cb~}|A~h}d~`~^~E~`|^|m|}]~CE?PĻ]K^~Z}S}W}i}~A~w>~T}[}}}C~X~Q~A~C~x}J~Y}M}˼Y}k}X}_}{=~}:~<~L~ƻ[~Y~T~T~e~g~g~Z~4=NG=P~M~ɿa~T~[~b~NƾU\Ee~>»W~|8~V}^|P|>~MVXV_ɿUDF`~V}v<~C~J~S~o~n~Ȼ^~W~R~P~ǽ_~N~Z~J~K~K~}y@~S~øX~S~F~O~U~[~S~O~J~J~t}q}q|ֆ|N~A~I~?~Y~M~Y~y~~P~K}]}r}b}X~EPS~T}yD}^}Ƽh~R}ԋ|ӌ||W}N~H=R~b}f}>~8~D~^~B~HQd~a~P~I~W~g}V}H}T}n}ƻT~c~?Oz0_`;@CDHżHD=@BDC?B@DEKIC89;>C?;c_5~5[NNJNPOY6;b@>n@MƾQOMYV@7z1XC[~j}d}ƹW}q}J~H~}?~p}[}T}Y}E~C~N}I}N}U~A~u~C~G~}}p}D~M~Hk~Y~Z~W~¹a}Y}|Ԁ|׈|̽m|k}ûY~Q~l}`}z|p|t|g|m|]||Z|r|j|d|S|a}~7~M~U~B~~:aP~J}v}c~_}zC}c}ݓ|۔|k|||Y|b|{||}R}Ex~o|j|q|N}j|n|||{{{{{{{{|||r|p|є{Z|e|I}~A~`~Cx}W}ԟ|[}ǽd}z7i~P~݌|{|Ԃ}l~H~{|P}ۑ|żX~l~_|՟{{Ϣ{{o{۴{޻{z{{{v|{r{p{zv{{{̫{{|b|q|{|X}l}C~\~˿QW~_}֡|]}|z?~e~Ⱦ[~}c}|||||||||||~|{{{{{|{{{{{{{x||||||||½|^}˿l}?~n}|||||||||||||||||š|k}V}|||_}y}l}|ǿt}}E~a~Y~N~؅}ދ}d}y}{}D~a~h~b~v}C~W~T}|W}Б||v|w|{{{~|Q}t}@~E~<~<~K~Q~S~52:9G~=~b}T}||ʌ||N}l}w}i}}|¿||||ٽ|؏}n~Mlf=:~y7~;~y:~E~h~[~s}Z}u}R~Y~b~>>ʽT~|}zB~]}||p|W|c||`}}j}t}Y~<@w0H~E~?~@~Q~;R~e}i|~\|~\|{h|^}~L~}y||H}|O}~|{{[}~}\}ʁ}p~EAY~F~d|||||{n|ļp}y}R~R~H~D~C~}C~m}d}Q}˄|x|NJ|x|||֡|˕|œ||¯{{n|]}h}[}h}M}ݔ|b}a}n}`~{~~IRQ{~w~L~}T~J~v~OQ}@n~w~BFKYCi~G~~H~f~t~i~}~a~o}|p||\|s|}O~P~߄}||}|{{{o|zX}{{ݱ||d}}Z~P~\~|~G~e}R~W}s}c}|Ձ}CG~v}ĺn}[}|b}`~O~W}u}R}H~X~H~m~y~r~V~K~K~N~G~|9R{:ſh~e~g}O~enq~r}|{{|}k|b}w~x~s~e~]~}m|z{j|z}`~X}{}|j|ē|f}||П|}|d|ƪ{|{{Ѿ{t|p||a}q|{{{{{{{{|h}l~Ļ^~A~n~S~h}j}ͫ| |}~Q~ܓ}}|R~N~c}j}||n}̑|{}f}_~ɾg~L~S~d~X}I~L~:T~}V}|m}Ľh~v}T}{}Q~i}ļf}|BY~IWHf}p}k}x|҆|^~~}zG}{}o~I~f}Ӓ|zL}V}w}[~U}M~~Ck}|}Wf~U~`}e~j~a~J~GS~CUi~KK~J}O~T}z|`}i||L}`}d~J~p}ԕ|˞{g}y|{v{Ľ{zzпzz{ƻ{y{b}H~B~R}W}A}X}^}u}H~K}z7}N|l}^}u|û{~W|—{a|ݓ}ݐ}S}a~|>~]}ʀ|d~O~ޅ||}IH\~j}m||||||}~`~v}c}w}w}|։}l~żb~Q`h}m}w}Č}|}}݂~DIGJIz~EXK?X~v}ʃ}o}k}||||O~Hl~o~~|}|ˇ}i~T~R~V~t}||g~BI9_]cJH~b|Y|S~fjaeki}Ȃ{ܙ{{T|b|V}g~oBO~q}O~@~ĺ]}Y~Up?I~\}Ƽq||V}[}}>~<ueXWjƽ_i~R~<~g}G~}?~ùa}b}t}O~U}S}W}?~q~ĸ^~F~<~C~D~@~=~m}j}`}]}w8~@~=~]}D~z~p~x~r~d~B~V};~ŸS~y5:DJH~I~}=~y:~?~Ƽa~M~T~F~ŻX~ĹT~I~8OWPTTUH¹QżUo~C~Q~Z~R~G~F~L~N~N~C~V~;ILSHU~O~O~T~øT~Z~Ƚ]~M~F~y~X~ȼ[~ɻR~[~p~KT}~I~L}g||}K~E~H~N~}t}L~t}O}H~L~<D~`}ݎ|V}Y}d~g~=|7Z~Ck~J~N~I~i}Ƚm}{E}\}s}̾^}¹]}Z}L}ʿ_}h}N~W~Y~d~`~d~{=~Y}O}j|t|>}H}C}]}=~e}[}<~A~T~6AHIIfDB[^4_>EA<8=875cX\{16:4aW@}4]~U~B~ŸM~f~˾Q~Q~y/7:`~b~\~C~8~`}\}I}{;}C}Q}I}T}k}E~`}g}[}`}P}T}O}T}?~H~T}ȻY}z<~G~n}r}C~j}w}u}L~d~U~F}Y}s}O~h}H~yO}ĸx|T}^}{N}o}x}b}||˃|؈|{ƻ{i|l|{Ư{ɴ{ê{{E}̾R~\~N~J~O~ĻU~H~c}X}O}ø{{û{~{ľ{{{{|||{|Q~Ƀ|{{|{{{{{{{{v|||||z{|~|{{{|v}þl~V~\~^~{{{{V|o|n|m|ŧ{zz|Ȏ|f|u|f}¼b~J~g|‹|Č|{{~`|z|Ӝ{y{_{[|v|Ľ|Ó|||`{h{է{m|{|o|{ɦzz{{u{{{¼{{v|ԗ|E~J~}N~Ӡ|~W}P~S~||~{|{w|Y}r}}D~}{ο{{{||{{{{ǿ{{|{{{{{{{{{~||}|||||Z}}}b~R~P~H~ʣ||||||||||||||߫|m}j}~}~Z}ڭ|s}]}u}L~h}k}w}j}h}||||y}`~Ab~V~¶Y~E~v}Q~I~}a}o}l}l}e}|||||||x|t|t||Z}a}J~F~:~I~2=C7`~I~K}c}f}a}ݺ|||||||w}\~R~S~F~N~~B~و|y|P}^}m}J~U~\~E~¸T~P~ĹS~¶P~H~ͿO~T~W~U~;~k}g}g}A~P~W~f~4X~O~K~u)}+//Q~J~@~b}?~C~\}ߌ|{{}|||}|{u|v|}~J~f}|p}t|c||Źh}t}T}G~U}ϊ{u{{{{~|T}d}l}c}b}t}}d}p}t}a}Ć|m|||ſ||_}e}|]}h}f}j}ש||ñ|||ȹ|k}}{}U~X~Q~N~|P~z~e~^~N~}I~W~ƾh~e~V~}}|}z@~A~Y~a~o~X~y}x}k}c}_}e}U~`~I~}}a}{m}c}P~\}||||z{|}~|||{{{˝|e|ý|Z}V}{}O~Z}M}~||g~XKJՋ}ȑ|P~p}||þ|ت|R~ƾ]~l}~?~r}m}>~B~h}D}Ĺw|p|U}P}~}b}c}g}p}J}úa}ºZ}w|ь|~|W}w~?^~B~t~T~f}Ы{}{{}~|޼|p}||||zz{{{{{{|{|~|{{{{{{{n|S~N~³c|l|֔}z|{zzz{{|~~Jx@H}X}Ҁ}[}y|k|m}r}X~l~j}z}Y}}}S~}Ղ}t}}R}X}X}~f~t}m}j}żu}Ă|R}<;h}c}a}\}P}]}V}N}u}Z~H~X}}V}j}H~d~~;Ŵ[~|w}م}|}[~a~^}K}w}܎|s|\}x~Cv~S}j|^~m~Q~]}l}ƿq}r}S~XRL~R}E~DVA¹c~]~U}̃|g|Ӏ}g}q}q}j~\}c}P~|{{{d|Q~`}ߵ{{z{{h|^|||}|k|O}E}k|b{n|W}h||Źc}}T}\}Ѕ|l|{q||z~|K}}H~b}}m}d}ŀ|K}>|:oYd}ל{٨{͓|s}n}v}_~GA@k~_~R~~QHH>i~h~S~|}T~ρ~}b~~|@n~@|;Lh~@YMOX~K~W~u~L~\ҿOD~|BFS}}S}Ϣ||޼{{{|||e}}`~Bf~ǻ_~X~g~K~b}d}l}^}^}]}[}Z}]}|ޡ|{T}\}||ԉ|S~Rk~R~P~Dz~v~OFNBB~<~E:e~Wjj^XP[~@H[~R~G~N~E~w~V~J~·`}w}p}J}h}Ǽ\~i~˾]}q}=~?~x7~l}r}I}l}U~||a|J}p}SQx~M~~?~i}}B~B~L~\~b~q~=L~N}I~A~P~e~u~HT~|}H}L}w}_}m|H}]}|6~D~m~O~T~n~k~ǻa~]~l~HE};x~m~W~>~|9~¸X~`~F~|~G~c~@z;a~X~K~\~\~M~~9~P~d~O~ûe~}?MHm~p}r~Cb~]~p~q~j~c~z:b~e}||f}K~X~p~LRPz~^~7x3;~V~z~|}p|̃|K}\}~9~a}R}P}]}I~X~U~5~d}{8~>~F~w9~J~Z~X}U}>?d~BT`NW~J}?}K~Q~>}L}ĶQ}x2~S~@OɿSRƼVOC~U~9Z~V}G~IW~H~`}\}A}>}B}@}<}I}\}b}^}C~f}w2~^}G}<}8~ǸI~C~Y~x4:~>~>~y0~;~;~?~7~t2~}4~6~7~]}u5}>~O~C~D~{<K~J~G~e~Z}K}j}A~R~z@~F~O~E~ŷ_}x9~g~M~O~[~\}L}k}|=~R}K~U~j}Z}f}P~z}U}_}e}}P}i}Q}[}V}[}|s|n||J~ǻR~[~C~n}S}|zC~e~c~v~Ľu}||{|`}i|{Ǵ{{{{{{§|̰|{}{{{{{{{{{{}||||{{{{|||{{{||{{zzz{|{{{{{{{Ł|p}R}|l|Ȣ{{c||N}U}|||ƅ|{d|{W|i|c|}|ɿo|[|Q|^|p|Џ|؈|݁|z|{{{ÿ{{{{l|~H}\}l||\|v|{|l|d|z||Y}l}H~Z}w|S|z{{Ƚ{֋|ѓ||{{{{{{{{{{{{{{{{¾{˹{{o|q|v|}|Ȕ||ܠ|[}o}d}d}y}s}u}}|D~D~{@~Q~z}i}|||||||||||b}}m}||||||||o}ˉ}[~`~c}ʵ|dz||ѯ|i}j}u}~D~D~}[}|ȡ|||||~|{{w|s|~o|m||c}n}P}Q}V}n}M~N~^}r|{B~}|||||y}}e}e}`}y}U}e}`}t}i}ȿd}J~L~X~W~ǽb~Y}U}V}g}D~C~L~Q~T~ȾO~^~4CTŻMK;>74640}-w*̹F~|7~E}j|x|ɺ^}k}}8~A~@~z}s|{{{{{b}؅}a}\}Y~_~{|{{{x|Ⱦ|n}~}O~»}{{{{{|dz|v}a}o}X~[~L~|d}e}|~X}w}n|ޤ|d}}}|ܬ|ά|||||xz|œ|}ʅ}}}y}||}ĕ}}ժ|||||d}}}w}F~L~{A~}}R~e~]~U~k~u~`~V~s~JA{<|~l~w||}d}ב}W~o}||||||||z|l}}m}|ߣ|y}D~u}}|]|ی|ړ|j||i}|ܽ|j}|X~g~S~}l}~M~~}a}n}`}j}R~S~y5~}8~R~F~u}i}S}n|{||~|u|q|f|||Q}Q}G~m~;~´_}|{{{{~{{{{zz|||||||{{~|~|{{uu|{||{{{}{zxu{{{~N}G~s.O~_}N}j|ą|W~Ƚj}|X|n|y}t~_~K~}|Ԃ|\F||z|v}ٙ}V}܃}c~}x}Һ||u||y}^}|{ɮ||n}Q~J~_~ɾ[~EL}@~Y}V}{Q}j}j}r}Q~@~L~J~t}~|s|t||H}H~_~N}|D^I~P~_}ɻ[}@~^}xK}M}h}R}l}O]~݅}r}j}n||I}\>]v~l}||I}F~lĻZo~M~j~P~X}X~v~E~|R~_W~R}~|~c|ˁ||F}p~?}|Ž{{º{{{zr{{q|W~xE~w}Ȼ^~B~O}M}E}^}R}ʽY}ƻZ~H~|C~V}l}f}e|j|[}Y}~Q~¼|{\|O}I~D~t}Z}c}¹x||}b~h~g~m~ƿ}}|Ҭ||h}S~W~T~\~BZƾ]Ǿ[`aVVG^~=gOHJI{8i~FOI}=b~R~s~Ew~Id~J~VɼTôY}f}}m}Hwin~j}Y}n|u|t|}u~QdeFm~{}o}K~R~_~k~W~s}]}|Ҙ|͙|đ|Ǽu}O~a~\~J~}}Z}ԟ||v}Q~j~PSBA~<Y~xA}o|}|i}z?~y?}y|r|G}}9~J~ŹP~X~_~ɾSWIO~i~e~Z~b~I~g}»[}>~Z~S~m}t}P~Q~H~Ǽf}F~@~r}?~k}f}X}E~x}m}g~`~I~R~X~r}C~G~I~D~H~V}r}g}M~]~|>Ci~H~I~u~b~j}z@}ôT}L~F~x8~q}p}p}ν`}g}s}u}}}ùd~~g~[~L~u}n}o}ƾ^}<~u~Y~p}{~bXv~c}h}N~Ϳd~[~ķ^~d~l~D@~H~I~Q~P~J~S~ʽV~H~Ƽe~k~DK~~x~úa~m~NSKKCȿ^~I~=~e}[~w}@~i}~9~<~]}P}h}i}d}>~j}[~_~]~Q~V~W~84R~T~K~@~:~A~C~>~M~D~W}g}Y}d}D~ĵP~<}:Y}{|F}N}|<}v8}?}K~C~3DQ;N~C~G~o}m|zE}zD}t||?}{=}h|b|f|\}S}<}μP}G}<}P}^}{3~@^ʼY~x8~;~F~:L8@~y9~f~Fc~C~B~n}q}K~F~H~@~=~p}d}Y}M~o}m}Q~_~d}U~JzKZ~W~\~}E~Ĺ[~x~S~K~ނ|n}M~V~e~D~}7;ùW~l}\}ʿj}T}ȹg}_}Օ|u|||]}~g|Փ|u}|P}\}`}^}ûw}|||U}x}g~W~||w}||{̷{˴{{ҹ{ݼ{γ{{||{{{~y||Z}|ʾd}|\}L~k|{Ѕ}t|{{|{{{|||{{{{{{{||{{{{{{m|]}i}|ƿ|~S}S}l}Y}l}z}N~L~H~P~}f}\}Ⱦk}y}_}c}r}r}p}·b}Ԁ|ʿv|R}`}z}|ˬzz{{{{{{{|||{߹{ס{û{ɒ{h|U}I~F~V}_|Ԋ{{~]|v|{|Z}܈|÷y|ʉ|}i|{{{{{zz{{zľzzƿz{}i|s|ƿ|û|ü|t|k|o||y||s}O~x9X~{|~|ŗ|Z}W}^}{}T~K~o}Щ|||||||Ԯ|Ŧ||x}ʷ||||||||}|O~x}r}}}|||||||||`}β|||||||~|~||||||||z|w|u|x|P}t}M~`~a~A~||{||[}E~i~a~U~O~e}H~n}\}]}[}L}ّ|ޚ|̓|ւ|P}t}M}s|k|{E}`};~;~y4~ͿX}}|Z|^|k|{E}G}|||p|b|{֮{ب{{v|f|{{{{ʶ{{{{{q}\~}g}h}P}\}Q}Z}a}}Q~^~j~ý|{{zz{{{{|{z{{||~~|{{|{|i}q}|||||||{{wx|||~|{tu|||ˇ}N~}|||||r}u}o}e}||||{}{}݇}d}ün}h}^}N~f~|1пL~D~f}}X~C~d}z}{}e}k}}^~ݑ}ճ|{{{Ժ|\~f}|}g~}|~~|{|ԓ}q~l~||||ω}d}j|y||||||||||||s}v}[~J~|~G~H~k~p~z=~:~G~R~;~J~I~|C~Z}|Ϥ{ļ{{c}B~|:~G~»l}i}|b|K}v}|Q}h}{{{v}|¹{{{{{{{{wx||zy|{{{{{{|||}~|{{{{|}s}y|||W|߆}MXh~]}p}ɼ}||t||||Ľ}}l}[}||p|{{{|ɾ{{{{{{{{}|\}m~JR~L~}j}r}J~S~ƻb~a~[~x~u}h}u|~||ȕ|~K~n~o}Y}s|k|R~m~o~}p}j}AI~}d}i}h}r|Մ{h|j}sz8n~U}h|k||؇}YR{}[}[}E~|}]~ƻ_h~?}M~j~`~F}U|z|M~v~]}X}v}}B~s~x}~|s|V}L~~p}}G~ĺf}׃|h|{c|ڗ||ʪ{{û{Ч{M}z?~P}O}ƺc}s|~||Y~x}o}\~c~|i|r|h}|ٍ|l|{|F~S~E~zA~Q}s|ܤ|͉}JT~}}}j}|~P~e~Á}ǭ|||Ÿ|o}M~|?ۀ}|a~ǽc~~}م}W~|}U~~~G{A?CFJ~}~}h}s}m}~h~ɾ]~u~J^~x}݅|{g}pBHh]DQ}zR|Ƽ||p}K~G~b}HUMz:e~m~ƽ^~_~\~^~:}9?Ec~C~Y}[}v}[~g~m~~<CB~9c~O~r}u}N~N~e}~E~S~C~v}]}úa~C[~K}m|q|y|¹~|{|c}ǾY~Q~H~[}z<}Y}]}R~I~MJJ~ȷ\}y>~B`~I~l}I~v}S}K}ĺf~u}j}>~T~D~c~s~J~e}o}G~?~I~F~}n}~|zL}{G}ǻp||zS}V}T}~}S~Q~ĵN~μR~P~S~F~A~C~F~A~@~g}U}H~T~yB~O}y>~g}}y}~C~J~Žd~};Xb~p~}<n~k~{A~w}L~üa~n~E@[~J~V~Y~I~S~|>~X~ļs}L}b}Ĺk}`}q}ɻf}}Z~ǻb~M~@~ƻW~|:Il~C~ƸS~r~s~AB@b~U~X~^~I~f}y:~D~W~X~I~A~M~T~]~Z~x2^~y/1487@8|3X~Q~D~h}m}`}L}]}C~\~W~<{7]~P~K~K~I~k}B~~7USK{2Sn>~;JAi~y}w|q||k|\|=}a}F~ʾQ~G~d~}6@~G~D~I~c~úQdGm~Ǿ\~k~HSf~H}K~~>~O~q}W}}}u}|s|{G}K}J}t}u}n|v}}T}T~G~[~V~h}}{~Ah~V~ü`~\~^}n}R~s~b~_~s}e}}M~o}EH~U~q}v}K~^~F~f}P~}B~¸t||||W|X||W}|K}{ݵ{{g|h}Z}V}h~x}W}}}h|n|o|j|\}ƿ}|¾|^}e}||{|{{{t|||z||z|{|}j~k}}}~f~C|~R}z}}x}J~K~ƾb~~ۄ}˞|~{{л{{Ķ{||l||||{||ߴ|{{|||{||Ξ||[}H~M~ƼZ~{?~t}X}k}d}L}\}~}^}j|S}@~LU~c|@~Y~=Q~|ѥ{x{{c|{|x|m|Ѧ{ɸ{ÿ{{Ъ|||{{{˦{||l||M}~|Ƚx|ߡ{{{֜{o|}V|j||Y|щ|{{zz{zz{{zz{{{Ø{{{{{o|Է{{|^}m|l}R~w}J~۔|~m|{t||Έ}̛||||||||||||}E~{@~{}|}ģ|ּ|Ŷ||ê|`}m}θ|||||||||||||||||||u||}|{{}||{{|{{|||||{{}h|S}Z}X}Q}d}P}^}~;~l}n}=~o}V}\}n}z6~N~C~}9~v}~}]}g}Žh}zB~^}zA}Y}_}e|{{ɺ{{{{{{{{{{{||||z{|{{¼{{{{{{||b}|Ό|^}t}X}ĺ^}s|N}Q}^}v|{~h|_}|{{|||||}k~}||}½}́}}}s}||||||||m}[~y}l}|||||||||||||||||||||||q}Ӏ}}}}}U~M~}n}u}Њ|p|~u||h}m~HS~}}ą}g}|{|||{{|݆}m}h}}~~l~V~R~Ӆ}v}||ɝ}V~e}ȧ|u}b~}}k}|h}{`}ٲ|||||||||~|Ŧ|h~T~|f}s|t|a}k}`}J~a~c~N~}K~\}||~|~}|=~F~}A~º{|j|u|{{|Կ{z{{|a}y}t|z|{{{{{{{{{{||}|{{{{|z}g}Z}ي}p|{|||ͭ|||}V}X~c}i|_}V~`}{|dz{{{|||||{||||||k}я}p}U~Y}||þn}~I~]}Ԅ}\~z}}D~Y~f}}y}}G~U~e~x~i~s~s~ޟ|{{t|||i||}T~z}_~K~r~X~n}u}Q~vKk~||t|~|v| {إ{Ԅ|U~d}]}o}I}=~d~q~X~\~M}h}q}U~N~d}_}q}e}{I~u}n}e~k}|H}M}zA}G~N~yH~|Ƽ|c}v}b~ŷX~{B~||n|R~M~m}X}|Y|Չ|v|z|}d|{y|R~Z~T}m}g}Ҕ|ύ|}l}S}b}IIp~Y~`}º\~]}|K}}|΃}|}p}`}}½m}T}ļ~|~_HBp~D~R~ɾc}miBc}k}s~r~_~~O~Ŀ{}ަ|Dž|E^~i~Qw~h}]}g~Q~|}h~Z~|||||||l~~q~ǽe}}=@tc~R~K~|W}J]}ф}|Y|ٙ{r|~}q~:i~j~{<Gg~MļX~U~ǽ]~]~F~{9~:ĸV~?;b~Y~l~b~Y~[~Q~ƸU~F~H~o}b}U~i~v~z~j~~A|AN~k~n~R~D~v}k}M}|||||z|||v|R~~A~|E~X}`}N~W~u}R~r~e~M~ʼ^}Z~N~V~K~C~b}[~a~ǻ_~u~L~K~~~Y~i}@~t}|A~B~Q~J~g}Ͽ[}L}ƺl}Y}j}w}e}o}{}k}x@~P}^}Ⱦj}_}t}Y~{}O}y@}@}^}zD|D}?~d}p}w}c}G~[~x;@~n}@~e~ƹW~G~´V~_~n~m~h~V~z?~{G~S~A~D~¶X~N~a~ĸ`~o}[}S~m~m~k~Q~p~Ļ^~^~O~T~H~?~}C{~d~u~BZ~a~`~f~s}ʿe}u}]~z}ȿf}\~I~T~d~x=~P~^~m~c~6y4Y~{8d~88<6777:9D==?]~99T~W~P~ɼJ~G~K~f};~{:~d}[}:~{7~=~~=~B~S~F~k}v<}l|m}ù^}e}p}U~}A~I~T~N~W~\~R~?Ae@7b\ȾT̿V~=}F}i}{B~K~j}X~i~E~{=~M}S}Q}ºk}_}S~d}s|Ǚ|]}|Ԙ||c}q~k~J~{|я|i}b}U}_}|^}Ѝ|f|H|w}L}c}|s||}~}q|͆|l}H~]~_~{}L~~J~S~O~R~S~P~źj~b~V~}т|zT}Y}v}V}|J}}xE~Y}L~l~^~}R}ӑ|ő|Q}ǻ`}m}]}R~X~L~|}_}}y}ɦ|ũ||{|{{{|[~~~l~|{|x|{y|{||}|M~}>ɾ]V~Q~GJ~|R}K}ĽW~Az>~P~`~F~у||d}}F~v}[}e}\}||n|~|³X}[u~e|۩{Ʀ{{{i||}I~Y~|ț{}J~n~J~y}}\}z|u||ߣ|b|Ő|y||^~w~R}؁|J~@D?_~U}s|l|I}}J}~^|{{{z{}|}L~|~i|ݬ{a|n|k|{}W|ƹ\}l}z|{i|{~{z{zzz{{{z{|{xv{z{{{zy{{{|||i|o|z{X||Ǿj}S}g}I~P}a}T~}c}|}i}M~s}ο|||||||||||||ť|g}}T~HKJ~_}U}Ӗ||Ƚr}}V}J~Ǿs}|k|ܷ{||ݺ{{{{{ŷ{{{{{̮{{Ÿ{{{o||{ۛ|zO}x|ؑ}_|e||~S}z}w}K~|Z}ƽs}a}m|{{e|q||y}o|z|]|G}Y}I~s}_}_}E~F~k}W}T}ʼl}V}P}V}G}|H}h|ž|i|ư{{ò{{||yz|{{{{{|{Ž{Շ|Ĺc}g}q}Y}o||͐}|}J~I~}؃}}_}{|{{j|į{|||x|r|¼}U~ƾn~}_}H~T~ъ}w}޹|r}Ԏ}r}j}}}c}ֶ||||||}}u}|t|{||||w}m}|e}||||||||||||||||c}a}d}o}f}]}\}h}}v}|{|||{{||||{~|||||||q~~_~S~}y}n}|w}ސ}^~g~x~܀~L~X~f~[~W~j~þe~S~T~P~E~K~}}n}|||||j||q}[~K~<~z=~U}~||||¹||j~ELK~||{|o}}Q}||||{o}a~<p~ȿh~ٌ}|||{{|||||~|{{{{{{{rr|vw||}|{{|||l}|{x|u}o}|a}½}a|D~EKQ~b}q}û|ȥ||{{{{||̵||~|||~|||}V}o||t~R~m}Á|S~w~Y}|m~~P~V~ZSYAl~y}\~l~t}|d|{ۻ{|w|}||\~p~G~N[zB|}Մ|}D}u}z|j}m~~}g}ǹk|zK|k}|>=@~l}J}@~8RF|<E~x}zB~E~B~^~S~}J~H~}y}w}W}I}H}u}p}]}S}S}`}T}n}|ۏ|Q}}N}M}{H}S}W}|e}Q}||U}~}R}w=~{}Q}v|}X|d|\}|S}T}I}yF}„|Ċ||݉||[}p}\}˕|||l|{s|µi}|F~އ}o}h}~L}}h}X}O~S}n|I}b_t~Y~}b~y~v}ǣ|{}rRFFe~s}d}y}G~Eb`ø\~Y~s~P~ł}ۛ|V}ԗ|s||||||||}P}U|_o}Ľg}t~Hȿi~܃}}p}u}||||j}KU~W~IU}9@k~N~E~G~G~}p~O~T~\~@STI|:d~Q~¶X~F~R~Ǿ\~U~w}ýv}U~~\~}J~}z~~~Z~j~\~ùL~V~N~>~M}L}ߎ|w|z|ņ|V}|}o~I~EPM~K}C~=AI~n~v}Ļl}u}w}ź^}@~V~}`}|{W}ż|Ã|q|p|U}Ǽc}yB~}b}Q}K}t}D~G~m}b|\|^}f|h|¸n|L}þr}Y~އ}]}P~}g|ݖ{|E}s|Ķp|{G}d}X~9k~O~i}j}M}y|l||}?~A~L~a}T}S}S}v>}s|v|||{|g}L~S~U~k}^}x}ɾg}t}O~e~q~{:JGo~Q~V~C~n}Z~j~^~F~M~L~Ķ[~R~s}^}S}S~G~|f}O~S}j}G~^}||Y}[}T}y|||̟||Ր|́|[}=~=~L~d~^~V~¸c}]}|B~}AY~L~f}F~V~P~Y~I~J~c~~6=:N~Z~N~ĸM~G~ȿV~ýX~ƿd}`}{=NTT~|>O^~i~b~@~ø^~P~f~C~@WBW~M~W~a}T~G\Np~j~v}ǿu}}b}n|v|>~L~W~Ն||ƍ|»||֡|}Z}Ƃ||ǂ|{g||w||m|S}i|V|Ջ||||ی|}B~`~u~~=~k~~~r}d}ׅ}}{~a~i}R~Ehf~y}|c|ۃ}{N}ˆ||I}l~|:W~i~H~|w}s}P~}_}^}U}U~~}|H~~~[~]~}}M~c~I}G~Ļp}ʝ|||{}|||{{{{{||Μ|X}s}}Y~ҍ||e|||j}h}zK~t}u~T~[}S~^~X~e~b~W~U~O~}^}W}|}ހ}|}}g}|n~Z~`}`~Q~~L}d}`||٩|݌}m}m|Н|ǽi}x~}~O}~|h~a~ۈ}J~[Ld~q~m~}}̍|}|}~||{ѵ{|e|؂|V}O}X}_}|t||K}||||Ф{{˲{{{{{{{޽{ě{}m{zzzzq||z¾z{{{zz{{{|{{zz{{{||g}|{{{|||̛|Ϛ|ط{b|{{{||{}Z~n~~>ދ}{|||||||{{|þp}X~O~y||||||z@~;[~Q~P}G}||e~BO~U~l~;M}B~ǽY~S~D~VH}j}k|{~g|ʖ|{D~k~|6R~J}m}}}m}G}}|ɼr|k|k|·s|j|g|l|b}R~Q~o|p|{{|~b|я{}N|`|b|R}||]}Q|p|}F}yF}s|Ԓ||j}^}l}T}||d|{̭{{z||||u|z[|`||[}¿|~u||{{{{ٹ{|~s||\}j}_}i~t~q~b~P~c~l~ߋ}Ĉ|s|ɖ{`||q|T}}N~v}|||К}|||ݐ}U~[~N~B~¹g}|||x}u}ĸ|||n~s~Z~}e}|{{u}z}|ö|||vx|rt||||||{||||||||||ߢ|ҝ|Ǖ||{¿{{||||||Ģ||{|~}||{||||||||||o}||q}̈}v}Ԃ}}|}d}}m}_}ӄ}}L~j}o}Ԉ}y}c}f}m}|ޚ|t||||||||w|p|˝|Ķv}yO}|z|{{~||N}P}p}}}V~u~\~M~ć|m}|˵{{{|{{{ʣ|w}ަ|y|s}y}{|{z|{{{{{{{zzz{{{{{rs|||{zy{{{{{{{i}]}Ř|ǎ|b}|Z}R~}w~}~V}|||Ɵ|s|{|wv|{{r|a}}^}||||t|||W}W~}}R~W~Lq~O~b~s}u}Y~l~UN~N~X~k}|||n|ת{u|`}N~~>~`~F|<d~f}ols~K~ʽg}O~e~^~N~j|Y}A?~v|^}[}L}t}F~B~B~@~\~b~M~W~U~L~y}m}q}p}i}_}a}k}N~u~~A~V}N}s}Y~@~R}Y}v|x||a}|ʈ|͈|Վ|~R}U}ψ|v|Q}Ƹ_}N~ĵb}J}b}o}z@~W}l|m{ʕ{щ|ʂ|g|p}х}}|x||ٱ{a||ڮ{n|||f}\}øj}Y}g}o||f~o~P}zK}{}MUo~Y}}a|\}}RFKG=It~g~{}y}eUT~O~Ƽg}g}Js~}`}w~׈}M~Z~:[½Ylv~~C~x~Q}||{|||ڨ|\}\}bb~߄~x~m~e~~e~i}ĺ|||ŕ|͚|–|z||[~~~UKEw~w~_~y}||g}t}d}}r~_~X~Y~r~]~D~}}]~h~X~O~}~~{~{@o~VTJHNG;z1}3|4ƺR~?~o}G~L~}M~|}}O~ľr}O~{AyA~w|o|b}Z~p~j~s~}|G}||{g|ѓ}ɖ}q|{b}}z}R}|y|{ʖ{l|i|r|F}b}<~|8~r}O}U}|I}N}z>~X~I~}B~Q~G~W}ʾl}zI~|}K}T}R}c|w|b}³f|ۏ{e|{j{e{݊{x|R}R}¹|q|q|{{y|ɥ{{h|s|v|ör|X|}L}V}݅|M}X}z|w||`}f}p}x@~r}b}S}̾g}x}A~@~n}o}yB~M~`~`~E~n}i}~<~K~I~N~N~M~Y~m~ǹZ~@~\}L~~H~L~CY~g}ڝ|}||Y}w|l}Z~w~c~K}}D~S~j}{}w}N~P~H|}r|ژ|z}p~U~M~|}j}q}F~m}|b}Y}|\}Z}||û|x}Y}p}q~~}֔|ݖ|i}_~F~E}H~g~K[P?F~f}U}|d|ۈ|N}j}U~\}ˍ|ϝ|w||||||||~|y|||Ĩ|ſ||x|{||||ż|b}|L}h}}6ļWU~T}}N~W~H~F~c~c~l~F~v}K~QA}H~m}K~`~d}^}|M}d}ǻe}˃}[}X~}N=?D~F~ɽd}Ͽg}O~{I}p}žk}~K}b}Qi~`~S~}d~V~ܖ|c}¾t}م}\~e}U}|t}|ؖ|k}̡|~o|{{{{z||Z}}P~R~}}߲{b}_~b}ρ}}C~H~l~~H}g~s}w}~D~m}O~X~X~}~S}ݓ|g}p}~}\~L~U}|}s~Ǿ^~K~k~|þ|޴||{|s}L~|ɉ|i}l}t}R}}|q}}üh}R}E~F~M~Z~K~[}n||{{}{{{¼{{˄|w}`~øP~A~|÷t|˿{|}|]}l}k}x|{{{{{{{{{{z~{z{x|{{{{m|q|{{y{u|ɣ{ȹ{{{{|{{{ƿzzz{{{{{{{¿{{{{{{|t}}}}L~|{||||||||{ƛ|Z}[}||{{{ذ|͊|H~e~T~I~U~m}e~ǿ`~N~O}|d|k|x|w|F~n~u}R}Q~o~z}K~}||{Õ{p|_}a}R}C~}i|xE}M|۞{u{zzt{~m{}{s|ɇ|t|{ڮ{̟{m|{{zl{}e|b|{x|j}|K}׏|‰|W}[}e|j|}T}_|i|^}ߌ||`|g|Ջ|Ŀ{z{{|||z~x{zҫ{|{}q}t}x}v}p}||޺|v}J~C~~D~W~~AH}=i~P}t}}}Z~j}`~|~Hi~O~x}L}\}Y}||v}}}}}}S~}r}j}X}Y}h}p}|z}|||||||զ|||||||}|}}||||f}ƿ}v}ُ}}ё}ؙ}}}d}||||m}ѝ}]~}}~}o}||{||͖|[}|~`mh~}z}||||||||||h}n}[}Ϙ|؋}m}k}ſ{}i}٢|d}ϊ}܇}߈}~}|||{|{{|x|{{{||s|{}|||||||w}n}ֽ||||֞||~h|||x}}N~Az<h~P~^}}K~O~B~~I}Ã|k}q}}}ª||Ⱦ|k}a}ž||||}||{|{{|V~w~u~L~p}j{{{||~|{{{{z{{{{|||Ι|a}_}k||z|{|yz|{||{{þ|̰|u|^}g}Ι|ę|o}Ľ|͉|Qv~}j~Z~Z}k~X~ľe~~~I~||s|k|}|}ч~v~w}f|v|\~CµZ~{I}`}]~=z9I~f~[~l}[~t~{:O~X}h}O}c}u}{E~~I}|t|S}`~\~]~i~e~_~A~a}Ƚ^}V~\}D}`}u}c~οd~Q~r}n}\}W}t}~?~N~>~q}}8~?~O~w<~]|{{{ǵ{t||˧{{{{k|k|U}Q}p}}k}v@}M}{G}T|]|xO|{s|O}G~J~G}k|c}yW|ȍ{|Վ|܅|݅|X}z}e}W}J}zD}}l~b}l|K}a~܌}͙|||x}Շ|y}P~c~j~|f|o|^}j}[~]~L}v|ø]}<Z~E~g~VZ~S}|`~R~|~~X}z|F~Dr~L~P~\}j~txZ~W~}]~y}c}c}ћ||]}U]MJX|GugNm}¹w|}\~Q}k|z||||i}v~U~¼r}R~K~~}t}e}q}k}|{}~}X}]~x~d~O~ހ}n}`}۫|݂}Ža~=~~Bu~Dw~GH?c~c~^~N~P~Q~S~{=L~~~Ź]~D~Q}|ƞ|`}}{}R~N~I~y|k|ʑ|g|}P|r|w|X}}B~q}]|w|{{{|~}]}Q}}H}Y}ʽi}p}µX}X}õX}Ϳd}d}g}Z}:~~9~}:~s}c}\}N}|n|x|új}H~E~Ź`~\~{}G~B~o}l}]}R}{|S}p}Z}ʻZ}u}g}l}m}?~H~}|l|~|Q}y}}^}M}õ`}t}z}S}L}\}ƺg}|}v}ºk}ԇ}f}K}M}V}v}Q~N~żj}Y}j}f}x}f}d}i}a}g}t}v}m}?~}C~zF~m}m}Ƚ^}Q}i}O~P~~}k}w}L~h~p~u~G~t~X~|~~}l||v}n}I~V~s}o}‚|j}s}ƻ}|w|v}Q~|H~{|s|w|ܙ|Q}u|³b|A~ȶ]~}T~b}~I~_~¿c~x}ȼe~JD~\~~V~^}ڨ|g|ü||||s||¹||~I~M}P}}O}Q~T~R~k}c}i}q|||||x|||ſ|n|Nj{n|L}f}]}a}l|p|z|_|f|o}q}A~f~w~~E~I}N~v}؁}T~z7@f~M~p~zC~üm}o}»l|z}}O~q}ǽs}|F~W~d}R~k}R~F~z}l~h~@o~D~~~^~W}i}i~}z~Ji}I~j~Q~V~s}a}}}y|ѧ|t}٫|z||w|W~½m}{{{s}֚|{|||||||̥||||||O}X}G~o~s}v|Z}G~~}|[}}úY~m~^}N}}Q~{~g~l}L~T~r~d~}}~K~`}w}r~z}s}|o}|}U~P~u}T~M~C\~]~x}c}џ||{}|Ϭ|~|ɾy|`}w}V}`}g}c|zU|Ր|ɏ|׵{{{ʴ{|k}T~~c~}}}U}g|Y}Ć|{{{{{{zz{{{{{]||ٗ|`}}Ձ}ݫ{c|O}|{{|¿{{{{{{{{{{{{{u||R}R}ћ||{{{z{|||||i}ֈ}ԡ|{Ī|||e}Ă}Ƀ}S~Y~u}|||{{v|y}||{{{|{|||{į{{j|ʎ|[}|Ҡ|k|F~h~zC}Z}_}{{zzz{zR|{ʫ{ӳ{{q|{c|s|{[|o||x|t{zj{}]|‰|p}ł}i}t|{ÿ{{{{{{լ{ߣ{Q}K~Y}{|i|{{֦{j|v|{{ij{||Ԝ|ĕ|v|l|{{~||{{{{{{{y||h}ԁ}}}||˾|s}˼|||۫|^}k}m}o}||||{{|}|||}c~b~r~Q~|}n}g}h}|||||||ΰ||||q}ƶ|||||||uu||||||||{||{|||}~||{|ܥ|k}ǁ}]~}V~΍}}}Y~_~S~ϋ}x}f}||{{{|И|n}Q~O~s}Z}|||vv|{|st|{|||̹|{{|d}||||||||t|{o|n|ڔ|»l~x~އ}½|o|t|u|}|w|y|̓|S}|u|k|m|o|z|||||||||k}MS_~{}S}}}W~z?~M~`~~7Y~E~^~L~t}P~L}n|}t}}ҥ|{{{||΋}[~k|{{z}}{{||e}t}y}|{||{{||||||||{{|||{||{{{k|n|k|ܦ|~Y}_}v}}n}Ջ}܏}}Z}d}g}||w|ڑ}h~i~K~]}}f~ȃ||t}fePy:˼^~n}z|z|o|{L}v}s}S}i}|}s~Ex}q|~Xq~J}I~JqQl}|ü{ۻ{u}e~c}\}}@J}M~}i}j}r}~?~q}D~T~W}U}w}zA~b~ǻd~T~B~s}A~p}o}q}U}b}ȼd}S}G}̽]}˾[}P}P}Y|Q|ǽw|q|k|ʾ{{{{e|j|j|Ⱦ|}`|z{{g|}|W}p}n}|w||w|^|υ|U}L}T}P}v}c}Z}Ɓ|W}c}j}c}|[}g}||M}c}Ľ{}ג|Q}I}>G~||Y}F~j~m~xD~ك|b}|o|{{ˬ{{_}L~P}Z}h~GEKD}d}F~HNk~R}|y}KI~}S~~S}|~Ir~NhRM~X~o~V~y}~|~_~a}N}ڀ}]}b~LVr~O~r~CBku~y?~}U~S~~}BIKAk~R~}}}څ}|N~R~Z~T~^~N~yL~a}|͆}|y|{{||}ł}b}ʱ|ů||Ӊ}\~¾j~w~ž]~S~x}|۵||ޭ||J~Xu~r~J~i}|}w}|{{}ʆ}ˑ||Ȏ|g|ƀ{ķy{p|K~Z~K~B~j}ȿd}[}م|U}|}n}M~S~I~w}d}Ƚ`}W}³[}V}f}ǻd}e}c}]}`}e}J~x}F~h~_~a~¸W~t}U}l}|=~B~Q~Y~B~d}Z}a}c}L}؁|h|Ƚu|Q}Z}[}U}]}d}u}C~x:~[}V}X}n}l}}=~E~K~~=~k}i}r}Ƹb}E~P~E~|;~s}a}c}R~J~y>~|A~F~}Ļe}}N~}C~Q~Q~l}`}@~A~zB~X~_}~||}H}D}O}k}o}k}}}H~D~t}Y}K}}@~ɽ`~`~P~@~v}{|p|F}k}ϾW}~@~^~ƺ^~N~Y~N~y}D~`~x~Nc~W}L~~|}e}l}l~n~Q~K~?b~M}B~O~L~y~LEHD`Q{}y}ɔ||k|{||||ŭ|||ģ|̩|ț|מ|t}N~Z~i~s}|J~Y~W}U}|T}|S}^}|}^~GMUL~G}O~R}}~w~J~~DLd~R~|}ѳ||z|ƽ|U}E~H~x<~g}r}l}~}ȼg}Y}AEX~r|}Z}’|||k|ż|f||U}D~P~H~V~R~E~f~||J~M~}a}W}{}n~c~o}|||||||w|||{ž|؊}e}M~Q~a}~|{|þt~Ї}d}u|o|}|o|b}R}E~O~Z}P~v~h}z}b}p}X}P~K~W~J~S~|>I~ɿi~s}L~zG~f}`}m}|y|{{_}Q~v}P}[}IdQ~t}_}[}\}s}p}a~K~]~N~C~}}{}i}Z}ю|ľ|s|{{{{{{{{{{Ȭ{X}_~t~}~}}k}`}j}a}֗|ݘ||{{{{{{f}r|m}L}|{{{ѽ{}e|g|΁|q|[}|y|}|{{{{{||w}}}||Ϩ|h}H~||||||||||||||||||||p}M~E~u}R~M~x}\}|{{||ړ}p}l}ю}~U}|}{{|{{{{{x|||ڊ}z}ÿ{{{{|{{{{{{{{zz{{я|R}|]}k}K~[~\~Y~b}{{{{{zz{h}ý||{|{|{|{|L~z|X}܏|o|_}}|{t||p||||{{{{{|wx|{{{||{{{ſ{{{y||||||||||||||||||{̺{˚|||׼||~^}^}V~~I~v}z}}}}f}}|||||||{|}||v||žt}}|Ѓ}T~Ӑ}||||׬||a}||~~|||{{|||||x}|ɹ||o}w}p}V~a~M~H~N~q}Ǩ|}||||ž{}y}g}d}|{{{{{wx|||||||||||||||||||||{~|Ԯ|g}l}Ԟ|||З|}|~f~x}݌}}Ɋ|v|~||`}P~U~n~;i~\~T}X~A}Y~Q~y}Y}k|d|z~HIDb~J~V}Z|{{z{|}}}|||{{|||||||{{{{{{|||~|{|j}||||{|vv|||{{{{|||{{{{{{׻{Ľ|c}}}S}Ѓ}ԇ}R}zB~d}^}v}»w}~}}}X~U~y~p}^}|k}T}r|i}U~I~D~OȼT^~I~~}Ļv}s}}D~h~E?Q~Q}ь|~V|~R|b}Gl~}~~E~Q~d}m|{`|Ո|T~˽e~G~{B~e~A~R}}C~Ln~M~D~F~O~C~~C}a}{}T}`}j}_}b}z>~}@~x}G~l}k}j}B~T}m}K}w|K}Ķf}|o|ނ|c}N}s|Ž~|h||M}^}S}y|Җ{ӗ{zV|]|~P|m|i|ÿ|o|ȕ{e|S}v}]}h}ڎ|a}c}׆|_}}ҧ{z{{q}|zC}l}^}g}g}ٍ||W}T}\~V}|}٨||}}U~w~^~}^}m}c}u|{ӱ{ܷ{Ւ{T~F}w|||~F~@u~}||U~l~@l~Z~c~j~;N~`}x|Z}Ǽr|؃}~z~v}W}}|Ï|¦|||{|г|֫|Z}S~C»d~e~^~Ǿk~~u~]~S~g~ހ~zAt~d~}}{}}}ȋ}^~܏}d}e}|Ѥ|ߩ|ע|ܤ||\}~B_>H~k}}|f|p|m|k}y}[~`~ÿ}W}||c}k}|ò||q}||z|~|s|d|m||r}Q~ی}}`}f|u|q|m|\}_~R~y}m}G~L~_}k|G~AGC;g~W~Q~w<~S}c}·c}m}x}^}ɽ{|e|_|J}X}^}|~P}zN}ȉ|c|j|a||t|}||¸|t|r||t|Њ|Z}}҂}Y}[}\}Q}Q}|}^}V}zQ}ø|g|\|]|{˥{{{^|M}V}X}W}R}G}[}ǻd}x}O~G~G~N~Q~N~E~o}[}º_}~@~n~܅}||n|ŀ|||`}}d}zG}N}|P}]}w}d}οd}v}@~u}n}d}̽b}^}M}Q}f}d}c}y}l}@~b~{7ɼW~r}F~k}}=~<~;~j}z7~u3~<~G~=Y_>C~M~O~=~I~U~j~U~s}]~»d~}O~f~j~BOP|@HR~c}R}?~?a~I~Z~Fo~D~}}R~[}|o|u|r|||||{|{Ͼ{{{a|z|j|b|~|^|v|W}Z}^}}J~J~L~\~K~}b}~}`~OLz}w}Z~}g||||֕|P~K~b}x}Q~z6<`~|Z}U}zE}e}_~y~F~l~q~~b~Á|¶r|s}C~ǸX~j~^~d~\~`~T~{E}Ʌ||Y}}O~x~[~f}[~Y~V~|IJ||{w|O~QHI~P~w}Γ||÷w}V}՚|y}y?Y~O~˅}|ſ|}l}k}b}[}}q~E[~S~}\}ٖ}|}||{}Ӌ}X~h~{@q~=Dh}D~j~b~~>~L~w}c}ȡ|{{{{|Y~V}m|i}Ԝ|z|||}}[~žm~J~S~{}W~a~@k~R~M~s}͗|{{{{{{{u|}Z}V}n}Ӝ||{||X}t}x}zD~ļo}}F~H~F~}J~yG~}z}ʿs}y}žw}ľx}Ѳ|||ʥ|¡|u|{|{{{ƽ{c}S~|{{|m}y}Q~l}{zu|{{{{|{b}U~w}ɾ{{{{{{||||||||~|||}||X}o}|A~Ձ}}||xy|{w|||h}ڪ|r|{ή||||{{{{{||{Ő|_}p|ȷ{{|e}\}x||||֏|[}O}||ȥ{{{{{ƽ{{{||ï|r}}[~}ʟ|}m}|||{{{{zz|{{{|{zz|բ|a}ǖ|d}|ڝ|V~]|{||{{||{{{||||vy|||||||{|Ü|w|ڴ|~`}q}|||||||{||`}}R}Ӆ}\~}{|||||||||||||||||{|||{{|{{||¼|_}}i~ː}|Ī|}}e}q}|||||y}ʘ}}}}}}½|||p}t}~}h}|{yy|||ŭ|x}}y}||||t}|t|k}|m}e~}|||}|}|{|b}~o~q}{{||{||||||||†|R}q}ɾm}Ɍ||{||Ä}ޠ|y||ٮ||ע|љ||p}y:~@~Q~N~k}˚|_}}}]~R~a~]~N~Y~|l|S~s~\~||||y|ҹ{{{~^|ݕ}΁}k}|||||ƻ{z{|||||}||{z|{~|Ӡ}~|`}{||]~x~}||{u||y|||{{{{{||{{{{Ž|h}j}J~|}a}ޘ||U}h}a}X~·l}\}s}y?~X}|_}U~}||e~[~~E~K~}~Vɽb~źm}y}d~j\_~f}Z}}U~c}Q}w?~?~^}{G}}B}EkhV~k}||z}{}|޽{{~{{s|߉}N~q}b}j}z~O~¸W~S~o}s}g}x=~w;~D~B~l}g}|9~}<~Y~W}L}|C}H}P}R}D~@~U}u}^}|Q}c}O}p}R~u|{{ج{{l|g|Y}A~Z}S}X|j}<~E~Y}r|e|j|ӭ{{ů{{{|||Я{ľ|\}zY}w|l|o|܇|M}W}Pj~Z}|O}|S}i}}ƾ}|{{||{t|Ɠ|g}|o}Ԑ}|O}Ⱦs}P~Q~i}t|||l}o~V~O~}}~|n}Y~}n~O~\~u}O܀~Ί}|{g|X|H~LSsru~_}`|o~niPDt~i}c~`~a}_}{T}v}Jj}{}ю}}~~l~~\~{}j~K|>Z~}}x}a~[~Q~Y~ݓ}y}u}Ɗ}h}u}}_}}VOt~X~t}[}d}e~C~;ks~||{||||||x|||||ߗ||Y}e}g}g}|\}c}t~\~y}R~t~ýi~O~BV~ľp}ڊ|k|ޓ||x|e}n}W}[}w}X}r|]|||ŵb}t}}{}Ⱦl}|m|j}>~G~E~w}w}}=~l}ƴX}Y}˄|j|s|ȏ|m|n|T}l||t|~h|ո{̱{إ{֒|f}_}g}g}}|M}P}ƽg}Y}P}]}{7~F~;~@~y=~z}yA~z}Y}Q}¶\}}e}|س{{Ā|Ԉ|^}΃}}q}ǿ||l|d||Ǔ|Ÿ}|Ջ|}|~|M}Z}m}|:~=~|;~|;~x;~d}V}L}G}||I}Z}w}v}e}U}X}h}b}Z}`}H~F~v}D~}d}p}ĸf}^}e}y@~e}n}d}`}k}P~K~a}k}A~B~A~O~\~}_}W}s}~E~G~}?~b~DGAJDw}H~L~c~˺W~{7OT~r}T}k}u}a~P~a~i~ޒ}ҵ||{{ȣ{\}}h~DS~D^~K~P~}s}L~üa~m~w~r}||c|{{{ӱ{{j|`}]~Tf~U}{J~L~}Ó|ɠ|q||e}{{}|||h|W}u~m~a~R~|đ|U~}N}ց|ɼh}s~QG[~AhF~B~O~D~Ⱦ`~ºY~z}Y}ɼd}X}Y~S~\~|~a}q}x}||||}}|{|~|{{}|{|k|P~x~f}f}K~R~C~?~F~}D~Z}u|{ɽ|ǽ||i|{P}^~Ľ^~U|P}ƚ|||r}^}y}DT~}S}S}j}m~An~·Y~Ǿ`~M~I~Z~̻h}n}č|k}}\}g||}}^~c}ߍ}҄}|Η|\}^}d}h}~}Z~l}c}y?~p~k~I~}s}s}o}||||||[}y}~}r}~}f}W}|||}K~U~}a}}}E~p}l}}|}W~e~l}Ϝ||||{{{Ω||||a}|Λ|||||{{W}b~g}||{{{{{{{ɬ{R}q~|Ϯ{~k|{{{{{{||{{{{{{{||{|}||||{{uv||{{`|p}f}s}t|p|ʯ|s}j}|}|v|{{Ǩ{p{ɾq|z}k}{o}|{L}[|i|ޫ{g|V}T}w}O~j|{{{{zzzz{~{{{z{||||~|{{{{{~|{{{{{{}|k}`~|˱|̫|v}b~m||||||{{{{{{{{||}}Ѣ}|||{{||j}`}u}^}||||{{s|}o~o~k~l~\~}T~E~}n}d}ݶ|ǹ|||||Џ}[~e~ׁ}t}i}[}ׁ}W~I~ľu}b}ס|~|p||||{K}N~c~Z~`~Y~||||||}{}x}ž}}}ޒ}ە}}w}~}m}||||||{{||||{|r}|d}X~`~|}n}p~l~Eƺd~|}|~}|||||v}Ą~d~o}{{{xw|ts||||y}a~}|X}`~~=e~T~C~ć|Ś||Ŀ||||{|}|s|’|`}|Z~EPk~i}|x|||øs}f}|x|{z|s|}a|t|Ŏ|d|Ƀ|`}}O}W}[~c~V~ļb~Y~}_}w|||ϸ||{{|r}|d}|||||ٜ|{{~||{||n||]}s|f}U~ƽd~g~}Ɣ|{|~S}N}|M~zD~c}|t|~d|v|Ҏ|c}}t}ɾu|t|p|׹{̎|`}M~j~Q~S~[~\}r|w~Nz;^~]}Z~Wƿf~X~q~D@y6M~o}V}؊}s}f~t}p|T}ۅ}h}w|d}Bc~C~}g}|m|{{{{{~{Ι{yN|ȷn|H}g}L}ځ|V}`}d}CfƺX<f~P~V}^}_}L}>}w|Z}H}M}u}N}i}L}o|^}t}L~H~X}B~v}L}||]}|`|a|X}i|j{{{t{o{{r|ǿo|Q}|j|Y|ǿ{{~{z{{{{|||{u|z|{|w|¸|Ӑ||\}ݑ|X}^}t}p}_}|Y}Y~|~|~}w|w{{U~Ƃ}z}U}w|»e~@d~K~_}|Ƚy|d|g}`~L~s}I~I΀~Ɇ}}}ſp}P~k~h~]|~~|H~]|R}T~i~N~x}n}Q}i}Z~f~H~V}ۈ{yI|c~Qi~}\RR~^}ȿr}R~T~Q~t}P|~y~m~b~}w}f~KNBGAŽ]~DLNE~<@K~i}L~Z~c~b~J~}}S}d}|||v|t|ݽ{|~m|Y|i|Ǖ|z}y}e}z}a}|{{||{¤{b|p}Xe\]SGEI~v~u~S~d}^}Q~؁}b~s~Gp~{}ż||լ{ܥ{Ռ|R}R}Ԍ|l|v|||Ջ{k{s|}J}ֆ|zL}h}V~T~}E~l}n}z8~H~ĺZ~M~F~D~I~O~H~M~Y~L~C~F~I~I~H~>~m}[}U}t}l}H}u|||N}|yG}N}ɼc}n}j}Q}[}f}k}`}t}u}d}B~u}p}B~?~n}j}i}j}}@~v}S}S}n}\}}y}e}G~~@~a}k};~?~L~e}Z}X}R}Q}S}o}?~A~@~x=~m}j}p}_}|}J}Z}?~ƻV~ȻW~P~m}m}E~l}g}n}r}A~A~F~r}[}b}Żk}T}úk}x}u}C~y>~D~E~s}}}zG~o}|f}g}d}|}N~E~}F~h}p}ľ{}׃}R~h~g~H~[~s~U~t}N}}L~|n|{}Z~P}û||~}}r|Я{{r||V}j~stnbcddTLJeG~@~Չ|m|||t|޳{{|~||ѡ|{}s}|~|yY}x}|W~~؀}v|{D~V~O~r|Ð{þ|a}ѕ||ü~}~}OlJ~d|o}N~^~Cf@ǽ\~g~b~|A~g}|}ǿi}S~K~o~r~^~}W}R}ýc~n~^~}z|~d}ü|u|{|||y|||X~z~X~|N~Ƽl}}=~V~C~I~B~k}J~|}Ƽ|p}R~~}ç|ӹ||||ڶ||[}J~Y~En~I~ȿb~K~r~@EN~n}Q~H~Ɂ}Ǽp}o}׆}M~P~}i}]}|||{|p}܀}L~F~Q~q~B~}}]}|w|˺{s|ɍ|h}~}m}T}V}c}þ|_}u}žm~R~l}l|o}D~L~r}Ӟ|ܓ|S~@z}d}}b}m}R~W~Ah~r~A{}}_}|ǡ|Y}|x}}o||}|}c|x}}}i~z}q|ʑ|Ġ{w|\}{{{{{b}Ə|Ù|{|{|}}|{{{{{||{y|}f~h~q~K~{{|̓}F~øh}}N}h|{||~]}j}ʈ|j|ķq|{|}|¯{|||||{|{{{|n|̠{{{{{t|w||{z{{{t|{Ҿz¸{{s{x{{k{h|{{{{{{{{ȿ~|h|ٛ{ȼ{{ö{{{{||{{z{{{{{{{{{{{{{{||{{{ԇ|ԗ|{rs|{z{{|||||{{{|Ѫ|Ý|{|||||^}g}l}߳|||||||||||z|x||U}o}\}ޓ|ϕ|r}Q~}|||||||||ռ||||{||||||W~÷p~S~Ǎ}{b}||||s}޷|¾|||||||||||||||ÿ|{{||}z|{{||||ϩ|r}U~o}|ϵ{q{f|“|y|z|||В|a}}}a}ǘ{{|||||||||yz|{{{{ۿ|g}ť||||}|]~\~٧||||˲|||Ü|g}W}͏||X~RagO~Y~}b}f}|ʘ|ș|~|{|||}|}||a}V}w8~?~S~:<YKO~|}܀~`~}c}ҭ||ҭ||~|f~܎~_~|ً|Ƒ|Ȍ|Z}]}ŋ}ď|{ݗ}~R~a}|||||{{{k|}`~||Ƶ{{}t|{l|w|V}p}Ʌ|{z|}||Ќ|Žp}Q~F}i}S~j}q~jbIKs~و}ˎ|||J~e~n}d|߉}|CHȽR:b~U~{}U}M}X}]~g~O~R}Y|L}Z~t}ƹw|Y|O}]~|Dm~Y~W~}{~p~v}U}݇|[}p~^~\}S}v}a}h}r}u}w}c~Iz6f~^~F~R}N}R}o|N}}U~|s|yD}R}[}N}Q}A~I~Q~P~_~\~]~ɶL~K~O~G~N~W}ߘ|†|і{Ě{{[|ƽv|o|{d{f{G}ʽ`}|ƌ{k{q|||ؙ}Q~[}l|}n}Ǿs}n}n}]}p}~}S~\~S}||g}}Z}ǁ|y||L~h~m~T~m~c}|^~W}|؈}~}|Ƽl~W~؁}~}J~v~z}Ļo}}}X~EF\~X~v}h}T}}}J~]~_~BcQ`~}]}q|q}{I~}T~g}L}D~<~p}Ⱦ|||||a}M~T~c}g}O~O~{D~Z~o~m~b~KZ}O~v~g~Ⱦ^~a}|E~g~A~u}چ|O}_}n}r}P~F~Z}߉|w|^~Mo~d~P~}}Z}{|ϥ{֡{Q}AXu~W~p}D}R}^RB]HJCP~U}i|ӭ{ē{Ą|Ί|X}T~s~Fy~c~|~{~~z~x~T~h}i}Q~p~R~~}X~y}r}n}_}і||^}X}S}t|r|X}_}|{}{r{{{Ԯ{ȿ|t|˯{{|Z}`}\~N~}}P~L~{}ƿo}_}S}d}O}}N}|҉|]}[}Y}Y}ƺ`}ȼa}s}H~c}a}Ÿ_}ɾd}}B~L~D~|>~w}D~c}V}n}t}t}Z}R}Ǿg||{Ż{{||p}Ą}i}΁}~I~F~K~D~D~?~K~?~R~^~Q~J~y}c}t}j}[}]}Z}W}^}k}a}zM}u|x||g}{}}~C~H~G~G~F~h}Q}֋||Ȏ||¶p}ƹl}X}~O}Έ|Ȇ|Â|{O}Y}R}|؃|W}R}L}h}p}z}F~}}ؗ||V}]}žu}}}~}l}}X~C~K~p~`~N~K~S~}v}}}u}Q~}~K~wD~Z}T}ź|{|ҍ|h~ā}}e|{q|Z}]}}\~c~P~`~f~<WMi}Q}z}}yB~M~ŀ}Ӣ|ͺ|||||z||||ʴ{Ž{|||y||||x|{\|~L~b~}J~z}|{||j}x~\Bq}x8}X}{7~y4~?~Z~|8>EHKHj~ٌ|Z|}{~ك}Ę|s|e}O~X~V~g}}g}Z}ۑ}s}|||{|Q~I~N~A>m~}}ž}K~N~g~T~w}|l}}g~d~_}v}||i}b}||r}c~~}i}a}}x}j~½e~D~f~Y~}ߟ|\}v}Jp~R~X~}u}n}|`}t}}H~\~r}h}Ɛ|~q|{{{~~|{{{j|~|D}i}B~zA~O~~}K~P~c~x}}b~~A~f~@P~i}t}{O}_}f}]}_}|N}P}žg~O~|;̽X~y~Ѐ}W}||ڳ|հ|}ߎ}x~i~|U}|I~Z~s}}}|v|N~]~w|Ƭ{~g|{{{|}}~W}|{{|{}~|{{|||ԭ|s|˧{U}i~d~O~ߕ|{Ž{{w||ϊ|Y}]}|{̔||{zzz{ºx}{~|}Q{s|}e~g~`~q}{z{{{}u}ӎ|{{Žs|}Q}~F~q}I}M}d}U}b}~|n|١|ݐ|Ƚr}ƽq}_|ȱ{{{{xy|{{|{{{{{ƭ{{{{{{{{{{{{{{|||ɾ{{{{{{{|||vw|{{{{{{{{{{{{z{{{||g}ʇ}Z}||c}z}g}|~|||{{||||{}̓}½~}||~|||||y|t|}|||||۲|_}w}ʉ}nj}|||}}͏}ޗ}`}d}ܴ|||||z}Ŏ}||yw|rr|||ww|{wv||̼|z}|||v|h}n~BKEP~o|||||r|~|R~X~Y~g~Y~ݑ|{|}}l~}˯|||||{{{{{{{{{{{{{{{|vx||||{|||}R~~G~o}͊}ڊ}ֈ}g}[}ߗ}i}|||||ɉ|b}X}}E}Y}n~C^~Y}p|||{t|ݺ|`}ԁ}}u}^}t}X~X~I}Ǽt|V}P~V]n~y}zR}ƾ|߮{{`}^}_}ľ}i}n}|{||յ{{}||ғ|û|||Á|^|{{i|Z}U~Pi~x}}m}y}O}y|n|i||d|p|x|{{{{{m|¹|׆|U}m}\}W}Y}܈||Ǿq~V|@E~S}S}|e}]}k~p~?Gb~Z}|J}\}V}`}r}p}Z}ޖ|}M}zD}G}ɺh|\}Nf{<S~f}:ƽ\Q~;u|X}֣|||{ި{Y}_~m}h|}cEɻj}R}k|t}R~Ԏ|j||p|{|c}[}c}[}v|a}xA~K}u|оe|z|D}a}>~c}Y}H}Ƚ_}Ÿ[~g~L~Z}`}P}v}z?~t9~P}L}Q}ø_}F~~}]~h}|xI}Q}}F}{||ׂ|x|R}]}L~}v}Ǻ`}L}H~D~L}J}h}_}R}FX}ϓ|i}Չ|}W~Y~}L~n}}R~{H~{P}Β|l}i}O}N~[~U~}c}y~L|~T~j~U~n}¬|ž|f}c}t|{|||`~Ր}o~Zey~q~Y~i}ؑ}f~M~|JY~O~v~E}p}p~s~BF~»b}L~y~^~R~j}O}g~b~A~P}Z~ъ}w||||o|э|^}~]}Ѕ}}Y}|i}H}t}ǁ}}|}[}|X}`}»o}{}a}InVv~Y~}}}U~L}z|?~c~HFFHO~F~z>~`}X}E}k|q|Ɓ|ڀ|N}H}m}t~HZ`žRĽTZX_~R}W|Ї{e}|~POX~[}||}x}z?~ùe~{BI[~j}P~]~d~}P}P}S}|||y|{{{{|}f~f}||{{||{{{{|p}ց}|M}]}P}f}m}r|`|ܚ||ʿv|ۍ|~|ߐ|zL}Y}Ϳg}j}q}B~T~R~[~O~zC~q}Y}[}Ļ||͐||Z}`}T}U}ͅ|`|ۼ{ɶ{{y|l}}q}Z}|}{H~E~}}F~e~c~r~:k~O~ɾ]~Ǿ]~n~S~zB~}߀}f}|m}[}Z}b}k}k}m}`}|ޝ|e}{}{B~|@~@~F~N~N~Q~ŹY~U~N~Q~V~R~k}|ٔ||U}[}^}e}v}I~L~E~E~O~T~\~h~T~o}^}Z}j}ڥ|ļ||{{{{{{||{{o||Ԏ|]}Z~Z~p}w}^~^~[}L}z}M~}υ}W}}ł}|X}y}Z~w}}S~Ӊ|j|m||}~EX~e~DK~^~m}m}Δ}||||||||ّ}ۉ~x~k~Q~ܤ|s||ľ|X~Shc`blmMo}b}f}b}̀}M~o}u}D~F~L~ķN~a~d~;L|=Žn}s}֏|t|n|{u|||c}n}|¾}}e}b}Т|o}߉~S~|}ғ}ʵ|{{|||a}{~\~w}^~c~C~E~q~PZ~{K~~N~O~ºj~؀~T~}U}`}|i}|u|~|̩|||ø|ߓ|Q}}u}}L~G~G~}g~N~~L~R~d~]~|H~X~W~F~~I~x}y}Y}øg}T}|~l|{{{{{{{{{{{m|h|T}K~]~k}{@~n~z~b~a}||||c}l~@g~]~k~^~T~W~]~y}z}}@~ʿ_~M~E~r}т}V~þ|}|V}|d}\}p}ۏ|o|{|||k}K~q}J~a~π}|w||||{{{{{{{z||y||{{{}||{{{{Џ|t~DKy~b~S~V}||{{{{{{Ծ{|||k||||~J~U}b|z{ɧ{~|b}|}P~{||~Q~R}{O}K}ûv|{|{~||a}s}`}y}f}e}H~O~X~k}щ||ؒ|r}T}}_}ć|||k|p|{h|l}}}^}{zzz{{{{{{{{{{||{{{{rq|||{{|~|{z{{{{{zz{{{j|S}Q}`}_}d}L}P}R}Ⱦb}V}D}t|f||v}~}||||||||̞|||ߝ|^~CCu~L~|~|||||b}Z}Ҧ||}u|z||}J~I~W~N~}}}n}}ƿ}y}¹}z}||{|}||||||||||ݶ||||{|{|t|ީ|}w}b}\}σ||{{{{{vx||}||}}|{{{{|||{{||{{{{{{||{{{||}խ|||||||ǘ|W~U~Q~{~xJ~|m}}}}u}||Υ|a}||s|u||u|}LPMNH[~||շ||}n}|||n}b~ށ~Mv~NZr~?~]~L~}}f}|}S}Ƽ}}Y}m|̱{y|}[~zH~h}ל|ˏ|{M}d~z9w}ļp}·n}|ߣ{r||Z}|j}k}b|{է{y|V}c}}¶h}o|p|ŭ{{Ű{||ʙ||V}|S}_~l~H~N~N~{B~i}a~w~V~ſt}|}\|n|d||y~ĹUDC~<~q}}c}V}a}k}Z~ׄ}X}܈|O}x~}~I~l}P~S~ԁ~`~u}Q}}I}[~~J~V}|ŗ|Щ|ş|q}_~m}t}k}þx}}|||g||j|ɧ{~`||i}|øy|`|w|L~O}I}c}ͽ[}B}R}C}j|Q|~I|X}U}Q}m|D}Żd}G~D~i}N}R}m}·c}`}^}O}C~b~ɹZ~C~y@~]}r|s}i~x@~l}|}~}n}n}q}f}Ԁ|||^}P~[}T~Y~Ĺj}N}]~Ye~^}_~IA^~}n}|j|ڝ|Ď|n}n}o}}s~FLIq~}|g}^ifº^}Ev}n|M}v~l~}Ŀ~~d~^}l|j}}dYU~|}v}|x}f~O~Q}[~~I~s@]u}R~P~W~q}e||y~KU~Z}Z}x|{{{{j||l}pElLq}u}o~Uh}ľy}~~_~a~y~j~c}~}}ג}p}}V~ǿs}|d}~Oýj~o}||U||k~Gs~a~JcRGZX@n~N~ùR~]Uc~O~Y~d}M}V}O~LŹOx2N~}j}O}R}O]Lr~|}w|ׂ|[}k}G~\~L~ƽm}}s~_~@~I~@HOW]TPI|>~e~h}|s|v|{{{{||{ÿ{|۰|ƛ|{{{Ѿ{|q}|{ٍ|}}m}E~}A~ûg}i}f}|{{ɓ|i|d|}|Ԙ|h}¼|ӷ{{{{{{|{}۰|t|{|ڻ|}}L~P~M~D~I~|C~I~K~~>~Y}K}||أ|r|ϊ|w}Ś||ƿ}_}W}|{{||y||n||؞|^}f}u}~F~x}w}y>~{>~v}y<~|=~s}y}j}R}֛||{{z|~||֒}|K~}h}y|{q||ϗ||Y}~V}Z}c}i}f}\}|Y}u}||||{||{{x||[}}żt}i}m}}W}z||||ş||{|k}Q~M~ýf~~`~V~Z~o}N~R~\}g}l}||ޘ|X~U~g~^~||q}|||||ÿ||Ľ}w}|y}|}N~ýd~i~~G~V}z|X}o~Vq@n\ľYb~;};oeM~|E~X~^~R}{K}g}V~DMTacgmk~y|Ӈ|ż|||||||m||t}J~Vdȿ]_~V}ĉ}n}i}ߜ|қ|}^|ٗ|с}T~MEX}X}zBFP~q~CFs~P~w~m~r}b}~}W}_}b}e}`}|}|q|||Ȟ|}\}t}}U~|I~Z~M~h~W~W~X~Y~`~J~}}m}|}|H~K~`~N~{K~y}|ҩ||ܲ|s}}||w|||e}t||{{k||Q}{}B~p}L~DS~J~n~U~݀}ʾo}ܟ||||S}o}c}|[}n~m}J~~B~u}m}ޣ||~}W}}u}U~U}T~E~Z}b}X}W}h}e}q}u}{X}ɛ||ݪ|[}އ}|{z||h}՞|{||||{{{{p|ŗ|Љ}}[}^}d~Z~n}d}|b}U~S~P~M}]|{v{}{ޯ{U}|}d}}|{x{]|S}l}y|z{f{f{y{h|{|U}a}N}p|g|̿y|o|]|u|͉|g|{j}[~u}~}[~M~I}}f}[}s}y}}|t|Ǿ}ލ|ţ{{{{ģ{ݪ{d|k|t||d|zz}{{{{{{{{{{{{{||j|n|||{{{{{{~t|y|{{{{{{zz{{{{e|Y}w}N}N}f}X}^}}D~}Z}|z|q}]}}I}M~}U}Z}\}yQ}ԓ|ƌ|Í|Ĕ|¹||‹|g}}]~P~֠||ٲ|||||ĭ||}O~[~u}ȥ|׹|Ş|Κ|̐|ѐ||X}Z}m}a}Z}x}y|{{{{{{{{{{{{{|}l}f}w|¿{{{{{{̾{{{{|n||z|{{||||}}|||Ǐ}W~}R~b~`~W~}[}ދ}e~i~f~ٔ}ڻ||||Ϝ|}{E~S}y||||Ƹ||o||m}|||{||||i}h}a}}J~s~V~s}d}ۈ}R~X~}e}Ľ|||z|ܲ|t}o}{}z}{G~~~o~e~û`~g~_qZW{HW~}ӂ}|||{{~_|h|‘|z}Żk~]~m}y}Ԁ}}}؆|[}g}_}ȿt}S~s~ہ}u||m|||͉|X}Y}O}p}ͽc}P}x=~}O}Z|Ͽ{|Ȼn}l}[}H~C~{C~W~L~L~}m|Ʉ|Ԙ||Ȍ}h~ȿv}|~M}ׄ|܎|T}õX~w7B~V}zC}`|{ߏ{zE}|d|~}z~o~O~w=~L~w~ĽYZɻ[~{A}P}X~n~j~W~s}}j~p~}|z|Ť|h}ܧ||m||ģ|v}ǽg~F~_~}]}}~IS~}}S}a|{|\~|}||u|]|f|\|S|ʻt|Q}xD}P}T}_}ֆ}~H~K~h}|V}t}n}s}Q~M~h}a}~;~@~F~B~o}R}N}M~Y~<~s}s}X}[}{}p}R}j}m}}}l}úo}{}žs}T~x}O~w}c}K~GTZ~}C}W~i~F~B~P~Z~m}¾|Ъ|{}Z~}y}^}}\}a}l~p}|{|v|ݘ|J~JD]~Y~R~}o}s||]}~K~X~]}V}k}h}ԇ}n}{{{|m||˥|ɛ|U}[~Q~a}[~O~|k|֦{–{ǚ|X~އ}|}v}~b|{~b|e|̌|Ɇ|U}|D~t~DGDżc~|E~x}X}L~þiPu}b~þhr~}ǿo~Z~r}l}}~SJY~H}O|[}P~}}|Z||P|X|P}D~]~q~h~ƾ]~K~P~x?|C[~W~H~i~{~}V}e}Q}~|K}U}D~ƺR~9GZN{;QVkX9BFw~y}A~`~O~N~n~c~\}I}I~>PYBH@`~z7b~T~n~w~^~`~r~r~T~O}R}G~N~X~K~]}Z}e}z|d||||}|{{{{{|}|{{{r|||||b}͎}x}j}||ݸ||{{||{{|||||||{i|}H~V~u}k}u}h}c}}}||`}Ž|{{{߽{|~t|{{{{{||||ӛ||Ǎ|~X}{Q}|T}b}zC~p}e}_}s}k}S}|v|t|č||{{~|{{{{{|||m|{{n||i}}M~W~R~G~}}G~R~R~}҉}_}||{{{{|||||||v|~n|{{|ڠ|f}A~ŸR~g~<@s~}I~y}Ɔ}j}֠|j}є}g}Ș|e}l}h}U~w~x~t~»p~U~t}||||||o}`~V~a}e}m}}Q~م}o}|}|~Wǿ_N[}k|l|d|{I}T}R}W}}V~e}K}z}Z~e~ſf~S~F~H~U~^~d~\~M~{}}ߓ}k}ٴ|^}n}{}{?~a~Q~i}m}ϔ||||||ʄ}[~Z~|@~k}d}z}o}c}d}U}G~H~b~v~c~~@Y~w}ԇ}}}}y}Ӂ}}ӂ}}ʆ}x}|}t}d}d}Ƹq}h}]}}e}ۏ}x}q}O~}E~O~Y~{@~Q~N~M~I~}w}m}H~m}}Z}g}|ܢ|||||٧|ݨ|i}||~l|½|~|i|ѐ|z|e}\}b}h~\~X~Ļj}}W~`~:BMp~O~X~c~Ž_~X~Z~U~D~Ǻm}L~q}I~u}W}ߐ|w|Օ|||U~h~O~m}|||½s}|n|ǿ|R}~}S~j}b}m}o}N~g}r}ԧ|{{|ՠ|N~Ha~Ƚ||t|{|{{{|_}ޕ|u|þ|y}v|~n|ү{[|`}Q~H~Ⱥ\}Y}H~T~{}P}t|ʾv|c}yB~J~{=~?~G~m|]|[|^|[}Z~z~v}p|]}V~x~T~D~W||X}z||{F}[~w3`~e}P||O}|ۤ{ܺ{|H~U~U}բ{{e||~|{{z{կ{{π|~C}R}k}p}l}d}ގ|t||ǔ{z{{zzz{{{zU|M}:~9~A~<~E~j}u|t||||{|¤|z|n}|{{x|\}r|{{{{||\|̗{[|a|ٍ{x|i~h~{}|`}j}}O~d~v~JXbU@Ⱦi~[}i|b|Z||}n~@OPg~w}M}|M}~B~S~W~Y~k~W~I~}~LSSI~}~¸]~yD~y|Żv|\}P}f}h~Fx~l}z|؍|u}n}~>~D~K~~?~g}[}P}}O}y|{t|}h~U~I~L~w}Y}Y}S~`}f}}}y}Y~}M~S~N~b~Y~}|ĉ|a}O~[~Ԉ}~}Žh}G~g~n~V~w}z}d~ÿo~`~}¾||Í}b~d~~I~u~x~x~g~m}k}}ɮ||۱|r}|¸m}m~j~<~n~N}=L}~݋}_~Q~j}{Q~}X}k}z}}؋}zI~|~h~}}G~^~<NjX]~V~a~}}ڙ}`}y|R}}Q~]~k~Ǿi~u~N~ۥ|Ǟ|p}p}_}e|{g|p}t}S}\~V~i~J~k}h}X}V}|||}K~N~q~W~M~?~E~X~y4`~{<~|s}y}ļ}||||p|S}ün~Y~q}\~`~ˁ}ʋ|g}n}T}V}R~k~|B~[}żi}|G~ALB>n~f~V~|E~X}l|o|q}H~v?~n}@FC~~}Ƚ{}}K~\}ٔ{¼{ݠ|}X}}^|i|{c|d|ނ}k~~~}~}e}Ž|||||{{{־{Ǭ{Ѻ{{{{{v||n|{϶{{{{|Ŭ|ΰ|Ĩ||||̚|{R}T}j}q}}e}P}P}N}e}m}l}|7~~>~n}}O~}X}S}W}¼_}M~h~k}p}k~i~t~q~]~ߎ}|U}K~R~_~o~LgGv}މ}U~}X}y||||||ô||ͭ|||||m}v}r~ǂ}Ć}i~S~ֆ}ބ}R~Ѓ}|[}ƨ||ʪ||c}փ}~YMv}v|Û{̫{ִ{ݦ{k}Gv~D~h~a>J~zI~ʁ}]}|ء||ا{{|Ҫ||{{y|||y|r|\}e}q~YBv~t~t~U~F~Z~|H~d~GKLY_~X~M~I~~J~~}J~Z~B~t}M~e~L~V}[|{Ƌ{~b||a|ɿ{ż{V|G~CGi~Y~LHCž_~R~J~J}B}|>}V}L}R}{J}Ն|L}r}I~}}l}Q~G~N~w~EFSTf~żf}|D~D~]~DAq~b~zA~Z}{}C~D~=~z9~=~o~GR_^O=;>p~]~C~I}|ùt|Ȅ|z|r}U~}`}T}a}_}]}J}T|Ⱦ{{o|p|z|}l|{||y||||{{{|}Z~}ׅ}Ʌ}|}r}׊}Z~}w|j||Ɔ}{N~Q~[~r~q~ƾ^~E~߆|W}~J~N~W~W~p}|||{|n}|}~|ߨ||o}}»||h|~[}\~}e}u|i|ޓ|v|É|zT}||T}I}G}Y}_}^}T}S}O}R}R}Q}҄|x||u|ù{{{h|t|w|߆|܀|v|q|E}R}׆|t|e|v|~|z|r||—|Ƙ|Ř|՝||Z}_}`}k}c}|~j|{{|||||ľ|Ľ|û|Ɠ|Z}P~ȿZ~^~d~·Y~I~X}]}ǃ}}c}ü}s}o}n}|^}}S~O~}V~g~O~L~[~Ǿp}j}m}}[~}e}R~@~?n~y}R}T~Ձ}b}W}ה|Z}}V~q}g}i}J~R~M~}X~N~{>~N~h~;@DNCT~Q~`}k}}}V~h~P~|}Ê}e}|g}o}~[}h}D~S~ŽV~Y~P~F~u}t}{}d}[}x?~I~@~=~~>~H~D~~}}K~ׁ}Ճ}Մ}σ}V~}N~S~u}||{V}k}^}q}{}}}}a}z}|ş||Ǚ|]}]}€}v}¸w}ǁ}l}̿d}E~m}K~P~N~o}e}W}p}`}c}]}i}i}|g}y}|~|p|r|ε{{»|zM}o}|||`}||Z}Z}þy}}}V~M~O~h}ʤ|ڛ|[}@~U~R~A~}d~e~[~=>d~q~?BC^~öe}{I}V}ڎ|o|l}\~a~h~U}{||ײ|}|||{|{||{{||{{{|ޫ|W~T~{}n}Z}}~r~ON}~|{|{{{y|x}h~}}[~L~T}ن|}J}`}v}`}U}ۑ|`|{{||m}i~l}˾j|V}݌|{v{zƾz{t|U}{>~?~q}h}||||^|u|P}k}R}Y}W~[~v}~G~s~~v~f~J}݅||yK|Y|Ÿe}ù_~b~|{u|֎|g}L}~R|j{{{{y|U}~J}|J}|_|Ӆ|o|͔{{{}{~{zzz{~U|c|h||p}K~_}W}]}ߡ|}U}i}s}u}Y}Ļl}a}Q}|s||w|{{|¿t}l}e}|{v|r}s}}O~~M~k}o|{{{{{{{||s}~f~}}i}|΀}V~P~P~j~v~l~n}]}Z~x~}?B?GS~D|b|Ʊ|||׹|ы}V~}x}}M~r~w~S~w}}T~G~n}ɿd}]}Y}g}߄}}W~`}c}b~a~U~P~P~}Y~T~s}}[~{}b}R~_~o}}Q~߉}ٍ}_~߂}Q~z~p~~~O~b}M~߅}{Q}Ѕ}JSIg~ݘ}~n~||ӓ{ͣ{|~hdVVT΋~i~}}||߳|ϑ}W~l~|~a~U~¶[~|;DFOv~|}]}Ń}P~_~v~}y}_~a~h~X~Z~l~ʺ^~B~P~|;BY~E~M~]~G~U}|d}n}|g|q||܅}Z~s}|||||||^~g}v|Ɣ{c}`~h~~}T}߂|I}^}P}f}z<~D~t}X~T~b}Y}b}z}a~P~I~˾[~w:E~G}d}}Q~r~y~b~Ȃ}||W~t~J~[}C~}=Jc~ƹa}µd}c}݊|م}z~u~@}~E~C~Vm[IWv~R}ٙ|f}u~Z~zG~{}e}l}\}g}U~n~[}g}j~¶\~U~V}z||l|ŵ{{{{|{{{{{{{{{{{{{{{{||s|{{{p|{؊|ƺn}{K}Z|R}Ľz}l}}U~|H~Q}ގ|X}g}ýo}|}}E~I~}}_}K}f}F~g}z8~|8~@~M~e~}}ImDK}E~W}{~~ES]_j~^}Ӆ|yN|ƽy|PVDoop\y~}g}e}Ѡ|^|{|O~^~>WQ~ȿc}C~M~Z~s~k~}~g~m}k}ļm~~}~~݂~n~z}|ǧ|||||_}K~^~׃}Z}e}ϧ||k|j|m|t|v|||֔|̌||||Y}Y}^}΂}v}||u||||[}l}}Nwm_XM}ƿo}z~I~^~y~~ׅ~s~a~u}R~p~u~}b}}}xB~P~ɽy}¸~}߉}}}\~]~[~P~~C~O~b~}m}|Ĩ||{}}q}|۬|Ο|ž|Z}N~S~V~E~m}?~Y~Ԁ}|W}^}|}e~x:w~e~M~O~Q~T~żZ~b~E~ú_~w~~}Gf~p}a~x~m~M~G~K~L~W~P~[~|~_~I~O~h~V~\~E~a}|}}\~[~H~Z~T~u}a}Žf}k}k}f}e}r}C~J~J~K~}~D~H~A~I~k}P}s}q}ſ|w||e}g}|||r|h|W}݄}b~}C}~݂}Ƃ|w|a}a~r~s~IFr~f~p~~H~|ϔ|||`|||k|r|q}P~N~X~c~d~X~|P}l}С}||{~||h}q}|{{||k}ӑ}Ȉ}úz}g}_}Y}G}S}T}t|y|l|~^|{{Ƙ{¹~|ȉ|n|o||V}X}?}<}J}}B}ɹ_|[|_||L|Ӑ{ء{{{}s|{{{{{{{{{{q|V}k}f}ޑ|x|i}S~s~CDIE~@t~ļc~`~j~w~m~C~V}H|{~L|V|_|o|Ɋ||V}Ȼd}}}o}{}o}c}~J~}Ҁ}t}}Ё}a}Ȕ||~|||||||||||ڝ|e}{}[~A?@AB^~O~A~Z}s}u}R~AH>p~W~|}a}g}c}X}|b}||W}U}u}||||ɛ|\}e}ǿu}}p}s}w}}؀}a}c}h}|>~=~C~I~w}|}ſr}|||}|Ɵ||e}ם}q}r}ûx}t}t}m}a}r}ϊ}z}y}̆}\}ɞ|޸||||߷|q}f}u}u}l}r}y}˾n}w}}}K~B~~}}}}ބ}Y}|ĉ||||d}w}V}e}}l}o}R}p|W}G~^}[}s}I~|A~ùb~GT~^}ء||g}^}k}||||a}h~~D}P~T~T~ŻW~F~E~O~v~a~yP}|U}Ѕ}؅}_}}|N~~~c~֏}|m|ެ||{{̬||{{||||||{{{{||||{|||||u}|{{tw|{{{{{{{h|_}H~N~p|£{{{i|k|l|k|γ{{{ӵ{{Ҵ{{{ۊ{l}s}ߡ{v{͗{c|t||أ||Ԑ|v{q{{s|y}f~w:=ɽ[~o}}j}Ň|Ņ|u}w}Ã}l}u|]}r}t}ڔ|ڇ{ց}W~ی}}}z}‚}ȅ}x}ƈ{a{{}`|a}~D~^}R}}I~L~n~x~K~e}U}i}r}j}ݚ|ָ{{w{||}I~M~T}|`~F?}8V~G~;~x9~F}{ݮ{{{ɡ{|k}{}y}c}r}[}{{{{{{{|||||{{{xx|{|o}ϫ|{{||||||||b}k~R~~||{{||ã|[~~V~]|{{{||l}̈}o}Y~M~}D~|}}C~U~[~f~e~y}||Ջ|n}}׆}q}l}h}\}e~W~F~h}e}V}|ׁ}J~xA~F~~E~t}T~En~_~t~w~l~c~S~ʿe}R~M~Y~Q~f~E~~źh~u}¹]}O~t~p~N~~}܅}y}|L~}[~`~]~k~p~U~}ý}i}ԟ||}W}\}ƅ||~}z}ԋ}|J~W}j}R~~@g~}<P~G~{~NUR@s~j~i}?~G~I~Ž`~k~o~}V}ƾj~YMLLf~R};u|q||I}g~gDB>DI|:ƽb~e}Ϋ|{{||||{{{ԝ|yS}ύ|ӌ|R~}}P}|X}[~M~t}J~M~i~w;[~ɺd}_}b}xH}ʿt|V}ɽn}p}}yI~z}P}o}[~R~T~I`ǽVG}>ļd~øh}ӄ|~T}Z}z}W\p~KMƽX~G~F~\}}a~f}m}|}}څ~~~}x||||{Ĺ{{~|d}}T~Έ~o~QD]t~}|ư{{{{||||{{{{{{{|y|{{Œ|m}a}ȿ|r|V}Z}h}`}ց|_}N~O~W~]~Z~R~k}ߓ|~|}f|{~s|x|x||||R}a~Z~E~]~d~g~H~H~d}z}}}o}z}J~R~c~ƾ]~J~]~f~OVFR~B~f~Z~O~u~m~e~N~Z~FºYr~J~˾g~Ÿ^~F~s}|||s|x||O}׃|{C}EXx~v~UaYHM~I}q|g~NBo~j~\~a~s~}o|ùm|n}[^RAc~ƾa~_~S~i}l}|}l}ǐ|r|~n|~p|{s|˯|ֲ||q}ك}S~w}\}y}h}|y|Ά||\~{~@~߁}|||{||ȑ}}}Ɩ}ŋ}|}ޡ||۫|Ք}҄}Ձ}j}N~]~k}¹f}f~c~c}R}S~KPN¼[½\SHLVI~x~x~|>x~|~a~L~|O}م{c}j~~<Ľ\cQi~V~}ƽm}I~v}L}P}L}J}z||ۍ|~|q||ǿ|[}c}ݔ|t|Α|W}W}˓||h}c}ׅ}a}s}U~h}L}o}U~z~t~n~ֈ}`}֏}\~ϋ}f}ێ}}}V~Q~{F~{E~~}F~z=~R}S}[}q}}z}k}i}v}}n}m}w}Ƽv}m}X}||i}}}n}||ݴ|e}`}|f}}a~[~Y~M~˒||ך|Z}΃}|O~w}|]||J~I~h}}}T}O~ʿo~O~^}g}ށ}c}|ϩ|||}|{{||||||{||ĕ|ә|Ʌ|b}V}g|޺{{n|{Ŭ{{{{{{{{{Ļ|y}k}|r|ݼ{ȝ{{Y|{ޟ{˘{ɿ{Я{{{{{{{{{{{{{{{k|||g}I~U~F~d}O}K}Ƚf}Z~s~BD@|9i~ļ\~L~}s}g}ɾh}T}K}Q}N}V}K}^}i}g}f}^}zL}{N}|`}Žp}m}t}͛||||ۨ|׫|||q|z|z|{||||Y}]}v}n}s}G~U~X~Y~L~C~{A~a}a}`}U}_}\}`}c}X}`}\}T}P}Z}|Ə|Ș|||||||||||Δ|Ў|P}q}m}n}F~G~G~B~P~y}i}ƽg}f}m}m}@~z>~l}}f}S}X}S}[}a}k}}u}}K~^~}Ո}f~S~Y~y}r}j}Z}||w||||||||||Ȫ|Ǚ||L~Y~{B~J~T~|B~}v}d}|h}b}Y}Q}Ⱦl}n}}P~O}k}X~x}œ||x}B}:C~Q}D~m~q~[~{?~w}t}n}D~]~W~\~l~}Ğ|||_}m}s}N~}5A8><9r~V~~}||||||{{|||||{|u}|||||||||||||{{tt|vv|}}||||{||||{|޲||{|{~c}z}{ÝzV|M~ƿn~ӄ|l{o{{|R}V}F~H~U}Z}B~>~=~I~ȽV~[~V~N~S~K~K~G~|?~l|u{l{zs{|ƶ|n}}i~F[^Gd~w<x@~σ|\}T~Y~P~m~݄~h~]}i}n}W~W~U~]~V}q}j~h~e}L}e|ùo|Ђ|O}c}j|̄{}J}~F~g~Ex~_~|W~}x}ȿ}}L~`~\~X}L}{}s}|}|{i{|X|U}Z}F}B}X}u}N~]~c~c~L~^}_}|||{|{}{{{{{{{{{zz{{{{{~w|v|ģ|ڻ|||||{|{{||{z|{{|}˟}}||{{{||Ӷ|w}n}s}}ѧ||||Ԯ|ԫ|||_}\}{J}||I}Q}a}Z}}P~yG~żm~w~J~O}N~ƺ_~A~h}h}L~M~^~HLf~Y~[~[~}@|~b~º]~\~]~ý`~V~U~}k}a~f~N~f~~RFZ~}}E~i}b}}o}Q}V}u}a}W}Q}e}k}W}Q}W}Ȃ}[~S~h~e~Q~@~Y~k~p~Z~?~X~EEi~A~m}V~q~m~r~z:k~M~]}y|{{d}n}Ľh}X~f~{K~`}|~|m|g||p||ڑ}w~Ng{9l`WHm~S~m}|Ʀ{{{{{{{|||||u|}|S}h}]}Źn}_}x|]}y~ML¶PUFh~~>~K~X[_~W}ŷg|o}z~c~ża~J~d}؋|r|Z}X~\~O~V~V~e}c|{|}\~}r}Ή}Ӕ}q}|r||}}a~f~Ն~R~b|y|||߷|ܬ|{{{||}Q~c~V~Y~[~P~|d|Ե{{u{t{ܐ|~lX~i~u}||{{{{{{{{{{|||y|{{{y|{{ÿ{{{{{{y|||x||||ʗ||ؐ|_}c~<58v0J~A~}G~[}U~T~w}^}\}K~Z~Z}l}U~K~JeEi~Qv~̿k}w~Hi~m~I~CC_~KBjt~Żk}O}ˈ}g}|Ĕ|Ϝ|||r|{{{||||m|||ˌ}k}|IoQQż_~j}J}|o~@>N}}|ֳ||j|ō|X}e}h~GGHPKۅ~r~}s}x}}Q~}}J~S~V~U~|}[}Q~l~S}h|_}{}P~b~~ߌ}Ѡ|m|{«{||}}y}Ȉ~[~}{}p}|~K~n~U~Žz}[}y|t|_}|R~[~l}o|n||o}P~W~[~Q~N~Z~ɽc~I~K}F}V~X~T~b~|~~Ƚo~{}||}@~[~E~K~o~|8KLb~K~ùY~L~H~_}{<~J~}8~T}E}h|m|T}J~R~r}S}~N}|{S}||]}k}p}j}_}|}R~u}S}[}g}`}g}޹|ġ|`}p}k}^}Փ|n}^~t~~zAg~GU|~O~O~Q~}U~w~KEx~W~y~N~w~X~\~b~u~QT~~ʃ~T~Ľ}}r}ȿ}j}[}r}d}w}Ǽf~~e~}P}Y}U~\~|L~ԉ}]}Y}R}ʀ|{{{||Ͳ||||{{{{{{{{{{{{|||{{{{{|{{{{{{{{{{{ǽ{{{ȿ{{{{{{{{{{{{{{{{{{ֽ{f|l|͍|V}h}|ޓ|r}Y~V~r}s}Ǿy}X~~BOOVJW~ǽm}\}\}T}´_}C~a~8~9T~l}S}f}d}]}ǹc}W}P}v|t|f||ٝ|||_}}}}̅}½z}j}Z}՜||||||V}]}T}`}i}v}G~J~Ⱦ[~i~_~^~^~ڀ}r}b}||S}U}Z}_}j}g}g}ùd}^}d}T}~N}_}^}Ǿg}p}c}_}[}Î|Y}p}r}{}{C~r}a}x}}}A~N~G~E~B~~@~K~~}n}n}e}Ց|W}r}e}a}e}`}`}i}o}|?~F~T~N~z}l}|y|||||žk}I~W~a~d~a}a}e}|p}̽||||||Ͻ||Ǧ|a}h}t}ùX~EURR~x||Ľn}~}c~i~Ž`~H~G~}d~~~y}xD~L~`}Œ|~||¸b}j}K~V~S~P~ȾZ~H~F~@~n}@~:~G~[~;~9p~E}>d~g~p~M~ɾl}P}|˖||٥|˿z}s}|||҅}~@U~||||{||{{{{{{ww|{{{|{{{~~|{||||Ҳ|Ҭ|h}|{~||{{{||||{{|ž{z{{{}{{{{{{{{u||w|{|ʿ||j|y|݌|^}ȼe}}|{{{V|ё{¹{zǰzzzzz{{||`}~L~|h|t|{{{{™|g}T}|g}X}U~Fz}|}K}b}j}٠|f|Ӥ{X|m|N}D~P~s~~@~s|{Ψ{||p}c~y~B@}>q~>IH~r||\}c}`}V}͆|u|l|{y||Ԧ|k}߳|ܮ|h|ݍ|o}b}M}||v|Y|Z|]|ٛ{͗{{{~[|z}L~Z~t~s~[~k}||Ω|}h~e~¸c~Z~}}Ί}m}||||ut|{{{{{{{{{{{{~|||ܲ||}a}ޢ|g}g}}c~~|E~l~Q~Q~R~W~S~Ѐ}`}ȥ|ڲ|}g}~F}L}؃}{H~T~d~k~^~n}}}T~V~b~a~Z}Ќ|o}}K~y}v}h}|}}|E~X~ހ}X}¼||p}ĸo}|}y~FHv~`}l|ǿ|Ҙ|Ӊ|a}m}a}|͚|٢|f}r}^}|G}X}[}d}X}ی}W~x}Ǿn}S}ɽf}}Y}R}}}[}a|{|Ѐ}^}|_}P~c~Z~H~y}b}k}o~q~}k}d}z}}~I~M~\~}H~i|{ɹ{Ƴ{ȭ{Ĥ{¦{{{׼{{{{{{{{{{{{{|||{{|ν|q}z}}}e}N~XZr~r~k~s}N}J~d~<~|A}N|x|V~@Do~^~Z~}||{{{{{||}_~R~b}Z}Ƃ}ږ|n}q}||||}x}|{~|e}X}b|{{|}ˑ}|s|z|{{~||Ź|||{|җ}żw~R~֏|}b||ʦ||{V}T~o~s~c~}}|||{{{{{ť|Ǧ|{{{{~~||||||{{{{{|||{{||u|Ψ|l}Y~~L݅~j~}}L}g}H~~}BP~h}f~Júc~|}LZK~p}l}T~}<y5N~ȼT~GV~CO~U}||j~jXe~o}P}i~V\S~M}P}r~PH?g~Z~Z~K~e}Ǿ|y|{{̽{{||ƹ|||||k}f~K~t}»i}n}a~Hi~a}¼|~||v}Ҕ}W~}h}ސ}Y~{}}ِ}ă}u}}u}|{|c}{X}s|n|y||S}܅||}ڀ~Ho~V~Z~|H~c}N~~;@ûa~g}ێ|||d}d}Y}||b}yJ~X}}TXKy~ێ}y|{}|||{|||`}l~@DJG;Z~A~I~~<NXSH~=p~ɽf~|D~Ⱦc~AAĹ\~V}e|f|I~KOJ}Cp~J~G}y>~k~k~a~Z~V~}c}k}\~þh~y}a}Y}ƍ|ȿ|»|ϩ|ͣ|؞|ԕ|ɇ|k}˿{}X}ۖ|ۏ|üs}h}p}ϓ}ܡ}}V~N~̅}u}g}|||||ܓ|a}}t}[}w}]~`~Z~k~m~N~V~n~y~n~T~M~R~˅}[}O}^}j}žz}k}ƽ|[|ƃ|n}}}΅}}U}ӓ|^}e}\}d}h}t}p}Z}U}||i|||||{{{{{{{{{||{{{ͻ{{{û{{{{{{{{{{{{}{|{{{{{{ǵ{ö{{{{{{{{ο{{{||||x|||l|{||||||||Z}q}{}O~Z~s~IXfq^G]~=~·[}Z}T}f}|ٜ||þ||ʢ||||||||||||ګ||||Ƥ|||Ɂ}ߒ}u}||Ϟ||ϣ||~V}a}f}¹m}Ļl}z}K~F~r}~}p}Y}|_}[}|X}[}d}s}Y}X}i}[}Z}v}g}`}p}`}O}d}t}o}t}s}B~C~m}C~^~b~T~X~}}]}o}ނ}t}x}s}t}}}f}k}f}a}h}e}w}`}ƌ|Ѡ||||}||V}p}R~K~L~?T~׆||}V~S~p~m~j~`~}}e}y|{|ެ|l}n}}W~\~}i}u}P~Z~F~U~y<l~R~_}[}Z}M~Y~\~R~^~g~S~b}}}ƽr}f}ڛ|}P}U}F~N~øW~ǼX~N~\~U~O~ɼR~\~z7C\~A~v}y=~x}D~y}y:~@~]~N~ƺR~^~m~l~P~L~T~ʽY~|<>c~U~ό}t}|||}}v}|||||||||||{zz||{{ww|{{}|{{{||ȹ||||||{||{~||||{{{{{{{|{{yx{{{z{{zzzzzz{{zzzzzzzzz{{{||||{|}{{}{{ý{j|z|m|{|{{ư{{߽{~i|||{{{{{s||~\}]}܇|I}V}>~^~x~FHV~S~l~T~w|g|Ј|z|}||ɿt}}؀}s}i}|q|ƈ|s|Z}f~JN~W}}Z~b~g~t~OLI~D}X}z<~H~W~D~s|O}g~KGJ~~~Y~|9~l}P~S~e~Q~i|w|̥|ΰ||||·|ƻ|||||ޮ|ߟ|ׁ}{B~O}|úz|h}}]}ٱ|—}c~|y|u||||||{{{||{{{{͠|u}l}׍}U~]~]~|}T}|W}r}}ʒ}}T~}y}ۅ}d~m~~~CGGt~a~K~U~l~m~W~ľr}|z|{г{ͱ{£{{¢{߽{Ю{{{{Ľ||z|y|f}d~{~EKúYclz:jQJICd~s}_}S~V~L~C~Q~U~~}y}z}ȼc}Y}N}z|c|{ի{{ѯ{{Ͽ{˾{{{{z{{{{ַ{ʖ|R~d~x~ILGy~Ⱦc~h~m~ʿd~`~Y~^~ºk~f~R~H~I~U~S~zA~S}J|j{n{{{ٽ{{{||||}|¶~|Ϳ|g|{c|q|q|{{||||||p|ɱ{ɾ{{||||ɛ|o}i}}d~}|ƻh|C~Ja_WUm~x}ܠ|||||||ֶ|||Š}~Q~o~}|{{{λ{|e}s}Ą}N~l~J~É||}}ɪ||{ا{u}Ƃ~l~u}||||||||y||||k}_~`~΀}d}Y}|x}ֻ|||ѫ|{~|||||||||||||ڳ|u}T}o}b~}ُ|i}}|||||}|v|s}j~i~j~\~p}i}p~n~y}Z}j~}~H~K~G\Rɿ`~M}x}Ii~}w}v~lbĿ\My~Nz~c}v|h||[}N~DGLe~ºd~DEIB|=M~a}K}K~ǿ_I;K<\~r}{O}̙|{|ӿ{{{ܮ{O}üg~~N~t|Մ|\~n~g~HTMO~|Y|ߴ{y|||n|y|i|x|d}YPCN߄}ܧ|||ŵ{{{|þ|||һ{{{ӿ{ڷ{˜|s}}r}~@~e~m~W~}c}}|>~g}d~w~~~`~c~Eȿcv~ŀ}d}_}m}n}b}߫||w|}l|~i|t||||y|x|ȗ|y}n~\~Q~S~}}}N~l~o~j~q~f~o~üi~}H~k}p}i}S}E~s~n~}}ۉ}k}ч|ّ|üz}}N~}p}|z|ڟ|j}}N~O~j~O~_}a}Y}Y}`}h}p}g}|Λ|Ŗ||m|_|t|}R}R}z|x|X}b}Y}ƌ|ۚ|`}`}|||}|{{{{|||{{{{|||{{{}|۹|}ȇ}p}|̓|w|`|o|y|}|z|w|Ȑ|ڠ||j}^~~c~À}\}͉|]}T~]~F~t}}ۇ}j}[}|Ӣ|||{|~||{xw|xx|~}|{{{{{{|{||}~|||{{{{{{{{{|{|{{|||{{||||{{{{|||||߼|j}o}||||{{||||{~|||{{m|n|||ȓ|Ƒ|̍|c}}X~`~Q~y}k}g}\}]}V}g}c}|||||Ұ||Y}[}X}Y}a}f}|z|||ϑ||T}d}ȿg}k}g}p}v}a}ܙ|||||||ћ||٣|͝||֢|̕|U}a}^}f}o}}<~B~|:~C~P~H~C~K~\~J~A~Q~D~d}d}w}}@~{}q}i}m}b}٧|Ѡ||d}p}B~K~F~G~G~;~}9~:~C~Q~X~M~}~}|}j}]}~}I~w}a}\}|||||͓||[}R~?HKXs~W~n}m}że~D}Z}O~^~|ޤ|p}r}`}u}}ߕ}|||c|Z}u}Y}ʿX~JYLb}w||j}Q~_~a~K~f~X~އ}و}L~}]}||}||`}d}x>~R~żV~ƻT~źT~H~|:~N~ǻV~b}x|n|c|\|n}o~@?GZ;:`~y=~g}I~d}_}y=~|=~G~^~s~u~BTDO~s}z}N~T~k}|e}}r}۲|̕}u}v|{||||{{{z{|||{{{|||||{{|vv|{yy|{{||{y{{{z{||tv|z|||||{{{{{{||y{|{z{{{{zz{{zzvu{{{{{{{{s{g|o}y}|d}{Q~f~W~R~}Nj|{|~|ѽ{{{{{{{{{{{{{{{{{{{{{{{~{{||}}[~T~n}d}х}W~e~Z~U~]~l~~N|~K~ӆ|~X}|}x}w}F~Fy~Z}|]}g}{F~O~|}`}j}X~y~~{~~{~}o~~~d~T~}ǣ|{ú{{{{{w{c|q}b~>B?=;v3̻P~E~J~I~c~IGy~m~Y~H~v}O}a|{{{e|i|j|g|m|v|v|֋|v|i|r|U}Q~`~M~Q}b|{{{ڵ{~h|w|||]}w}G~I~}^}N~l~b~V~Z~[~…}|{ս{{{{̶{ж{ƪ{{{{h|f|X}W~~o~f~u~@HOTY¼\TKFAt~{H~ђ||||j|m|Ҁ}}|ό|І}}U}m|ֺ{{{{{{{{l|y|ą|o|ܝ{{~T}}]~]~a~^~|}O}||dž||[}\}}[}|}\}_}{T}׍|y|s|u||G}X}}V~}q}¼y}^}b}zF~Q~ſj~w~}~s~X~~@~S~BFg~F~D~}}}}T~K~N~Z~R~р}g}|w|۷{ҽ{{||{~~||k}P~}Y}|j|{s|y|}||y||m|{~y|||{|||||{|||~|{{{{{{{{{|||||{{|zz|{{||Ե|n}}f}Ʀ|||||{||n}z}}R}Ȉ|h}}ƙ|{q|{|{|||||||z}d}|q|{{|||_}}`~k~F~i}s}`}e}ߗ}z}e}}o}y|||}|}l~ºh]Ps~úl}ˌ|¿|q}žo~|~~܌~P~{}h~MLe~A~|B}O}u~ZWUDA>h~E~P}a}`~_~z}K}L}˹N~T~dzT}b|j|e}~bZQ}@R~y}ûv}׋}Z~~TTMMBH~}}H~ǻk}~|b|d}`~C~q|Q}ľu~pw_܀~d~_~ʄ}u|{h|l|q|s|r|w||E}[}NGz|H|J~ч|s|͊|c}f}׍|~||ì||||{{u|~|{ū{¨{k|~|З||~W}Ҍ|h}e~^~f~~Bo}HkijeKo~G~K~_~\~ւ}|yC}D~O~i}ۅ|g}Y~s~q~\~ۇ}|}}t}Ç}}f}\}f}Ɗ}|O~Ļj}h}úf~{De~O~`~P~b}c}T~}E~v}u~KC?b~I~م}X~^~}b}j|ݼ{{y|||ޜ||O~e~`~g~d~h~e~V~I~}E~{B~F~M~M~Ղ}b}|||s|x|z|~|K}R}]}e}d}ϖ|v|{{{{x||||||||{{{{{{{{{|||yz|{{{{{{{{{{||y|}|{{ۚ|܏}{R~}}|s||{{{|||~|{{{ww||{zz||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{xw|{{{{{|Ԣ|k}}S~X}K}W~o~Ս}u}\~k~}~LOI~m~Y~ͅ}NJ|p|e}Ȉ}~}||||||||||ÿ|Ȗ|[}w}G~N~M~M~N~}z}}J~I~w}m}q}?~J~~A~|A~F~}A~o}m}p}E~K~r}{=~S~@~z}H~V~U~N~O~_~]~[~g~{7o~_~S~y}b}e}r}Y}Z}z}e}ܗ||S}n}V~S~ƿg}~K}_}s}g}|:~F~N~V~E~7~B~S~K~µJ~Y~}3?EGCV~۠|||j}úk}[}}T~I~d}͟|p}m}Z}^}}}|B~M~t~_~O~FD}m}Տ}}_~]~k}f}m}[}|y||||||t}}˦}|IJ||w|{s|||||||||||||||||{{q||||}K~e~DJSX[bX·SŹP8o~F~K~i~E~I~D~z<~}A~t}zK}k|q|||i|w|{Z}h}}g~LZb~I~_~r~\~||||||||||||yz||||||{||xy|{|||`}մ||{||{{||{{||{{{{{|||||qs|{{{{||||{~s|߫|~}c}{´{{{˾{{{{{{{{{{{{{{{{{{{{|||||ޣ|}}ƃ}p|Զ{·{{{z~}{{{{{{{zzzzzȊ{r}}J~`}|||||{xy||xy|{{{{|ӿ||||||ȭ|||||{{|||{{{{v|Α||d}|J~Q~ӄ}g}|c|v||||||||||~||||y}|{|{{{{|Z}n}Z}uA}ͻm|~|e}}׊}}X}͑|q}H~O~W~W~J~[~K[ľZ¼WSbr`H{7\~W~T~Z~o~i~o}l|{|Z~RT_jeWKE}~p~f~~}|{|||||||w||ˎ}~Mj~V~d~¶Z~_~y=q~O~{=~X~v~LKm~Ƚn}{|y|||||||||||{||{{{{z|{{{`}W~J~b}|`|\|V}i}T}Ѕ||[}}G~w}j}h}}~}U}f|^|i|c|޵{޻{l|Ң|o}y}ùz}S}_|Õ{{{á{f|w||||{{{~p||ȕ|ǽ|l||[~~t~]~{}ْ|q|||}†}|~I}Z~EEB=7c~ɹN~@~;~e}|t|p|{{{{{{˾||||||||||||ث|r}j}Ĺ|y||||s|{{|||||||||||{{{{{|||t|{{{|}^~}||r}ן}Y~c~}d}]}||ɛ|||ե||ӧ|f}i}[}v}i~c~R~҅}Ă}~}m}È}k~V~^}|v|r|ȿ|ޛ|͋||\}ĶY~Hµ\~\}l}~lXe~و}~}֋}W~}t}n}͂}y}O~o~}٠|ŗ|ݚ}؇~}Gc~}}G~X~H~x}}}X}X~Pq~e}r}Q~H~U}s|R}o~A_~|9TTJH@q~F}~p}È|ԭ|ڻ||}}ܚ}t}Ā}ޙ}c~~߀~n~{~Q~y:~Y~~l~{~AS~c~W@Iu~l}Q}c|`|h|a|||V}zR}zN}u}g~q~{}g}b~@LYPǿ`~v}_}{P}\}ƽk~OpKJlþj~t}ڧ|||t|{{{x|||||||ʟ|u}}Ȉ}U}P}Q}M}d}S~d~e~N~}J}~|ё|û||||}Y~`~R~w}]}_}}}S~V~{}t}[~x~j~Z~j~}~p~}k}g}b}}ˎ}k}|||n}||Q}֊|||c}֍||}{CdcWL~s~d~S~ق}\}U}Y}k}x}Z}LJ|S}s}v}؁}·s}xJ}|Y}r}b}\}^}n}]~v~g~H~]}I}zC}J}|N}|w|e|g|}||g}Η}\~}y}y}Һ|||{ļ{{{{{{{{ý{z|f}e}ʻ|~k|ѽ{{{{{{||{{{{{{{{{{{{||{{{{{}|||{{{~}|||||||{{{{{{{{{ָ{o||˜|U}o}y}i}ƽg}žh}d}b}b}d}]}|||~|||}||~|||||||||̠||П|ԡ|Ң||w|||z|}||||||||x|w|||||||||{{{z||||||}|||{{m|Ő|_}~}a~v~ʎ}p|e|||[}Ҋ}Ύ}j}ܬ|ڮ|||]}f}y}|}}s}ɛ||||||Ơ|||||k}L~P~g}|||||ܹ|j}|||||π}ߋ}m}|͗|||}D~E~^~;A@V~}B~i}Ļd}G~K~E~D~X~`~T~N~ۊ}̄}{}]}|||||||ý|¾|Ţ|Я|~g}}c~s~e~]~V~}Ќ}y}S~w~@Du~S~Y~o~h~`~}U}h}r}f}ڝ|z}q~y~s~½f~Y~X~d~Z~|}n}t}}g}W}|י|ԓ||Y}c}x}F~Q~S~L~}E~~}^}c}a}ܟ||~||R}}M~[~ƽc~U~G~}ۀ}~}v}}K~V~{~u~b~x~}?{~^~{}R}Z}m}u}Ƚl}ۇ}a}|ˉ|`}L~Ǿf~g~Y~}}||_||||е||q}p}z}}O~^~V~^}k|ύ|X}W}݂}J~M~J~~G~X~R~|{ƽ|Dž}n}t}}|||||||||{{z{|{{|ĥ||{{{|o}]~}n}u}~}O~}Z}ߦ|Ǜ|{{{zzzz~~{{{{{{{{{{{z{|{{{|||{{{{{z{{{{{{{{{~{{{{{{{{zzz{{|{{{{{uw|||||}~||||{||||xx|{sr|||||||{{{{||||||}\}ڥ|{{¼{ѻ{е{{{}j|r|l|ְ{ӥ{y|ʘ|||||||||||{z{}~W}FO~~K~W~Z~Ɛ}}`}||||||{{{||Dz|z}b~q~o~q~r~Ƚi~P~S}\|ĺ|_}Ľu}}M~W~»f~p~t~^~w}|||z|y|~|~|||~||||m}}w}|r}m}ۢ|˜|ƿ||ú{}}|}|©|||||{{{{{||||||߬|]}Y}k}p}e}g}m}̿i}X}P}yF}i|^|ܰ{{r{y{{{zw{{Ř{‡|i}j}Y}̆|c|~^|y|z|a|ը{{{k||З|ג||||w|c|Y||t}P~L~F~w}T}Ԇ|d}א}l}m|ޭ{o|||||||||}̉}x}d}͘|p|{||m}zI~I~F~G~N~[~V~s}T}z}z~~X~}W}||{{{{|||||{{||||̦|||||||u|||||||||||||r|v|s|o|ӌ|}]~}u}|Ȫ||h||^~{~FN~N~|T}|ۯ|ܸ|ũ||۵||p|~||k}{}{}w}j}݂}O~m}\}Z~SH~F~d}݉}h}y|ږ|х}T~N~c}}|ż|}T}t}Żr}a}Q~kIwKX~Q~]~}{}m}o}r}|||v|{|f}v~^FR~J~}n}S}z|W|w|N~f~S~L~S~ɻO~M~H~~<~P}j|a|w||Ŝ|ŝ||a}l}}_~HDt~i~W~u}V}Y}A~ż`~i~P~H}[}Af\Gp~_~ߓ}r}b}||}}}d~d~U~S~P~}ĺl}|}Ȼx}l}z}ȁ}r}Y~]}D}>~Ži~ǿt~̄}r}u}|Ŀ||f}t}v}n}¨|q|s|͏|l}_~u~s~Z~k}Z}d}ʛ|||\}ƀ}׈}{}ºz}w}[}||||נ|Z}||N}||||||||||||\}P~g~Y~x}||Â}ݕ}L}|N~`~T~U~X~͌}p}h}׬|||q|{||||||{||||||c}}\}g}e~|~r~\~W~o~M_o~Bap~}K~U~~}Z}|~R}r}]~Q~_}\}ܓ}}t}W}[}g}f~N~l~k~˾e~T~u?~k}ɺd}\}N}||}S}f}ǽ}}źu}]}|ż||||||||||||{|{ҟ{f|g}o}Ο|ș|ؙ|DŽ|w||||j|{y|Ü||^}_}^}|u|{{{{{{{{{{{{{{}||||||g}ˏ}}ђ}}}}~}ٿ|˵||||d|p|n}_~=EINPOGFHGB<i~e~f~:@EGIGDFNSNDu~o~q~i~Z~P~|D~m}k}v}~}{B~I~Q~K~ƿn}e}Ľy}w}|||||||||||||||||||||||||{{{{{{{{{{{{{{{|{{|}||}}|~|||ȳ||ŵ||||||b}z}}}}W~s}ղ||||||||׭|f}J~]~U~H~d~E?j~y~m~}S}ؕ||V}g}}a~ӊ}Q}n}}و}s}j}x}w}|||j}׹||||||||||||{|q}y}_~TSF}?n~w~N~c}q}ź\~Ag~q}S}X}|{Q}n}c~KXWPY~˄|||þ|y||K}E~~@NOPHe~x~Lj~z>~|}}H~l}\}}}[~h~Ã}||{ع{{Ω{t||}W~S~N~Q~Z~Że~t~{Ai~}W~U~W~g~b~\~b~m~~F~}x}żt}}}}Ʒn}`}Ǽ|p|o|||p|||u|o|k|{g{{Q}}F~W~f~{;r~d~<ƼY]E~||_}}܂}h}a}U}g}Ľ}l}z|{{{{{|ʻ|i}s~x~a}{{|||||||½}֋}ڢ|||p}x}|{|Υ|n}|{~||}{|{{{z~}{zzzzzzzzwu{~|{{zz{{{|||||f}}|ԗ|s}b}|\}|{{{{{{{xz|{||{{{|||}|{{{{{||||||||}|||||||||{||||||||}|{{{{||||||||}|{{½{{Ш|}}]~i~h~d~d~b~a~X~X~^~U~e}||||w||}}|||z}}||z|||}f~}|||||||{{{|||t}Ֆ}}}Ľ~}ތ|X}I~v>~_}\}m}}R~]~l~t~t~że~N~g}g}v}t}؏}V~V~}S}ܞ|}[~a~d~g~Y~Տ}n}k}p}ɿ}׎}j}ў||||y||||{ſ{{{~~|||{{{{{{{}||{{{{|e}c}j|g|o|ɂ|P}S}^}a}R}ڑ||a}S~~}Et~l~Y~E~o}ƺ`}]}T}M}|K}V}o}܁}x@~{?~I~G~f}=~d~;<~9;?n~L~}݊}r}|{|{Y|֚{}\|x||z||W}e}|I~m~v~y<y~x~·j~U~}k}T}֐|ӎ|χ|҉|~S}V}~N}P}R}S}}c~S~[}ĺg}}z}|r|{{{||||}|||{{{{zz|||||||ڟ|ļx}}o}U}S}O~|BF|~x~~~E~DJJK~^~}}ـ}N~x~~n~W~F~o}W~|=s~b~N~z}c}}j~GPRPn~Y}|}}l}|o|Ç|p}‹}b}t|Ǘ|¾}]~~_Yd~||w}~W~c~g~_~a~e~J~D~c~FI~f~N~y}k}e}ˤ|n|l|[}{}}E~R~V~T~S~m}u|h||ȿ||I~q~g~e~[~y}V}Y}Z}σ|n|m|\}f~JGc~z}yP}y|Ɏ|]}p}r}|ב|¿y}u~Mk~k}S}x}?cid@CshbT<y6?s~ø^~K~x}s}׃}}Ճ}}}‰}~`}|||h}Y~V~ľ{}}{~R~e~ɇ}e~Ƿ~z]~}}|||||{{{||{{{~~|||y||||r}}}l}ӯ||||~|v|{|||{||||||||{{{||{{{||v|~|l}·o~SŻcĽc\PGGTR~E~~z~k~d~}Z}Ã|̓|w}O~}y}Z~BE::p~S~q}}\~w~B{>v~p~L~|}e~~o~ļe~q~KV~S~~}}EľlZ~̈́~i~n}[}ی}}{G~L~s}T}|{H}W}Ƚh}G~`~`~M~}}}}z}J~e~x~u~r~~|E~d~˿y}f}ƹu}|}zD~@~I~̿]~ALH{>i~Y~B~s}D~A~¿f}U}^}Ƽo}ʿr}^}M}X}т}ڎ}~}g}^}X}Ԑ|w|։|U}Ջ}h~h~}ʗ|{z||||¾|||||u}ԣ|{~p||||ſ||~]}|Π|ݨ|e}z}}}}E~N~K~E~G~I~M~P~O~L~N~P~H~A~E~S~ɿ]~Ⱦ[~S~ƻV~X~_~e~\~K~C~}D~I~P~X~U~J~J~Q~}}|ƻ||||||||||||||||||||}|}|}|}|}|||{|||||||}|z|w|x|v|s|s|s|{{{{{{{{{{{{{||||s}P~½q~t~v~L~}X}]}n}Δ}ޝ}}_}h}I~V~j~{;HGi~}}X}z}[~d~]~Y~f~~~E@r~p~e~M~N~f~v~þs~a~W~b~c~}z||^}v}_}||Ф||Z}y}Q~|G~m}۠|||l}g}d}~G~R~J~r}O}|U}s}w<~i}o}t}W}w|q|||܁|z?}G}x3~M~z19:ʾ]~h}Q}m}F~K~k~Dh~q}|N}f}|}{}`}|s|||||b}h}~I~^~U~}s}Q~r~y~m~\~V~_~W~T~}i}z}}M~֍}\}ü|y|x|||||٪|f}|՟|ٚ||S}L}G}e|Q|X|b|ڈ|_}c}X}K}D}}D}|Q}}V~a~S~ƽb}E}|r|}|u}Ŀj~Bj~e~k~}j}DŽ}}Q~}e}ý|׫|ݝ|h}`}o|Ŵ{{{ö{{Ͽ{|{{{|ץ|h}}ٖ}||dž|{|n||||||{{{{{{{{{zzzzzzzz{{{{{z{{{|}ן|||{S}p}}}t}΂}u}ב|z||{{|~|{{{{||||||{{{{{{{|||||ּ||||st|vw||||||||||||ռ||i}r}ύ}b~x~d~˂}|]}Y}z||}þh~JO~f~X~c~u~}BF~g~Q~}]}l|{{{{{{|||{{{{|ʛ|Ӝ|Ι|П||{{{{{{{{{{{{{{{z|׾|۽|v|{{{{{{x||x}||||||~~|{{}|ݤ|t}}}t}a}ʗ||}r~IG|~x~w~l~̍}|b}Љ}Ԅ}{}܅}}ߗ}^}||||{{Ž|}|{{{{{{{{{{{||{{{{{{{|p}~}`}Ε|ϖ|ؔ|üp}V~i~Z~}]}}c~\~Q~}^}}|X|{t{{˗{Ί{z|Q~{;9^~ŵQ~P~S~O~I~|C~|}m}e}a}~]}c}i}b}||S}u|[||D~CEg~]~V~M~K~G~|}f}}G}^|ɒ{{{{||ַ|y}~Y~f~V~ܓ}֒}Ƽ~}zS}i}}u}|ױ|Ϋ|v}b~b~}||~|||||{{{|||||||||ƿ|S}h}}}w}r}O~y~}~b~]~T~O~X~M~Žs}d}n}w}r}p}|þ|z|{{m|T}e~^\w~}`}||j}|ҵ|||è||p|{{||||||||{{||||||w}}}d}[}ƿ|||ܘ||r|m|r|x|n|`|b|t|T}}e~}FE9MjYg~<~L~s~Au~o~~w~b~d~]~}}ώ}]}¼|~|ك}c~W~҆}a}ۢ|ƾ|X}֏}Ɋ}y}h}˦||{{|||`}~}w}\}~||a~l>:b~µN~;~<~G~A~ù[}h}P~e~|~v~|}l||w}j~NYI>d~Z~k~Ȼ^~r}n}~}}b}Y}Ɯ|x|y|~|y|z|ݫ|j}d}i}l}w}z}|n|{||||}}||}|t|~r|s||||||z|ƻv|Ӆ|Ӈ|c|Y|i|}||G}N}S}P}Q}P}wA}F}o}J~·[~a~B~P}ȿ`}s}P~T~G~y}F~R~h~x~\~H~Y~O~p}|g}v}Ы|||}|}||||s|͐|e}p}t}ޒ}a~b~^~Y~Y~O~l}Z}S}|ٜ|ɡ|̥|~||ü}i~t~\~]}||k|{{{|||||l}|Ũ||||{||||||w||||י||Ҁ|P}|v|{||||{{{{{{|||{{{{~s||l}}}D~J~Q~X~Z~O~p}Η||||||{պ{||s}||}||||||||̢||}V}T~~~~q}u|v|||||||||||||||{|y|||ǖ||˒||~V}U}Z}_}k}{}}}}w}Ϳd}^}]}a}^}ߙ|}|||y|z|||||Ѡ|]}c}]}~V}`}^}V}^}k}d}||d}Ҧ|{|{{{{{~~|||||||||۶|ݮ||ƻt}c~m~[~̀}_}|||||չ|||||{s|}m|{v|ݭ||Y}h}g}Z}|c}w}l}^}|Å|a|Þ{{{{{{{{{{{{{{{{{{{Ƕ{{m|k|{}g|y|ա|a}[}Ʉ|\|{u{z{m{r{q{p{n{j{o{y{ż{ݘ{\|ɾu|yE}F}E}E}~A}||zE}|H}|u|c|]|\|b|i|i|a|yF}y}I~j}[}ùj}_}{M}|~N}N}|G}}|x||]}»n}v}k}Y}آ|||||w|{{|{|y||ͤ|޹|¦||{{{{|||||||||||||||||̞}_~y}¼|¾|ӝ|Ԡ|Û|ҧ||ղ|||{{{{{{{{{{|zX}b}p}ӂ}ن}x}g}l}}}ҋ}ݐ}Ո}s}z}L~}v}X}L}N}M}Έ||y|h|o|||}|e}`~}~{~y=C@n~Y~|}[}ʛ||k}ϕ}Ð}}a}|{|f||b}r~XJi~B~Z}w}T~Y~H~e}q}KCNLMRNB`Cĺc~g~r~Y~x}|p|u|z|{{{ľ{{{{{{{||ˤ|ʟ||n|g|t|~|Ղ|~B~]GLF~;W>}=HH@v~n~j~o~?GG@e~J~üm}i}q}v}y}t}q}v}z}w}d}c}[~KHƾe~T~_~~~HVTq~_~v~~k~k}ɤ||||||||s||j}b~~~~a~Q~ü{}|q|u|X}E~V~Q~~}k}|||p}yW}x|{ܥ{ٯ{߹{{||k}d}||\}j}~}}u}|e}h}l}q}݆}I~K~V~j~p~R~G~_~p~l~]~Ӑ}f}|w||}a~K~|}}ߟ}x}ɪ|||||v}i}||ڔ||{Z}c}|ř|Ϥ||i}p}p}x}}{}w}}}w}|~l}x}p}e}e}k}l}j}}]}ԣ||w|{|ض||i}s}}d~n~u~~~y~g~]~e~g~}}d}|x|t|s|s|ғ|[}l}˅}N~S~N~O~}x}}܈}ۍ}z}{T}v|a|}`|z`|d|k|v|ѐ|p}Z~i~Y~}K~N~R~e~~NO~h~_~e~m~|:~;{8f~g~z59<?BEEB<}7}67::<:7~5579@@><s~c~O~|A~y}r}]}ӌ|z|||||||||||||||||||||||||||||||||||||ߜ|Z}h}}S~ź]~e~f~Ǿ[~U~O~F~x}o}l}g}c}]}||||||||||||||||||||||||õ|Ͱ||c}p}ȿt}r}{>~U~|:BDAABB>n~e~f~k~k~\~Q~Ż]~p~q~j~h~f~g~d~`~R~s}h}D~c~CMPM~d~Q~}j}ܕ|l|{ݻ{{}||q|p||}}g}||||u||g}Z~ڀ~JIy~úb~V~M~R~Z~n~DIBFKJEENJZ~|j|ȉ|W}k}H~g~Dû_rZp~i}S}}}U~~~~Fu~V~V~g~{AMQEZ~v}}d~Q~g}Y}X}w}}ޟ}ľ}x}p}||||ʴ|q}|}|||ư|ػ|||ܴ|ɜ|Ċ|Y}x}O~O~F~K~x}y@~F~Z}yI}X}c}e}h}d}a}V}w||E}d}y}t}|p||ȟ|Ƞ|հ||q}y}ǃ}}V~a~{~__ſm~׃}ׁ}M~T~}~l~g}|}~}ۡ|m|{{ü{j|՘|ѕ|ȿ||g}}R~Z~_~ڈ}|||p||Н||c}T~~IX~c}h}W}|ƒ|ߣ|e}ܔ}zP~}ي}v}[}ݏ|~M}r}}q}}a~T~}}j}|ˋ|||||^}||۝||{z{{|~U}d}}P~}P~܏}…}}r}ʦ|{z{zz{{{А|N~EQV`aSI|~i~}|m|Đ|o}ƽ}v}||{ײ{p||i}I~T~}_}ܭ|Ҵ|||||||||||||||||h}g}n}|}}}F~P~U~N~J~H~E~C~>~?~@~C~C~E~H~G~I~P~V~W~Y~û[~U~P~T~Y~W~S~O~J~F~P~W~[~b~øY~L~G~G~J~N~L~L~K~K~I~J~O~W~T~Q~}P~˔}y}ɭ||{{~~||||||{{{{{{{{{{{{{{{{{{{{}i|v|}|u|Ȅ|i}}G~zA~|A~K~U~M~=~n}m}u}t}n}`}\}P}~K}܌||||||}o|{{{{{|||||٠|þ}^~w~}~q~]~H~z;~F~a~~<=<7_~N~D~{7~;~Ai~]}G}S}i}x}p}d}]}ɼ[}^}`}{<~T~l~h~ȹ^~ǷZ~P~H~H~M~N~K~G~D~F~F~F~H~K~H~}}Q~y}כ|t|{|||{{{°|w}v}yV}{||Y}|V~͉~SWY[Xz~¹t}|w|ţ|r}|l}ñ||||||{||vv|uu||||{{{{{{{{{{{xw|{{|׫|m}|}~}}}}{A~M~Ĺ_~k~÷a~K~D~N~\~g~o~k~X~|>~c}`}Z}|½|{|}|||i}v}u}a}|a}i~~c~ļq}R}_}q}ɼj}[}Q}V}}żo~h~[~a~_~i~CCb~ʽY~U~}=~Q}G}\}\}|X}O~{~~BEJIu~˾]~?~C}|ف}V~|F~v}}B~J~~G~w}w}}j}č|x||u}T~M~O~ǿd~øe~I~}}o}U}}n~JPKLPPK~ÿq~u~~YihjeIp~b~}z}]~LSI~@t~`~O~}}N~L~U~PjRo~]~e~w~ShiVu~{I~p}k}q}|}y}n}f}`}|ɞ||Ǘ|Æ}o~o~}]}~T}\}Y}ޙ|Ć|ۏ|v}\~s~BRUMm~}j~}9m~X~}}X}ٍ|º|p|q|V}|L~^~b~v~HHk~J~ǽx}q}e}d}ԍ}g~~~G~a~\~Z~|}ڙ|o|ߎ|S~Qol\Gg~}m}}\}Ԧ|½|ý|b}Z~~N|~P~h}|s|~M}Ɂ}}Q~}}X~]~]~^~]~[~Z~Y~\~c~c~R~}}{M~Z~]~Z~T~N~ގ}|}f}ü|||{{||||||||||||||||w|{{{t|||j}u}~}}O~Q~M~K~F~C~A~}~}z}{}}}|}v}üs}u}q}ſo}s}w}y}{}Ҁ}΀}σ}ʀ}|}v}}V~p~FRURQOJD>:98878;=>@DGD=:=|9d~ɿ[~Q~N~}l}^}[}|ߡ|۟||`}g}_}ڧ||Ÿ|ţ|||||ؠ|||ڠ|Л|ԟ||`}a}||||Ѹ|¯||||||~|s|{n|||d}d}ȼj}}}G~I~B~@~B~K~S~U~U~T~U~H~e}ă|}||||||||||||||||||||||||||||||||ɐ||T}|<~]~<?:67:8m~f~U~E~G~S~]~e~h~z8{9e~Q~H~J~P~O~G~B~A~G~P~Z~h~z8}:r~f~^~żY~X~a~l~b~K~i}]}d}|ğ|||Ϭ|۴|Ę||֒|`}}}}}s}t}I~g~CJVYQHB@@AAl~C~L}v|x||X}D~X~ĹZ~Ƽ[~ƽ^~W~}E~e}e}}ٍ}i}؜|w|b|o||x}¾}i}^}~}Y~r~~AB|~d~|}l}؈}`~~~z~^~~P~R~}}w}o}Ԯ|||Ԟ|{}X~a~d~[~˄}X}X}Y}d}y}z}Y}M}R}Z}ĺ_}Y}R}Q}Y}T}G}V}f}e}}O}}P}v}U~úf~~~ýp~O~݂}ľm}j}w}h}Y}ה|||||ѡ|m}}d~c~ވ}n}}}r}ߚ||^}v}w}g}Л|u|x|||{b|ޖ|b}c}U}M}õ\}d}k}ɽl}\}|üy|j|p|q|l|`|b|Ñ|g}Ԫ|x|g|~|ҧ|h}ό}_~z~|~s~f~c~\~G~J~`~~LQVXPDt~o~\~w}l}}R~X~L~v}̊||Ƨ||y|Ъ{{~Y|Ĉ|ņ|Z|[|u||||{{{||||||||»~||V}n}}}ƈ}١|x|{||||Ć}[~`~}i}|{{{{{k|Ɍ|ݚ|Е|—||Ѳ|||i}t}Ɖ}}K~M~Y~c~c~a~ĸ\~S~Q~T~Q~M~K~N~O~N~K~G~E~G~E~~A~~A~A~A~~@~A~F~I~I~G~G~G~B~|}x}w}u}v}A~D~F~F~E~D~B~C~D~G~J~Q~U~ǽX~ȿY~^~`~¹Z~X~V~M~}}P~j~m~}||||~}ڣ}}͆}v}żm}a}c}t}ƽx}f}ڞ||~i|{׵{˩{{{{k|y|ږ|ýt}T~j~u~}?FJHD?>>~=|?|?y~n~X~q}|Չ|S}c}}N~|J~}o}[}Y}U}T}Q}e}j}m}a}^}~Y}ћ||w|w||||y}T~l~m~X~S~ǻ[~y9@B?o~^~ĹY~Z~g~Ļe~c~U~¹o}|_|{͎{c|O}z}~B~I~L~L~D~q}l}K~i~~E~_~I~Ž]}R}ľa}K~d~z~y~~A~n~S~G~}}}f}ҕ||đ|۟|||_}ݏ}W~W~xK~j}[}Z}a}ž}Y~·o~~w~}ڑ|}|z|||{}t}^}|q|q|y|||||ɻ|Ȼ|µ|μ||ֻ||{{{{{sq|{x|}||||ۼ|}T~a~\~V~S~S~F~u}u}w}Ļl}h}|}~I~zF~w}}[~r~Z~m}[}W}yK}{H}k}Q~V~T~Ľb~ſg~\~Q~}}|||׭|n}o}_}|ڛ|ԕ|Փ|ٔ||}N}ȿi}X~s~~?z;`~T~M~u}T}zK}V}\}úf}C~X~a~H~n}r}j}ɽc}B~^~u~EKPRRNGAB}>q~s~w~[~l}N}|Žt|Ž~||}|||||~s|{{r||أ|̊}r~KzEd~X~_~e~_~}}\~HPCX~K~m~~~FW[Fa~d~FIn~R~G~I~H~}}y}I~S~R~R~L~}}x}}}}}j}~M}Q}}B~V~L~J~R~X~Z~U~}}W}v|e|_|Z|P}I~]~y:Cn~H~[}Վ|}|v|ɓ||d}b}¼s}P~c~x}ϝ||{{||}g~i~}}\~i~l~`~}v}|w|y|»|Ύ|m}p~~x~^~k~t~c~қ}}}}Z~~T~U~Y~}b}v}_~s~V~j}Ӫ|ʨ|޲|a}ƹn}z?~K~ʼW~S~N~p~T]QG~{~l~^~S~N~T~^~g~j~^~}|}_}͖||{||Ø|ޭ|||٠||v|p|t|{|||||½|Δ||U}Y}d}w}}~E~H~M~S~^~j~l~m~j~b~ƽ_~]~[~Q~G~}y}z}w}t}r}q}y}}H~N~S~X~\~b~g~l~{8<=>ADJSYbjgaWPF@@??<;;:z6h~h~l~l~j~e~d~_~źX~U~X~K~Žs}`}||||||||||||||֡|}Y}a}h}l}r}}F~P~U~ĺ\~X~R~G~A~{@~D~H~J~K~N~Q~S~T~W~U~}n}||||۟|`}n}|}F~F~G~H~M~T~¸Z~_~a~e~m~y~@B|~e~}||||||||||||||||ɣ||e}y}}~}c}™|™|ۧ|`}k}y}E~G~L~U~]~\~S~R~Z~b~~59;{7_~W~W~c~i~d~_~V~G~t}g}[}Y}j}L~`~_~Z~c~:EKF?=;|8e~d~i~W~H~K~Q~M~}݅}v}j}c}`}c}k}p}n}߂}X~ļl~z~|~üp~o~\~ۇ}p}Ľn}}P~b~f~[~K~D~H~O~K~v}U}L}|t|_|M|~F|[|K}A~˾]~g~k~;IR\igKR~~S}p|v||Ľ|||r|l|s|{J}o}O~M~b}m}h~FOH|?E\j߉~üw}ą||»||¼~}]~g~a~U~V~f~j~X~}S~W~V~}٠}}}n}n}W}|^}R~}~y~e~`~c~g~~~ľv~}c}F}e|b|t|Ǒ|b}z}ɇ}}}~}b}|J}\}q}}]}|}Y~}s}b}_}|||՚|q|a|c||`|{ª{¼{̵{γ{{|||ۘ||Q}i}g}|`|öu|g}r}\}ۊ|źr|݀|L}~G}|xH}Y}ĺj}`}|d|h|r|q|r|{ƽ{{{~v|{{n|Ō|S}Z}}X~g~p~x~|?t~[~i}||e|{k|ǒ|n}Ă}h}|…|~|u||ݓ|{|{Ə{ԟ{{i|u|n|h|i|Ƽp|x||U}b}Ǻg}a}T}|n|r|t|u||g}}}}}}}}|}{}|||||{~|z}||||{{{|||{{޼{i|||L}Ǿb}|}J~Z~`~U~F~}|E~G~{B~|}y}A~J~P~V~\~\~X~R~M~M~N~L~H~{@~t}m}g}ùg}o}x}}}}}|}}J~L~J~J~K~K~K~I~H~H~I~H~H~E~H~H~F~}|}d}|ɟ|l}}}||||}y}|ј||b}}R~[~M~z}o}x}}}{}{}|C~M~b~p~q~q~e~W~Q~N~P~N~G~y}ǽ_}ƻ_}k}}C~T~l~y~CEKMRIi~}|\|ܟ{{|p}ה}}R~}ƺp}Q}Q}´b}s}M~j~~~s~`~Y~Y~h~y~~~~n~b~N~z}}b~Ofº_Eg~L~A~y}úp}k}b}֡|j|ķ{i{f{{Ε|}T~b~ľs~~~߁~W~}}o~JUODk~N~w}X}|ю|n}z}q}Қ||x|͈|ǀ|ֆ|W}V~|J\UDp~g~[~~}|}|z||||Û||r}P~d~`~}c}|Ż}|Ӈ|w}j~~~l~~K~d}…|`|۰{ۺ{Ի{ϻ{²{{{{{{{{{{{{{{{{{{vt|xv|{x||||ç|ң||b}}T~Žh~s~u~m~_~P~E~y}t}{}t}[}ؙ|И|\}m}y}}S~}}}}L~O~}l}}}e~~Ho~zB~`}]}_}b}w}M~^~_~U~р}n}L~C\\D^~L~V~^~}}}À}`}S}n}M~U~R~H~Q~q~GPCe~ĽX~d~q~x~t~Y~w}Q}{|e|`|ۂ|\}c}Y}|R}G~X~n}ڏ|ē||}}_~\~T~}}̈}u}l}h}m}}Ȉ}Ј}}}݌}r}`}W}Ҁ}a~v~Žr~Q~t}^}c}J~~~F~i~M~{}|B~U~e~~WdYIr~|P~o}Ŕ|x|V}T~ۀ~~~Du~\~|}[}n}b~TbY}Hr~]~ڃ}Q}|Y}}c~f~U~ڈ}~}K~j~t~w~|G~f~{~e~}r}{]}ׯ|||||}[~b~}ш}}]~|~~j~ӂ}}Q}||ֺ||;||||||ĵ||˕|t}}V~_~h~f~[~S~ܖ}{}|||ΐ||N}Y}s}}yE~{C~}@~}@~~}l}b}\}[}\}[}Y}]}d}f}d}f}q}~B~L~S~Z~`~d~h~m~:>BEHJMPNLHE@;:9|8k~h~f~f~a~Y~ɼR~N~O~·V~^~a~c~f~d~d~g~l~x8k~`~\~Z~Z~`~f~j~r~p~i~d~_~¹X~U~M~G~E~H~J~H~I~N~U~^~k~y~~|~x~p~g~c~_~S~}I~~}w}f}|Ξ|||||||||||||||||ڸ|||||Y}e}n}¹q}m}ƽm}r}w}}}y}x}~@~B~I~I~J~M~N~}e}Ԙ|||||||||||||||||||۬|]}h}y}}H~N~Y~l~q~s~k~k~k~e~Z~}p}\}۟|||||||||||||Λ|؜||Z}f}o}r}r}l}s}H~^~z~DEx~k~i~l~j~_~[~]~]~[~\~_~h~8=AA;|3z39<:|6e~c~a~y39CJNMLHDDECCA=<=:d~ĻQ~E~D~I~O~P~P~R~ƻX~Ⱦ[~S~N~K~F~D~}z}}}B~|?~>~E~O~S~S~T~]~l~k~Y~O~]~o~t~y~w~f~Y~k~IQNIEt~W~K~P~ļ[~d~g~i~a~O~=~l}n}u}k}Q}A}|A}\}wF~]~]~Q~}}}^~v~k~}ƾk}z}O~a~n~_~~D~s}J~p~~{Tэ~`~}P~e~n~c~}G~j}^}a}d}X}S}a}ڂ}L~I~~}s}݅}{I~L~}}[}ь|w|n|g|n|Ď|i}v}j}`}{}Z~i~ȿa~{;~p}K~ɾn~~{~c~G~o}d}o}u}i}_}\}_}b}i}f}]}|||ח|͖||s|g|i|||r}k}|ݸ{{o|{ӓ{zO|v|ٕ||ݔ||U}b}Y}އ|v|P}y}M~J~F~H~J~R~^~j~n~a~Q~G~p}j}}F~b~n~d~M~̀}Y}|¾|p|Ɛ|a}n}||w|{{{||ɬ|ر|ͥ|ɞ|˝|Ϟ|Ē|۟|Z}Y}W}Y}a}V}}B}|O}V}S}|}|و|ݓ|ڗ|LJ|ƃ|ܔ|V}\}ړ|t|}|ՙ||\}p}}̄}~}Ӏ}}}Ǎ}د|{{{||||||||||||{{{vx|}|z{|{{{{{{||{{d|v|މ|L}R}_}i}m}t}z}~B~K~P~M~I~K~K~G~}v}s}v}~@~H~O~V~T~J~D~E~L~M~M~N~O~Q~R~R~M~}}}}s}Żk}h}i}d}|Ȝ||d}q}w}s}p}j}j}f}ǽa}\}d}t}{}}}~}G~J~}}}}j}_}v}T~b~a~c~v~~MNG~~l~\~H~E~]~~STNG~r~U~}}[}ϑ|||~|||z|v|||}}ޜ}ړ}þ{}k}m}z}s}k}c}[}|||||||||k}s}o}_}||j}}U~|I~b}}G}\}}d~b~c~t~z~q~}{}}h~|~~|~q~j~f~d~g~o~v~y~u~ļp~b~V~}~}u}h}[}\}u}}e~l~j~k~n~i~ƽa~V~H~|G~O~\~]~U~E~D~F~G~H~zD~߂}{}p}_}˝|||`}}\~{V~}a}||l}|¼|ſ|ǥ|¼|||||||{{{{{{{{{{{{{{{{{{{{{|ý||||a}r}|}؅}}}ك}}L~M~}}|F~L~V~Z~S~ۃ}Ļr}ф}R~Z~X~\~z~~~e~G~}M~j~CJDy:_~O~~@~r}w}G~L~}T}|P}f}P~~^cPx~Y~ό}{}Ņ}}S~|H~|}A~H~G~J~V~?_o[}Bl~_~P~}ʿm}k}f}f}p}E~\~}@NPGi~H~u}P~LtzlR~x~¼l~Q~f}ҝ||ÿ|||||{{{|||||||||||{|χ|j}c~~G@>@CIMQTTRPI@v~k~Z~݅}`}ڛ|ԝ|o}c~~ZWK~l~ݓ}~X}~|o|w|S}~}Q~X~Q~t}|Ƽ{|ς|P}r}S~CVû_b]QEacVt~H~S}Ɇ|ǽ|׈|T}p}J~`~p~y~r~_~P~M~P~P~O~K~J~J~H~D~{}t}v}}}}}}~F~J~K~}}؈}ُ}ޗ}ّ}͆}́}ׄ}}}I~T~Y~W~X~^~h~p~<;::;:99;@AADEDCFIHEBBBCDD@=:y7k~j~i~i~m~q~o~n~l~l~m~n~n~l~l~k~k~i~i~i~l~p~p~n~i~g~f~j~o~p~n~l~p~u~{=}?z=v~s~n~k~j~i~e~b~_~^~Y~Q~~H~{G~}G~{F~G~~E~}w}n}f}a}\}^}`}a}|ԥ||||||||ڥ||b}o}s}x}{}w}h}|ԡ|ƛ|œ|œ|Ŀ|–|ʗ|۠||[}f}l}p}u}~}{C~C~E~G~L~P~Q~Q~S~]~W~}k}|۟|͟|Þ||||||||||||||||||||ʪ|ͦ|ץ||_}l}̀}ׁ}z}t}Ⱦh}|}G~W~_~_~[~N~L~J~M~O~K~I~K~O~U~V~S~P~M~O~U~U~R~N~M~O~S~W~S~P~R~[~h~8875z25;DIHEA=:89>BBA>8`~T~ʽQ~W~}4AǺOVYYVɽQLFA==?:e~[~[~b~{7z7c~U~L~K~L~J~I~M~ƻT~Y~[~[~[~d~;A@>;~;~;s~i~c~j~t~p~d~Ⱦ\~a~j~m~j~i~j~Z~}}}\}|i}x}}}}Ć}Ή}}O~Y~Ļk~~zE~x~v~w~v~w~x~AILI@;<=~<s~g~Y~L~E~}}l}X}||Y}o}s}g}ޮ||p}ב}}ʌ}|||||||{Ŀ{ȫ{{h|x|Ɔ|||||I}W}q}H~O~N~I~E~L~g~M`Tj~k}W}˄}i~|~p~T~C~}A~}|K~ه}j}^}|Ë|ݛ|l}}\~i~^~z}`}X}\}ɾ}\~b~ݜ}x}||~||\}}ߝ}}_~e~n~h~}d}||||}b}s}s}~[}]|ј{{o|||j}M~m~{=><<DOYXHd~p}N}|W}h}|Í|͓|c}u}m}[}T}`}f}o}g}e}b}`}|Ԟ|ҡ||`}|ۦ|נ||Z}g}q}g}W}Ɖ|h|{̜{ę{ڥ{_|r||{I}zG}{G}Q}Y}Y}S}R}Q}T}^}i}b}~W}~X}\}[}|Œ||||¾||||||||||||||||{Ż{{{||||{{z|||||{{{{{{{{{{{˻{{{{x||Ĥ||}_}e}h}m}r}r}r}Ļs}u}u}m}`}X}S}X}`}a}~]}|޸|ϳ|ï||||½|j}ΐ}Z~f~h~źe~f~ʾd~Y~N~D~D~O~X~Ĺa~ʿe~Źc~`~_~]~Z~W~ȻY~^~c~b~a~Ź_~\~Q~~F~݀}s}f}\}|Ӗ|Ŀ|||͛|Ñ||||a}d}w}}}}ܚ}ĉ}r}[}|a}~}٘}˒}w}||}|n|{Μ{ڢ{l||ûz}S~c~i~g~\~L~z}i}Ÿ\}k}q}W~~;GQPSW[^YUPNMPLI~q~`~L~}|}t}d}b}d}u}́}܇}Ӂ}o}U}y|Y|[|X}}m~z~l~Y~M~ڀ}e}zM}Ԍ|V}֍}R~Z~́}S}Ɍ|||~Q}X}`}z}O~\~˼`~п]~^~ʻV~Q~P~N~O~C~`}ہ|a|~\|m||Ŝ||{|r|u|t|{{{|{||||||||{{{{{{{{{{{{{{{{{{{{{{{{{||}{|{{}x|w|u|}|”|\}p}܇}O~Q~P~U~b~m~q~Ϳb~M~t}^}Q}Z}r}}}y}w}F~]~b~̿]~l~CLJCm~¹]~ºc~ȿk~Z~}]}N}P}}R}V}e}}}~N~_~|C~}}}G~_~yBI|~^~E~w}}L~U~V~T~a~|~P_Z@M~l}X}k}S~o~}~z~n~j~k~j~ûc~S~B~q}?~o~Vk=CC:VAg~V~}^}|||||\}|ڨ||q|}o|y|||||||||ʜ|_}ސ}g~~~üq~U~{}^}X}~}ƿo~NO|~]~R~S~]~a~b~^~O~v}b}Z}b}}a}|Գ|«||||||{{{{{||å||΋}Z~n~w~FNVû^l~}~ABDED?|8h~c~c~`~ɿX~Q~K~E~E~~E~}x}t}y@~G~I~H~K~Q~ɿZ~_~`~e~j~l~l~c~[~V~̽X~ɼY~¶Z~X~V~P~K~H~G~}F~~F~F~I~P~U~Z~]~c~h~n~r~z:{:{:{9{:{:}<=>?@=|;r~m~h~d~_~Y~W~^~h~}9<><9~7{6g~`~]~_~c~g~l~l~k~j~n~r~s~p~m~h~^~T~L~}}}}}}{}r}o}u}|}þ}}~}Ŀz}w}}E~L~V~Ƚ_~h~n~n~o~m~k~f~]~V~P~O~N~K~}́}v}h}]}[}\}^}b}g}k}n}v}σ}}}G~J~M~M~O~M~J~~F~}}t}m}n}w}w?~B~I~K~O~O~P~R~O~O~O~O~K~H~J~J~H~K~G~F~F~{C~}}؃}s}i}a}|||V}f}}}H~I~E~F~zB~F~M~K~P~R~T~T~T~R~ĹU~]~b~g~i~g~d~]~]~`~b~e~a~h~z8>BDB@@@?>>>>==<:~:z8k~^~S~C~r}i}s}G~V~`~b~a~a~a~e~9CHLIFCA@?==@DHGB{:k~i~|:AHPW]bfeĽc[RK}E~~~~u~e~R~}~}x}t}p}m}q}w}v}s}ļp}n}j}_}U}R}R}R}Z}k}}}}}}}G~}u}l}m}o}j}_}V}|S}U}Z}d}t}}S~d~žl~c~S~F~~B~H~K~J~I~P~^~j~o~x~BDB{~x~v~w~w~v~t~q~Ǿi~Y~K~{G~{H~~L~}ք}փ}}K~Z~o~{~{~}~~~x~e~O~Ճ}}}Ȅ}Ž~}p}l}u}́}ڃ}ـ}q}d}f}u}D~J~M~M~J~B~y}|}}{C~{C~}}o}`}X}~N}ی|}O}j}߄}}ûq}|y|e|{{~f|j|k|u||\}Q~o~r~f~W~P~}I~}}Ӂ}}}}}p}ۘ|Ð||m}z}ć}}\~`~Z~ߜ}}v}̈́}P~]~R~Ȁ}̐|k|p|~||v|{||k}y}y}`}a}ùn}q}t}}}O~c~_~}Z}S}X}_}e}y}U~^~`~X~}M~؈}j}f}g}t}t}|}ƾt}c}V}l}R~z~Ev~d~j~m~ƿf~c~Z~S~S~f~s~p~L~[}||n}Y~x~r~S~m}X}a}j}||||̨||j}|þ|||ԫ||י||O}ùo}P~g~ļp~_~v}Ӂ|^|}Y|f|{||ɨ|ճ|ʮ|||z|y|z|||ޮ|k}q}v}s}m}g}|ի||||||||||||||||||~|z{|vw|{{z{||||||~~|{{{{{{{{{||||||ʩ|~`}m}þz}߈}L~S~Z~Ⱦg~q~o~e~]~[~^~f~z588z6g~_~øY~T~Q~O~K~J~H~J~K~L~M~M~N~}u}m}o}Ǿx}ς}݉}߇}}|J~P~U~T~S~S~U~X~[~[~yL~s}Y}|Н|||y|v|v|{|||x|~r|}m|y||_}Η}q~ԑ~ڔ~‚~r~j~a~h~r~žm~i~W~}o}r}P~m~~FA~=k~b~ǿZ~X~[~û]~û_~º`~b~`~\~W~S~Q~P~}ؖ}r}||t|׾{ڿ{{ѵ{¥{{m{f{zתzz{{l|Y}o}ÿ}s}_}ץ|||}|m|{ڳ{٪{ߢ{{a||҉}a~u~v~n~]~M~}?~u}S~DVXGj~|E~]}܌|p|r|~|||||{˼{{{Ƽ{{{|||||~||u|||½||f}||ߩ||m}׊}Q~[~`~a~c~f~p~~IRZdhdZLBi~O~s}Y}|͓|||ɠ||~]}|ݖ|b}X~~SSQNKCp~Y~I~y@~F~]~v~z~u~C`@D{ePAu~x@{Ew~T~т}o}i}|ͩ||q|m|||•}c~}}~j}Դ|æ||||ܩ|}\~l~c~W~Y~g~{~FQZS{@d~O~{>~p}üb}`}}>~V~r~{CG~l~W~R~Ļg~FPPOKCs~ż_~N~?~H~k~Rhmlj_K>i~Y~O~}E~ƽp}[}||||Ƽ|n|e|z|b}}s~}~|~i~_~~M~u}M}y|o|F}j}R~s~CD;5z2c~\~źX~V~Ƽ[~e~l~y;y<x~u~z~؁~~~y~n~h~e~}}|ȧ|||||p|q|u|ˊ|]}J~o}a~SioĽf[SJBp~ɿa~W~R~Q~R~M~|D~}|}y}z}t}n}k}e}`}~[}|ϙ|Đ|Ō||_}y}ٌ}Ո}x}l}g}b}c}k}üw}|}{}v}Ľr}żq}s}v}ȿq}j}e}g}g}e}c}g}q}~}C~F~I~N~R~V~\~ƽ_~_~\~ļW~T~úT~W~^~`~\~Y~U~U~U~Z~a~h~z69::m~f~a~\~ƾU~R~S~W~Z~[~\~Z~X~U~N~}o}Z}֢|ž||||||||||||||||||||ڣ|~Y}\}\}a}h}Ƽp}t}w}u}n}h}d}f}¹i}k}u}|B~E~F~D~E~G~H~K~·S~Z~]~e~p~{:{:y9n~i~i~k~n~o~q~q~l~i~i~k~p~t~v~|={<u~r~n~i~g~g~b~]~_~Ƚ]~T~Q~J~F~F~B~}A~|A~{A~{@~|@~C~G~N~V~\~]~a~{6:>CFHKLȽOQTVSúQPNOPLLJEEGEIJGGB><<p~c~Ľ\~P~I~}}}r}m}f}`}]}Z}_}»n}}}G~R~V~Y~źb~l~p~k~_~Q~~D~}}|D~J~Q~W~¸_~e~d~^~Z~ǽX~X~]~_~d~i~p~q~o~j~m~=CGEy~Z~y}Y}˘||ĝ|̣|Ԩ|ݯ|ޯ|ר|Ϡ|Ҡ||\}a}d}e}d}c}_}[}Z}_}f}l}m}l}j}n}z}E~K~R~`~{7:~9f~Y~P~G~{=~g}\}Z}c}z}I~Q~T~S~R~¸U~]~g~n~n~h~i~~8>AB@<<>ADGGE@u~h~Ƽ_~Z~\~Ƽ_~Ǿ_~Ž_~ǿb~h~žg~]~T~M~L~M~P~R~S~U~X~Z~V~}u}f}e}|Ѳ||||||||½|˜|і|S}Ĺm}I~ø\~k~~<BDCCHOȾX__ZKz~c~S~݂}_}ʏ|u|i|{{ޮ{{b|؏|p}}Y~V~R~T~Q~Y~d~p~{~Bz~z~v~_~}[}||g}M~b~e~Z~}]}]}k}v}x}o}x}}\~i~ƿm~e~[~S~W~]~_~Y~}N~~}n}f}d}_}W}Z}h}Q~l~~~~b~H~c}[}\}c}d}d}d}e}e}¹f}j}q}z}}}yA~}|C~H~L~K~~F~}E~F~P~Z~b~b~b~`~Ļ_~ż^~º\~[~ȿ`~g~t~{~y~{~}~~BHLH~n~\~L~{}f}`}i}~}˛}}}f}|r|{~o|q|u|||f}։}S~X~Y~؏}a}|t|q|}m|{{{~m|||||b}|ڟ||p|i|k|~|ɛ|}^}j}t}y}{}{}}}Ā}̓}ʁ}ֆ}O~]~i~e~X~؅}V}|o|m|t||||||||||||||||||||{{{{{{{|||||q}z}u}|ά||r|{v|||||||||™|ѣ|ܬ||׭|ť|||||ï||o}}Ê}ҏ}}}O~R~S~W~S~K~}߆}څ}}}N~R~Q~U~U~T~Q~}ޒ}̇}ą}ˋ}Ւ}ُ}݊}}}G~N~T~[~^~h~w~p~_~O~}y}}F~H~~}}}E~^~y~EID~Cs~^~U~u}|x|~|b}}}}b~]~{V~ڛ}y}h}•|w|{Ŀ{ý{ý{{{|||||̱|v}c~o~m~h~O~}}]~ڂ~O`lfaRD?y~z~l~^~N~ކ}̈́}x}]}֚|||Χ|||||ҿ||ʹ|||||Ρ||i}u}r}m}g}c}ûh}w}G~U~_~ŷ`~W~J~y}X}z|]|c|V}}t~PctEKIBrdXOHE}B~w~p~k~h~c~}X~љ}Ž}}}}n}||{|{ܿ{{t|||x|{ι{ʴ{{{y||||ƶ||||||\}\}U}X}p}}Y~`~_~}}n}ܮ|||w|s|~|ʠ||m}l}b}||~V}]}b}~^}|\}f}ɾy}}J~J~L~T~X~P~I~N~`~w~DILJEB~@x~o~k~ƺd~[~M~x}¸f}g}{}M~[~o~AMSTRLEAELPK@c~D~µa}\}Q}||||Q}]}w}N~ýn~JX\THJR^_WKBt~z~~~܂~v~c~U~ϋ}u}b}|Ɨ|||{|z|||||^}h}É}ݘ}Y~T~}o}R}||||ˑ||z|s|o|o|t|{|Ł||q~Že~V~H~{@~{@~B~C~D~E~I~O~U~V~R~N~L~K~H~F~E~D~D~H~H~H~G~B~@~B~D~H~L~O~P~O~O~O~M~L~I~G~E~}}~F~F~E~}C~~A~C~G~J~N~R~U~żV~ɾV~U~˿S~ʿS~ǻQ~¶O~N~K~G~B~A~C~F~J~N~R~T~U~U~ŹW~˿Z~^~b~c~]~ǽV~P~J~F~H~L~Q~U~V~S~M~}n}]}|Ӝ||||||||||||||||||||||||||||~|~||֏|W}p}|D~L~P~N~K~K~L~O~Q~Q~Q~R~ǽV~X~Y~[~^~^~a~c~d~d~i~i~h~f~c~`~^~_~a~a~c~b~`~˿]~W~S~Q~N~O~S~V~Y~\~Y~U~P~L~G~{C~}}}z}s}r}o}m}u}x}y}~}B~I~T~^~d~h~o~r~t~t~u~w~q~m~m~i~h~m~q~v~z<y;n~b~¸Y~Q~M~N~L~O~N~M~N~L~L~N~L~G~G~G~~G~H~I~}H~~G~F~I~Q~V~a~k~j~_~U~І}c}Z}||||T}b}z}L~X~`~e~l~n~i~`~X~M~I~I~L~Q~[~c~h~}79=DKPVZXQH|~l~^~}}w}|̴|||ÿ|Ū|ʩ|Ц|ڥ||Z}f}v}}}|B~|@~?~A~I~R~ĸX~\~^~_~]~]~`~e~h~l~y6y6x5x5{7:>BA=k~[~Q~N~M~L~I~F~}}p}a}[}g}A~T~e~p~y7j~j~y6|8z7h~^~ȼT~L~I~I~K~I~F~E~J~R~ʿX~[~^~g~|8=>>=}9{9:?DHJIBq~b~a~g~m~p~o~h~c~^~[~\~_~a~`~Y~}ӕ}}s}~g}|׸|Ӵ|ٶ||||||||||\}h}~}I~S~X~X~Z~[~^~b~g~c~]~V~S~Q~Q~N~J~H~H~L~O~R~S~P~L~G~E~E~F~G~K~Z~o~}~ȿw~\~þ}b}ƨ|||ļ||||k}o}u}}}ȼ}ƺ}}z}s}k}g}b}`}c}k}ýt}}}}}}}zB~D~E~F~K~P~Z~÷^~e~e~c~]~ǻV~ŸR~ȺR~Y~^~d~f~f~d~\~˿X~ǻW~^~h~n~p~g~\~S~M~F~|>~z<~@~J~ƻW~b~h~h~~699;<>?CFHKJIHIOTXUJ?u~v~v~z~z~u~n~m~s~p~i~_~Q~ٌ}x}a}Ӡ|||||||v|s|v|}|ƌ|Z}ƾx}}M~N~K~M~Q~V~ļ\~d~k~s~~~DJMMIE{?u~p~j~d~[~U~}o}|Ľ|||||||||||||||Ţ|֩|ڪ|ܬ|ܯ|ح|ѫ|ͬ|Ʃ|¿|ƭ|ȱ|°||||||||||o}v}~}}}ņ}Ή}Ҋ}Ԋ}}}}N~W~]~d~j~l~q~t~ýo~g~a~^~`~a~b~b~^~T~|J~}~}ǽw}m}g}d}`}^}]}^}]}Y}|||ܡ|آ|Ҡ|ŗ||||Ǿ|̊||W}ƽp}M~`~j~m~[~{}X}O}ɺf}K~a~x~yDyCyB~n~·g~\~O~{J~{}_}S}xL}M}R}t}R~[~h~y~y=~AEyB~~x~ɿs~b~}}m}f}̀}}Z~s~{~b~֐}}}|ȡ|Ť||ÿ||||ĩ|Я|ձ||ܳ||{|n|h|r|||a}p}u}u}o}b}ߨ|||z|x|||ľ|ͦ|˧|||||ǭ|Ӳ||k}{}ы}}Q~V~X~T~R~T~S~V~]~]~]~]~X~U~V~Y~W~S~K~}}źe}V}O}T}b}l}m}k}j}o}}}}}|}e}|y|m|q|||ƾ|lj|̌|lj|ü|}|ʉ|}S}d}t}Ն}N~S~V~W~V~S~L~}y}n}l}l}m}j}e}_}{T}Ǝ|y|r|w|þ|~Z}w}ٗ}}}۝}}j}|ĥ|||v|t|{||Ö|ܤ||[}]}_}k}܇}]~~~LPOKIGE~B~BEJLI|Bq~úd~Z~W~T~P~G~z}o}c}Z}V}Y}d}w}Ս}}O~Z~Ŀl~~~{~þk~b~^~[~Z~W~M~}}u}D~R~d~p~t~s~`~M~}}|D~J~W~`~d~m~~\zEHKPMEnR~~m~h~a~c~f~_~]~}y}`}ˉ|l|d|j|t|z|΄|X}O~o~UXS|Hq~Y~{@~z}|}N~û`~~~GFF@Az>t~j~d~d~d~j~r~t~K~D~}?~x}s}r}u}r}v}}}G~J~}u}c}W}|ҋ|͈|׏||Y}a}c}e}m}~}H~O~U~V~R~N~K~L~N~Q~S~S~T~T~S~M~C~y}t}s}q}p}l}÷d}]}Y}Z}_}f}j}l}i}f}e}Ļg}l}r}u}r}l}f}c}b}c}a}^}\}Y}W}U}~S}~R}S}V}Z}a}ɿi}w}|D~N~W~\~\~S~J~}F~}}}}}ф}~}z}u}p}k}i}e}`}}\}||ڮ|ҫ|ɤ||||||||t|n|h|d|b|`|_|^|`|f|w|K}`}v}E~M~P~O~M~L~L~K~K~J~I~H~E~{A~x}s}p}q}p}n}p}r}s}t}w}y}{}x}r}m}l}i}d}a}a}a}a}`}_}^}a}ƺh}q}t}t}p}p}m}ûg}d}i}u}|@~G~O~S~P~N~L~J~I~K~L~N~P~N~J~J~H~E~H~I~J~Q~V~W~Z~ź]~Y~S~Q~N~K~O~T~ɽY~^~c~c~a~a~a~\~Z~ȻW~÷U~T~R~P~P~O~M~O~R~V~X~Z~Z~[~\~Y~T~Q~O~F~|}y}w}r}l}m}ʿk}ǻi}ʽi}l}t}}@~I~Q~W~˽[~^~^~d~k~y8~:;:z6i~i~i~k~z6~9;;}:y8n~k~j~l~l~m~m~h~ż^~V~P~N~N~T~]~żc~h~k~k~i~e~ɿ`~Ƽ_~¸\~[~X~W~T~T~V~Y~[~Y~U~P~M~K~J~L~P~T~X~]~ĺ^~¸^~Y~P~G~@~|>~@~F~K~M~K~F~B~A~A~E~J~O~S~̿X~\~a~d~h~h~f~c~_~Z~Z~\~^~̿]~Y~U~R~P~Q~U~W~W~T~Q~R~V~ʿ_~f~k~k~k~j~j~j~h~h~h~k~|:@FKNOPPPONPRSMCs~h~žb~a~^~X~P~H~}}}}}ی}ȃ}w}i}^}Z}Y}\}g}~}S~k~{~~EG~t~a~O~}~}s}j}]}|Ŏ|q|h|~g||g|}k|o|u|{||||||||||||||||||||||µ|͹|||n}r}w}w}v}t}s}r}n}j}~g}|||c}f}t}~}ˌ}}R~W~\~`~b~]~Y~W~T~[~^~a~f~g~k~n~q~¼u~s~o~k~h~c~_~}ږ}ć}~}w}r}q}p}r}w}~}މ}}}yG~|G~}F~}F~}F~}E~~E~E~C~B~C~G~P~[~e~}9@GJJJIIHGFEDDEEEFIMMFp~S~|}`}|Κ||||||||||||||||||||||||k}}È}Lj}Ä}ý~}x}v}v}w}v}w}Žy}z}|}~}Ձ}߄}}{E~I~M~Q~V~[~]~ź]~a~c~c~e~g~i~i~m~q~s~s~n~h~h~h~e~d~d~d~g~i~i~j~j~f~_~ǻ[~ŹY~÷X~ĸY~Ź[~÷[~X~S~L~H~E~D~}E~G~~I~{I~zI~}}}܊}Ո}˃}ž~}t}k}h}i}n}{}Ž}ԟ}}]~d~g~h~m~q~p~r~v~s~l~e~\~R~M~M~N~M~J~}~}w}u}v}z}}܃}}}}݉}ۈ}׈}΃}Ɓ}ƾ~}}}ü}ˎ}ђ}̎}ۚ}}ؗ}}{}s}b}|{X}{W}|~W}]}^}a}j}p}s}t}s}q}n}k}d}`}^}|ŕ|||v|z|||||Х||o}{}}}}{}o}[}||z|t|~|Ɛ|Ȕ|˗||||q}}}|}u}n}e}_}|||ԭ|||||}||||||||||||||||||ƨ||||||z|~w|y|||||̭||f}d}{Z}|ߎ|M}_}y}}}}F~N~Y~`~ƽd~ɿd~Ƽc~]~R~|C~r}b}T}~S}Y}`}h}s}}}}ʇ}ۑ}}Ӌ}y}i}_}d}x}}W~W~}ƿu}c}b}m}~}}I~T~`~h~úh~_~S~E~j}V}V}n}zF~W~_~^~V~M~~I~}}r}\}P}Q}^}{}M~Z~d~o~z~~~GLMKGDBDGKRļ\fkh»`T~Ek~P~Ā}~}Ҙ}d~y~~~}~q~g~^~U~Q~Q~U~f~~O[digY~ľn~Y~K~}P~e~Є~~UZY}RЊ~m~}b}ߑ|̓|݊||[}p}}[~e~d~e~_~Y~R~}}ގ}׎}͊}w}g}zS}Ԑ|x|g|m|Żp||X}d}y}C~N~U~÷[~]~g~n~r~<@?>?=?AB@{:h~ŹX~N~F~}|}t}o}l}f}]}Y}Y}V}X}^}^}Y}~X}|ϛ|Ľ|||||||||||||||ƛ|ɜ|ř||||||ſ|͘|||`}e}g}e}`}|٣|Ɨ|||||||||||||||||||||ˉ|zM}b}}W~l~~GMQTVZ¹^Ǿ__`_][žY»WVTTTTUüX[^``^ûZVSPNMJIGEDA{?y~t~j~]~Q~F~|}m}b}]}_}g}o}z<~@~C~D~D~D~C~B~C~D~F~I~M~P~źU~X~Z~Z~ȽV~T~R~Q~R~ĸU~ȽX~˿X~˿Z~\~^~a~c~e~h~j~g~e~d~d~b~`~a~a~c~c~Ƽ_~Y~U~S~T~T~U~V~÷[~c~e~b~Ȼ`~Ȼ`~ǻa~\~X~W~X~X~Y~a~i~l~l~l~k~g~b~^~^~`~a~g~l~r~y9y:y:p~k~f~d~b~`~^~`~c~e~e~g~g~h~h~h~g~i~i~g~`~Y~W~ƼT~O~N~ȽR~Z~_~b~y58;=AEFECA@~<o~h~e~]~W~R~N~L~J~I~I~I~I~J~M~O~Q~T~Y~Ĺ[~ʾ]~`~c~c~d~d~b~a~a~`~b~a~c~d~a~^~Z~Q~{F~x}ùk}`}Y}Z}]}b}g}o}x}}}yD~}}}}x?~@~C~G~J~N~R~V~˿[~_~a~_~Z~̿V~ʽT~X~c~o~=BEFEEGIIH}C|~m~_~P~}Ղ}}ˁ}̈́}˄}}{}Ƚy}˿y}{}Ձ}}yF~H~L~Q~W~_~d~i~k~m~m~i~ȿc~]~Y~Z~[~^~`~a~b~b~d~Żi~o~v~z~z~v~p~j~d~_~Z~T~Q~P~R~S~Q~}}Ӈ}Ł}|}y}w}u}t}t}s}n}j}|f}|ڸ|ɩ||||˞||\}m}ф}N~[~l~z~~KPQ~Mτ~k~}w}|||||||||||||||||||||||||||||||||||ȱ|̲|ҵ|ն|ط|ܸ||||||||~`}e}h}m}r}úw}Ѐ}݅}zI~M~O~R~O~}ۈ}»x}l}k}i}k}p}u}Ļ}Ҍ}ۏ}}}}߄}݀}~}}}{G~{G~{G~}ځ}o}c}Z}U}S}|R}|||ܖ|ג|ӌ|Ҋ|׋||yL}P}U}[}b}e}e}Żg}j}m}s}|}A~D~I~P~U~Z~\~]~Ÿa~Ǻd~¶d~`~[~U~N~}z}i}Y}|ђ|Ȑ|ǔ|˘|ў|ۤ|||]}c}i}m}r}v}z}}}~}}}|}}}}}C~I~P~W~\~Ƽ`~e~j~m~m~n~m~i~e~b~`~\~·W~S~O~N~L~L~L~K~K~J~J~J~L~N~P~R~S~S~S~S~U~W~Y~]~b~e~f~f~c~]~V~N~}Ռ}ă}}}w}p}j}|||||||||||||||||||||||||||||״|g}u}}}Đ}Ŏ}ǿ}}~}v}l}e}|ۼ|м|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||Ƽ|ú||||||||{{{{{{{½{{{{{v|||¿|ζ|ջ|Ѷ|©||}|p|{|i|t||֦|c}u}}ƾ}ļ}x}s}t}v}s}l}g}h}k}l}l}q}ƃ}}Y~a~g~g~e~a~[~S~O~N~J~}p}`}Y}Z}g}݂}T~h~v~|~{~q~b~P~}}|I~O~U~W~Q~ڌ}r}Y}ِ|Ļ||o|e|a|g|x||c}y}ڌ}}\~d~j~g~_~V~}|}i}^}[}a}q}}N~V~Z~Z~Z~Y~V~M~~@~m}d}i}@~R~e~q~s~n~e~Y~M~F~}t}h}^}Y}Z}e}~}T~p~GTZWOG|~k~\~M~~}h}^}a}j}r}ς}}O~O~O~R~V~Z~^~_~Z~Q~K~K~Q~\~l~x~}~z~u~q~n~m~k~i~i~k~l~k~k~m~p~r~q~e~Z~S~}Њ}ž}t}o}t}v}q}k}d}߭|Ö||y|p|i|o|þ|Ԏ||V}^}f}n}o}n}p}q}w}}~B~H~Q~T~]~c~Ǿ^~T~P~K~G~H~J~J~P~W~Ž\~j~x~|~|@}@x~o~l~e~d~i~d~[~T~S~Q~N~K~I~F~}s}h}a}^}Y}S}R}T}S}R}V}_}d}e}g}i}i}g}c}b}c}e}f}j}o}ǿu}z}}}~}~}}}}{}}}}}}}߄}߆}ۇ}׆}΃}}¹z}t}o}l}i}g}d}`}|_}z^}|||||٩|Ӧ|Ф|ϣ|ϣ|Ѥ|Ӥ|ԣ|Ӡ|қ|Ԙ|ݚ||T}Z}b}ùj}r}w}z}{}~}{?~A~C~F~I~O~X~_~g~m~t~>CGKNPRUUSQMKHHGGFFCA?|:n~i~i~g~b~]~ɽV~R~N~I~F~C~B~B~@~@~?~?~?~?~>~?~?~@~@~?~?~@~@~A~C~C~C~D~F~H~I~I~G~E~D~D~C~B~{@~y?~x?~z@~}A~C~E~G~H~J~I~I~H~K~M~N~Q~Q~R~U~[~`~ùc~a~Z~R~I~ނ}q}_}|R}ږ|Ɏ|||Ô|ș|џ|ۦ|||}\}]}^}`}b}d}e}f}h}i}l}p}Ļr}u}z}ڀ}}}}zH~|H~}I~}I~}I~}I~|I~zG~}}}}ك}փ}΀}{}»w}v}r}m}i}g}b}]}||۩|ˢ|||||||||||||ȿ|ɘ|֜||[}d}o}z}܅}|H~M~Q~U~X~\~_~`~c~e~g~j~n~q~|9|:{:r~o~j~f~a~^~\~[~Z~W~U~P~K~}E~}{}v}u}x}~}݂}}}}}ڊ}̈́}~}y}v}u}w}{}~}ǁ}ч}ގ}}Q~W~^~b~d~b~\~T~{L~}}H~P~U~Y~Z~Y~ĺ\~a~j~r~v~w~q~f~W~|I~Ӏ}s}l}j}k}m}p}r}u}{}Ą}ь}ߕ}}}}}}ߎ}܌}܌}؊}ӆ}҆}Ѓ}Ԇ}׈}݋}}}}}~N~O~~N~}}}}}}J~O~U~Y~\~[~V~M~߇}ľu}g}`}]}[}]}^}]}[}Y}Z}`}j}z}}F~Q~[~b~h~ǿk~p~u~y~z~{~}~~փ~މ~~~~~OQQR~P~ֆ~ƾu~g~\~U~S~V~[~`~d~i~l~k~i~e~_~Z~S~O~O~O~P~S~T~U~Q~}ێ}ǃ}v}o}m}l}o}p}x}}ʇ}ڏ}}}}M~O~P~O~M~M~}L~}}ۊ}}}n}d}^}٨||||||||||Ǽ|˜||`}h}o}}ی}}}N~R~U~X~]~^~_~_~^~[~Y~¾Y~Z~_~h~~:@EJORUUUUUQNJIGB<p~h~c~ȿ]~T~K~D~?~z;~m}e}`}^}^}a}j}s}|=~>~=~<~~;~}:~}:~;~=~?~A~C~E~F~H~I~K~M~M~N~P~µR~ɻU~Z~_~d~f~e~e~c~a~_~_~a~d~f~w4w4w4x4x4x3v2c~c~c~e~e~g~g~f~f~d~a~a~`~`~^~_~`~b~d~h~k~m~m~m~n~o~o~q~q~r~r~t~v~x~z~|~~~|~|~y~w~u~q~n~n~k~i~g~f~f~e~f~h~j~l~l~m~m~m~ÿk~þl~Ŀm~n~¾n~n~n~p~o~k~e~[~۠}}{}o}|Ӿ|÷|¼|||¼|ľ||||||w}}}}}}}}}}}{}y}x}w}u}t}u}x}z}{}|}|}|}{}{}|}}}}}}Ȋ}ە}}}}R~V~[~]~]~]~]~^~_~`~d~h~ƽl~m~q~v~{~}~~~{D}EGG~F}E|C~~|~v~r~r~s~q~o~o~p~p~k~c~[~X~U~Q~J~}~}y}r}·k}e}c}b}`}]}\}^}ƹc}h}o}w}z@~B~C~F~H~J~K~J~F~{B~~}{}x}v}t}v}x}z}x}v}q}o}m}ʿk}k}o}v}}}}~F~I~J~J~H~F~zC~}|}u}Ǿo}k}g}c}_}[}{Z}|{]}b}k}w}}}}y}k}]}|̍|û|û|͒||\}e}j}j}f}_}||ߛ|||S}Z}c}k}o}źo}Ƚp}ʿo}p}q}q}r}s}t}s}q}r}p}s}{;~?~D~L~T~Z~`~g~r~|~CFIJG}An~X~E~p}Ȼ`}]}¸a}ƽd}ƽe}úc}^}[}^}h}{>~L~`~y~KW`ehmrv{ABA=p~i~\~K~D~?~s}k}r}}|D~I~R~]~b~¸d~Ƽe~żd~`~[~V~T~S~U~V~U~U~V~W~U~b~a~a~^~\~ŻX~U~T~U~T~T~U~V~W~X~Z~·Z~·Z~Y~Y~Y~·Y~·X~·W~øV~ȻW~Z~\~_~_~_~]~]~_~_~]~]~`~b~e~g~i~j~l~l~n~y7z8z8z8{8z8z8y7o~n~n~x7y8y7n~l~l~l~j~i~g~e~d~b~_~_~]~\~Z~ƹW~T~P~L~J~H~H~I~K~N~Q~S~U~U~T~R~P~N~L~K~J~J~J~I~G~G~F~F~E~}E~}}}}}}z}x}u}s}r}r}r}s}u}w}{}}}ـ}݂}}}}}}}}}}}}}}}}}}}}~}|}|}}}}}}}|}~}}}}zC~{C~|D~~E~F~E~E~D~C~C~D~E~F~H~I~L~M~O~Q~R~R~R~R~T~W~Y~[~[~Z~Y~U~P~K~}D~{}m}a}T}||u|q|r|t|x|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||Ý|ӥ|ߩ|||\}`}c}e}h}j}l}ƾq}y}}}}H~H~J~J~K~K~J~I~I~J~L~O~R~T~U~T~Q~~L~}҂}ýx}l}c}^}]}_}c}f}h}j}j}i}h}f}b}_}\}\}^}a}g}m}u}|}ǿ}ȉ}͋}ˊ}LJ}ſ}z}t}m}h}d}c}d}f}k}r}{}˅}ُ}}~Q~T~W~X~Y~Y~Y~X~V~T~S~Q~N~M~}K~}}}}L~M~P~R~U~X~Y~Z~[~[~Z~Z~Y~X~Z~[~^~`~b~d~c~a~[~}͒}}p}d}|Ѭ|Ƨ||¼|||||||»|Ŝ|Ӣ||{Y}^}d}k}p}s}Ǽt}u}t}w}x}{}~}}}B~C~D~D~C~D~E~G~H~J~L~N~P~R~S~R~Q~P~N~K~}΁}t}g}]}|Ǜ||||||||||||||||||ź|Ǒ|ҕ|ڗ||}R}T}U}V}W}X}Y}Z}Z}\}_}b}c}e}żh}j}k}i}h}h}g}ļd}a}^}^}Z}V}T}R}N}|܎|}|k|c|{߰{˧{ģ{ä{ȫ{ӵ{{p||||Ǭ|ֹ||||||||||||||d}|b}|a}}`}}_}~^}_}c}i}n}t}}M~X~f~v~EOYbjqx}=?@BCDEGHHGFDB?~;pie`][XSOHAs~ƽb~S~F~v}ƻf}[}R}L}||؈|҅|υ|˄|Ʌ|ƅ|Ä|˿|Ǽ|ù|||||||||||||||||||||||||||||||||||||||||||||||||||||||||ž|Ƙ|Λ|מ|||W}[}`}e}l}t}¼{}Á}DŽ}dž}Ň}}}}}u}n}g}|߼|ҷ|ɶ|ŷ|ù|û|½||||||||||||Þ|ܬ|~_}j}t}½}ّ}|Q~[~c~g~g~d~a~`~`~b~c~d~e~g~k~q~u~y~|~|~~~~~|D}EEFIJJJIJLMMLMNOOOONNNMLKJIJKLMMNOPQQPNJGDB?u~m~h~d~`~\~V~N~zF~{}m}d}[}|R}Ԑ|}|q|i|b|z[|{ӟ{ɘ{Ŕ{ȕ{Ϛ{ڢ{{m||֝||_}g}q}}}ȉ}֒}}}yP~R~T~R~}Ԅ}q}`}T}||||~R}Y}a}g}k}o}u}}}ÿ}Ċ}}}{}u}r}p}p}o}m}j}g}c}_}~\}|٢|Ô||}|v|s|r|x||||f}w}}Ț}ۨ}}}~_~`~_~]~]~\~]~_~a~d~g~i~k~k~k~ýl~n~p~u~x~|~~~~~|~z~x~u~q~o~k~i~g~c~a~_~]~]~]~]~\~\~\~\~[~Y~ƼV~R~R~Q~S~X~ɿ]~f~m~u~{~~}~x~q~h~Ĺ_~W~O~K~I~J~M~P~T~X~[~ù]~Ⱦ`~a~a~_~_~_~_~a~d~j~o~q~q~n~j~g~d~ƺa~]~V~P~I~}x}g}Y}|ؔ|Ɗ|ɾ||Ê|Ë|ʐ|ݙ|zR}V}V}U}U}W}\}b}f}k}x}H~U~ɿ]~e~t~?ABBAA?<~:}9q~l~i~g~k~q~t~t~r~t~{;|<t~o~m~m~k~k~h~f~d~Ⱦ[~ŻY~¸X~V~U~R~P~N~M~M~M~N~N~O~P~R~T~U~W~Y~ù\~ɾ_~a~d~f~f~e~e~e~e~e~e~f~f~g~g~g~i~j~j~l~l~x7z8|8}9:::;;;;<<=>?@ACEGILOQRSSSQOKG{C|~r~ƽi~_~V~M~}x}i}^}V}|֔|Ŏ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||Ø|̝|֡|ߤ|||X}[}_}b}f}i}n}ƽs}x}}}}}|F~J~N~Q~V~Z~]~a~c~ļf~j~n~t~z~~~~~~~߆~ۅ~؃~Ҁ~z~ûs~k~c~[~U~}P~}ُ}ˇ}}}|}x}t}p}m}j}h}h}~g}|||ӽ|ɹ|÷||Ŀ|þ|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||ɸ|м|ֿ||||پ|ҹ|˵|ȴ|Ʋ|Ų|ȵ|˸|̹|λ|н|о|п||̾|ɾ|ƽ|ú||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||~|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}|||||||||||¾|||||||||||||}}~}}}}}}}}}}}}}}}}}}}š}Ȝ}˞}͝}Μ}Λ}Κ}Ϙ}ї}є}ѓ}ѐ}Ґ}Ր}ے}ߓ}}܏}֋}̅}|}p}d}X}|Ə||}|z|y|{|}|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||к||l}u}~}}½}͛}ݢ}}}|W~}V~}}}ߒ}؋}Ԇ}ф}Ђ}ӂ}ׅ}އ}}}}}}}K~L~N~P~Q~R~R~R~P~O~~N~}}}}}}}}ݐ}ٍ}׍}Ջ}ԋ}Ї}ʄ}Ā}~}ƽ|}ú{}x}u}r}o}n}m}m}k}i}g}g}f}e}e}e}d}d}b}{a}|ܸ|ϲ|ĭ|ľ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||¶|μ|||o}y}}‘}ݡ}}_~f~l~q~u~y~}~~~~GJLPRTVWXYYXWUSRQONMLJIFECA>=<;}9z8n~l~j~i~g~f~f~e~c~`~_~ƻ\~Y~V~S~P~O~L~K~I~G~D~|B~}|}x}u}p}ɿj}e}`}[}X}T}S}R}R}T}W}\}a}g}m}t}z}}}}}E~G~I~K~M~P~T~Y~¸]~c~f~k~n~p~q~s~s~t~s~p~m~k~i~h~h~f~d~d~d~d~b~b~_~]~Z~X~V~U~ĺR~O~L~L~K~K~M~N~N~O~P~R~S~T~S~R~P~N~L~L~N~P~S~T~T~T~W~Ǿ\~b~d~f~f~d~d~d~f~f~d~b~^~_~^~\~\~Z~Z~Z~Z~Z~Z~Z~\~^~_~a~c~c~e~h~j~l~n~y8z9{9|:|;};}<~<~<~=~=}=}=|={<y;w;t~r~o~k~h~f~a~̾^~Ź\~Y~X~V~T~S~R~Q~P~O~O~O~O~P~P~P~Q~Q~Q~P~P~N~L~~J~yH~}}܅}Ӏ}|}w}s}n}k}h}f}e}c}b}a}a}`}`}a}a}b}b}b}c}d}e}e}f}f}g}g}f}f}f}e}e}e}d}d}c}c}c}~b}~b}}b}}b}}b}|a}|a}|||||||||||{a}{a}|a}}b}c}d}d}e}e}f}f}f}f}f}f}f}f}f}e}e}d}d}d}e}e}e}e}e}f}f}g}h}h}i}i}i}i}i}i}j}j}j}j}j}j}j}k}k}k}l}n}n}o}p}q}q}q}p}·q}÷q}÷p}ĸp}Źp}ǻp}˿r}t}u}w}z}|}}}}yD~|E~}E~F~G~H~H~I~J~K~L~O~P~Q~R~S~T~U~W~X~[~]~_~`~a~a~a~a~`~`~_~_~^~^~^~^~]~\~[~W~T~P~L~~H~}}}܀}}}{}x}v}s}žr}üq}r}q}q}q}q}q}o}o}m}l}k}i}h}g}f}e}e}e}f}g}h}h}i}i}i}h}g}g}f}f}e}d}b}a}a}`}`}`}`}_}_}_}^}^}^}^}]}]}}\}}\}||||||||||||||||||||||||||||||||||ߦ|ۤ|ס|ԟ|М|͚|˘|Ȗ|Ɣ|Ē|‘|ſ|ľ|½|½|½|þ|ſ||Ñ|Ɠ|ʖ|ϙ|Ԝ|٠|ߣ||||}Z}\}^}a}c}e}g}j}k}m}o}q}s}u}w}y}÷{}ƺ}}˾}Â}ǃ}ˆ}ъ}Ռ}܏}}}}}zN~|O~~P~Q~R~T~V~W~Y~Z~]~^~`~a~b~c~c~c~c~b~a~`~]~Z~W~S~P~}}܎}ӊ}ˆ}Ă}½}|}x}u}q}m}h}c}_}||Ӥ|Ȟ|Ŀ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{||~||||||||||||||||||||||~|}|}{|{y|zx|yw|xv|{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{|{|}||~}|}|~|||||||~|}|~}|}{|{{{{{{{{{{{{{{{{{{{{{{{{{~||||}|}|~|~|}|}|||||{|{|{|z|z|y|z|||}|||||||þ|ɠ|ը||||^}`}c}d}f}f}h}h}h}h}h}i}i}i}i}i}i}h}h}g}f}f}f}g}g}h}h}h}h}g}g}g}g}h}i}k}l}n}o}p}p}p}p}p}p}p}o}p}o}o}n}n}m}l}~k}|||׾|̷|ij|þ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||Ʈ|Ӷ|߾||}g}k}p}s}w}x}z}y}y}w}v}s}q}n}k}h}e}c}a}_}]}[}Z}Z}Y}Y}Y}Y}\}]}_}c}e}j}n}q}u}y}}}ـ}߄}}}}}G~H~J~K~L~M~N~N~N~N~O~P~P~O~O~N~N~M~L~J~H~G~F~E~E~E~E~E~E~E~F~F~G~G~G~F~F~E~E~D~D~D~D~D~E~F~G~G~H~I~L~M~O~Q~S~V~W~Z~\~`~b~f~i~l~n~s~u~x~|=~>>>=~<|:s~o~j~h~d~a~_~]~[~[~Y~Y~Y~Y~Y~[~[~^~^~`~b~c~e~g~g~g~i~i~i~i~i~i~i~i~i~i~i~h~f~f~f~d~d~f~f~d~d~d~d~c~b~a~a~a~a~c~c~c~c~c~a~i}j}l}l}n}n}o}q}q}t}t}w}y}z}|}{?~}@~B~D~F~G~J~L~N~P~R~U~W~Z~\~ù^~Ⱦa~c~e~g~j~l~o~q~t~v~z~y>|?ABCDEFHIJKKLNOOPQQSSTUUVVVWXXXXYYYYYYYYXXXXWWWVUUTTSRRRQQQPPPOONNNMMMMMLLLLLLLLKLLKKLKKKKLMMMMMMNNNOOPPQRRSSTTTTUUUVVVVVVVVVVWWWXXXXWWWWWVVUTTSSSRRQQQPPPPPPOOONMMMLLKKJIHHHGGGGFFFEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDCCCCAA@@~?~~|~{~y~w~t~t~q~o~m~h~f~d~¾`~^~Z~X~T~R~O~L~I~G~D~B~|?~{}v}q}l}g}ýb}]}Y}T}P}L}I}E}|~|y|v|r|o|ʿl|ĺi|h|f|e|d|d|c|c|c|c|c|d|d|e|e|e|f|f|g|h|i|j|k|k|l|m|n|o|p|q|r|s|s|t|v|v|w|x|x|y|y|y|y|z|z|z|z|z|{|{|||||}|~|||||·|·|¸|ø|ø|ø|ù|ù|Ĺ|Ĺ|Ĺ|ĺ|ĺ|ĺ|ĺ|Ż|Ż|Ż|Ż|Ƽ|Ż|Ƽ|Ƽ|Ż|Ż|ĺ|ĺ|ĺ|ù|ù|ù|¹|¹|¹|¹|¹|¹|¹|ù|¹|¹|¹|ú|º|ú|ú|ú|º|º|ú|¹|º|ú|ú|º|º|º|º|º||||||||||||||||||||||||||||||~|}|}|||{|{|z|z|y|y|x|x|w|v|v|u|u|t|s|r|q|p|o|n|m|l|~k|}j||i|{h|{g|{{{{{{{{{{{{{{{{{{{{{{{{{{{ߴ{߲{߲{ݰ{ݰ{ܮ{ۮ{ۮ{۬{۬{۬{۬{ܬ{ݬ{ݬ{ݬ{ݭ{߭{߭{߭{߭{߭{߭{߭{߭{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{Z|}\|~]|^|_|a|b|d|e|g|i|k|m|p|s|v|y|{|~|||||ƾ|Ó|Ȗ|͚|Ҟ|ס|ݦ||||||\}^}a}b}d}f}h}j}l}n}o}p}r}t}u}v}x}y}{}}}~}}}}ž}}ć}Ɖ}ɋ}ˌ}΍}ю}Ӑ}ג}ْ}ܕ}}}}}}}}{Q~}R~T~U~W~X~X~Y~Z~[~\~\~]~]~]~]~]~]~^~^~^~^~^~^~^~`~`~`~a~a~b~b~c~d~d~d~e~e~e~e~e~f~g~g~g~g~g~h~h~h~i~i~i~j~j~j~k~k~k~k~k~k~k~k~»l~¼l~ül~Ľl~Ľm~žm~ƾm~ƾn~ƿn~ǿn~n~n~n~ǿn~ƿn~ƾn~žn~Žn~ļm~»l~l~k~j~j~i~i~h~h~g~f~f~e~e~e~e~d~d~c~b~`~^~]~\~Z~X~V~U~S~Q~P~O~N~M~}L~|K~}}}}}}}}}}}߉}݉}ه}օ}҂}Ђ}̀}}~}}}|}{}z}z}ſy}Ŀy}ýy}½x}x}w}w}v}u}t}t}s}s}r}r}q}q}p}p}o}n}n}m}m}m}l}l}l}l}l}l}k}k}k}k}k}j}j}j}i}i}i}i}i}i}i}i}i}i}i}i}i}i}j}j}j}j}i}i}i}h}h}h}g}g}g}g}f}f}f}e}e}e}d}d}d}c}c}b}a}`}`}`}_}_}^}^}]}]}\}\}\}[}[}Z}Z}Y}~X}~W}}W}|V}||||||||||||||||ߟ|ޝ|ݝ|ݝ|ۛ|ۛ|ٛ|ٛ|ٙ|י|י|י|י|՗|՗|՗|ԗ|ӗ|Ӗ|Ӗ|Ӗ|Җ|ҕ|ҕ|ҕ|ҕ|ҕ|ҕ|ӕ|ӕ|ӕ|ӕ|ԕ|Օ|Օ|֕|֕|ؖ|ؖ|ؖ|ؖ|ٖ|ږ|ۖ|ۖ|ۖ|ۖ|ܖ|ݖ|ݖ|ݖ|ݖ|ޖ|ߗ||||||||||||||||||||||||||{O}{O}|P}}P}}P}~P}~P}~P}~P}P}P}Q}S}S}T}U}W}X}Y}Z}[}\}\}\}[}[}[}[}]}]}^}_}a}b}d}e}ļf}ȿi} \ No newline at end of file diff --git a/BCnEncTests/testImages/test_hdr_probe.hdr b/BCnEncTests/testImages/encoded/hdr_2_rgbe.hdr similarity index 100% rename from BCnEncTests/testImages/test_hdr_probe.hdr rename to BCnEncTests/testImages/encoded/hdr_2_rgbe.hdr diff --git a/BCnEncTests/testImages/encoded/pack16_r4g4b4a4_unorm.ktx b/BCnEncTests/testImages/encoded/pack16_r4g4b4a4_unorm.ktx new file mode 100644 index 0000000..084e2f9 Binary files /dev/null and b/BCnEncTests/testImages/encoded/pack16_r4g4b4a4_unorm.ktx differ diff --git a/BCnEncTests/testImages/encoded/pack16_r5g5b5a1_unorm.ktx b/BCnEncTests/testImages/encoded/pack16_r5g5b5a1_unorm.ktx new file mode 100644 index 0000000..ca8f09c Binary files /dev/null and b/BCnEncTests/testImages/encoded/pack16_r5g5b5a1_unorm.ktx differ diff --git a/BCnEncTests/testImages/encoded/pack16_r5g6b5_unorm.ktx b/BCnEncTests/testImages/encoded/pack16_r5g6b5_unorm.ktx new file mode 100644 index 0000000..3d7594f Binary files /dev/null and b/BCnEncTests/testImages/encoded/pack16_r5g6b5_unorm.ktx differ diff --git a/BCnEncTests/testImages/encoded/raw_r16g16b16_sfloat.ktx b/BCnEncTests/testImages/encoded/raw_r16g16b16_sfloat.ktx new file mode 100644 index 0000000..4d5d815 Binary files /dev/null and b/BCnEncTests/testImages/encoded/raw_r16g16b16_sfloat.ktx differ diff --git a/BCnEncTests/testImages/encoded/raw_r8_unorm.ktx b/BCnEncTests/testImages/encoded/raw_r8_unorm.ktx new file mode 100644 index 0000000..7e97b71 Binary files /dev/null and b/BCnEncTests/testImages/encoded/raw_r8_unorm.ktx differ diff --git a/BCnEncTests/testImages/encoded/raw_r8g8_unorm.ktx b/BCnEncTests/testImages/encoded/raw_r8g8_unorm.ktx new file mode 100644 index 0000000..0ae377d Binary files /dev/null and b/BCnEncTests/testImages/encoded/raw_r8g8_unorm.ktx differ diff --git a/BCnEncTests/testImages/encoded/raw_r8g8b8_srgb.ktx b/BCnEncTests/testImages/encoded/raw_r8g8b8_srgb.ktx new file mode 100644 index 0000000..c1c7881 Binary files /dev/null and b/BCnEncTests/testImages/encoded/raw_r8g8b8_srgb.ktx differ diff --git a/BCnEncTests/testImages/encoded/raw_r8g8b8_unorm.ktx b/BCnEncTests/testImages/encoded/raw_r8g8b8_unorm.ktx new file mode 100644 index 0000000..080ed7f Binary files /dev/null and b/BCnEncTests/testImages/encoded/raw_r8g8b8_unorm.ktx differ diff --git a/BCnEncTests/testImages/encoded/raw_r8g8b8a8_srgb.ktx b/BCnEncTests/testImages/encoded/raw_r8g8b8a8_srgb.ktx new file mode 100644 index 0000000..7635b90 Binary files /dev/null and b/BCnEncTests/testImages/encoded/raw_r8g8b8a8_srgb.ktx differ diff --git a/BCnEncTests/testImages/encoded/raw_r8g8b8a8_unorm.ktx b/BCnEncTests/testImages/encoded/raw_r8g8b8a8_unorm.ktx new file mode 100644 index 0000000..149009f Binary files /dev/null and b/BCnEncTests/testImages/encoded/raw_r8g8b8a8_unorm.ktx differ diff --git a/BCnEncTests/testImages/raw.png b/BCnEncTests/testImages/raw.png new file mode 100644 index 0000000..fe0031d Binary files /dev/null and b/BCnEncTests/testImages/raw.png differ diff --git a/BCnEncTests/testImages/raw/alpha_1.png b/BCnEncTests/testImages/raw/alpha_1.png new file mode 100644 index 0000000..76418b7 Binary files /dev/null and b/BCnEncTests/testImages/raw/alpha_1.png differ diff --git a/BCnEncTests/testImages/test_alpha_1_512.png b/BCnEncTests/testImages/raw/alpha_2.png similarity index 100% rename from BCnEncTests/testImages/test_alpha_1_512.png rename to BCnEncTests/testImages/raw/alpha_2.png diff --git a/BCnEncTests/testImages/test_alphagradient_1_512.png b/BCnEncTests/testImages/raw/alpha_3.png similarity index 100% rename from BCnEncTests/testImages/test_alphagradient_1_512.png rename to BCnEncTests/testImages/raw/alpha_3.png diff --git a/BCnEncTests/testImages/raw/alpha_4.png b/BCnEncTests/testImages/raw/alpha_4.png new file mode 100644 index 0000000..7f5396f Binary files /dev/null and b/BCnEncTests/testImages/raw/alpha_4.png differ diff --git a/BCnEncTests/testImages/test_transparent.png b/BCnEncTests/testImages/raw/alpha_5.png similarity index 100% rename from BCnEncTests/testImages/test_transparent.png rename to BCnEncTests/testImages/raw/alpha_5.png diff --git a/BCnEncTests/testImages/raw/bc7_subsets2.png b/BCnEncTests/testImages/raw/bc7_subsets2.png new file mode 100644 index 0000000..19ea44f Binary files /dev/null and b/BCnEncTests/testImages/raw/bc7_subsets2.png differ diff --git a/BCnEncTests/testImages/raw/bc7_subsets3.png b/BCnEncTests/testImages/raw/bc7_subsets3.png new file mode 100644 index 0000000..6dfeb27 Binary files /dev/null and b/BCnEncTests/testImages/raw/bc7_subsets3.png differ diff --git a/BCnEncTests/testImages/raw/blocks.png b/BCnEncTests/testImages/raw/blocks.png new file mode 100644 index 0000000..fe0031d Binary files /dev/null and b/BCnEncTests/testImages/raw/blocks.png differ diff --git a/BCnEncTests/testImages/raw/diffuse_1.png b/BCnEncTests/testImages/raw/diffuse_1.png new file mode 100644 index 0000000..72048e1 Binary files /dev/null and b/BCnEncTests/testImages/raw/diffuse_1.png differ diff --git a/BCnEncTests/testImages/raw/diffuse_2.png b/BCnEncTests/testImages/raw/diffuse_2.png new file mode 100644 index 0000000..09bf1a3 Binary files /dev/null and b/BCnEncTests/testImages/raw/diffuse_2.png differ diff --git a/BCnEncTests/testImages/test_rgb_hard_1.png b/BCnEncTests/testImages/raw/diffuse_3.png similarity index 100% rename from BCnEncTests/testImages/test_rgb_hard_1.png rename to BCnEncTests/testImages/raw/diffuse_3.png diff --git a/BCnEncTests/testImages/raw/diffuse_4.png b/BCnEncTests/testImages/raw/diffuse_4.png new file mode 100644 index 0000000..bcf320d Binary files /dev/null and b/BCnEncTests/testImages/raw/diffuse_4.png differ diff --git a/BCnEncTests/testImages/raw/diffuse_5.png b/BCnEncTests/testImages/raw/diffuse_5.png new file mode 100644 index 0000000..3bc1b6f Binary files /dev/null and b/BCnEncTests/testImages/raw/diffuse_5.png differ diff --git a/BCnEncTests/testImages/raw/diffuse_6.png b/BCnEncTests/testImages/raw/diffuse_6.png new file mode 100644 index 0000000..ab09ae1 Binary files /dev/null and b/BCnEncTests/testImages/raw/diffuse_6.png differ diff --git a/BCnEncTests/testImages/raw/height_1.png b/BCnEncTests/testImages/raw/height_1.png new file mode 100644 index 0000000..8e8e9c5 Binary files /dev/null and b/BCnEncTests/testImages/raw/height_1.png differ diff --git a/BCnEncTests/testImages/raw/normal_1.png b/BCnEncTests/testImages/raw/normal_1.png new file mode 100644 index 0000000..9de1c0e Binary files /dev/null and b/BCnEncTests/testImages/raw/normal_1.png differ diff --git a/BCnEncTests/testImages/test_red_green_1_64.png b/BCnEncTests/testImages/raw/rg_1.png similarity index 100% rename from BCnEncTests/testImages/test_red_green_1_64.png rename to BCnEncTests/testImages/raw/rg_1.png diff --git a/BCnEncTests/testImages/raw/rgb_hard.png b/BCnEncTests/testImages/raw/rgb_hard.png new file mode 100644 index 0000000..4d8fe37 Binary files /dev/null and b/BCnEncTests/testImages/raw/rgb_hard.png differ diff --git a/BCnEncTests/testImages/reference/alpha_1_bc1a.png b/BCnEncTests/testImages/reference/alpha_1_bc1a.png new file mode 100644 index 0000000..c48c1ef Binary files /dev/null and b/BCnEncTests/testImages/reference/alpha_1_bc1a.png differ diff --git a/BCnEncTests/testImages/reference/alpha_1_bc2.png b/BCnEncTests/testImages/reference/alpha_1_bc2.png new file mode 100644 index 0000000..8a6397a Binary files /dev/null and b/BCnEncTests/testImages/reference/alpha_1_bc2.png differ diff --git a/BCnEncTests/testImages/reference/alpha_1_bc3.png b/BCnEncTests/testImages/reference/alpha_1_bc3.png new file mode 100644 index 0000000..a64c732 Binary files /dev/null and b/BCnEncTests/testImages/reference/alpha_1_bc3.png differ diff --git a/BCnEncTests/testImages/reference/alpha_1_bc7.png b/BCnEncTests/testImages/reference/alpha_1_bc7.png new file mode 100644 index 0000000..3661ac3 Binary files /dev/null and b/BCnEncTests/testImages/reference/alpha_1_bc7.png differ diff --git a/BCnEncTests/testImages/reference/alpha_1_bgra.png b/BCnEncTests/testImages/reference/alpha_1_bgra.png new file mode 100644 index 0000000..76418b7 Binary files /dev/null and b/BCnEncTests/testImages/reference/alpha_1_bgra.png differ diff --git a/BCnEncTests/testImages/reference/alpha_2_bc1a.png b/BCnEncTests/testImages/reference/alpha_2_bc1a.png new file mode 100644 index 0000000..5278c1b Binary files /dev/null and b/BCnEncTests/testImages/reference/alpha_2_bc1a.png differ diff --git a/BCnEncTests/testImages/reference/alpha_2_bc2.png b/BCnEncTests/testImages/reference/alpha_2_bc2.png new file mode 100644 index 0000000..ac4b35a Binary files /dev/null and b/BCnEncTests/testImages/reference/alpha_2_bc2.png differ diff --git a/BCnEncTests/testImages/reference/alpha_2_bc3.png b/BCnEncTests/testImages/reference/alpha_2_bc3.png new file mode 100644 index 0000000..aa6afa3 Binary files /dev/null and b/BCnEncTests/testImages/reference/alpha_2_bc3.png differ diff --git a/BCnEncTests/testImages/reference/alpha_2_bc7.png b/BCnEncTests/testImages/reference/alpha_2_bc7.png new file mode 100644 index 0000000..fb28a5e Binary files /dev/null and b/BCnEncTests/testImages/reference/alpha_2_bc7.png differ diff --git a/BCnEncTests/testImages/reference/alpha_2_rgba.png b/BCnEncTests/testImages/reference/alpha_2_rgba.png new file mode 100644 index 0000000..b39635e Binary files /dev/null and b/BCnEncTests/testImages/reference/alpha_2_rgba.png differ diff --git a/BCnEncTests/testImages/reference/astc_12x12_unorm.png b/BCnEncTests/testImages/reference/astc_12x12_unorm.png new file mode 100644 index 0000000..dde607e Binary files /dev/null and b/BCnEncTests/testImages/reference/astc_12x12_unorm.png differ diff --git a/BCnEncTests/testImages/reference/astc_4x4_srgb.png b/BCnEncTests/testImages/reference/astc_4x4_srgb.png new file mode 100644 index 0000000..6e3f318 Binary files /dev/null and b/BCnEncTests/testImages/reference/astc_4x4_srgb.png differ diff --git a/BCnEncTests/testImages/reference/astc_8x5_unorm.png b/BCnEncTests/testImages/reference/astc_8x5_unorm.png new file mode 100644 index 0000000..f5db67a Binary files /dev/null and b/BCnEncTests/testImages/reference/astc_8x5_unorm.png differ diff --git a/BCnEncTests/testImages/reference/bc1_unorm.png b/BCnEncTests/testImages/reference/bc1_unorm.png new file mode 100644 index 0000000..34462e7 Binary files /dev/null and b/BCnEncTests/testImages/reference/bc1_unorm.png differ diff --git a/BCnEncTests/testImages/reference/bc1a_unorm.png b/BCnEncTests/testImages/reference/bc1a_unorm.png new file mode 100644 index 0000000..0c2ce3a Binary files /dev/null and b/BCnEncTests/testImages/reference/bc1a_unorm.png differ diff --git a/BCnEncTests/testImages/reference/bc2_unorm.png b/BCnEncTests/testImages/reference/bc2_unorm.png new file mode 100644 index 0000000..66eb7c4 Binary files /dev/null and b/BCnEncTests/testImages/reference/bc2_unorm.png differ diff --git a/BCnEncTests/testImages/reference/bc3_unorm.png b/BCnEncTests/testImages/reference/bc3_unorm.png new file mode 100644 index 0000000..47a6e5a Binary files /dev/null and b/BCnEncTests/testImages/reference/bc3_unorm.png differ diff --git a/BCnEncTests/testImages/test_decompress_bc4_unorm_reference.bmp b/BCnEncTests/testImages/reference/bc4_unorm.bmp similarity index 100% rename from BCnEncTests/testImages/test_decompress_bc4_unorm_reference.bmp rename to BCnEncTests/testImages/reference/bc4_unorm.bmp diff --git a/BCnEncTests/testImages/reference/bc4_unorm.png b/BCnEncTests/testImages/reference/bc4_unorm.png new file mode 100644 index 0000000..222012b Binary files /dev/null and b/BCnEncTests/testImages/reference/bc4_unorm.png differ diff --git a/BCnEncTests/testImages/reference/bc5_unorm.png b/BCnEncTests/testImages/reference/bc5_unorm.png new file mode 100644 index 0000000..493433c Binary files /dev/null and b/BCnEncTests/testImages/reference/bc5_unorm.png differ diff --git a/BCnEncTests/testImages/reference/bc6h_ufloat.png b/BCnEncTests/testImages/reference/bc6h_ufloat.png new file mode 100644 index 0000000..a7c8b95 Binary files /dev/null and b/BCnEncTests/testImages/reference/bc6h_ufloat.png differ diff --git a/BCnEncTests/testImages/reference/bc7_unorm.png b/BCnEncTests/testImages/reference/bc7_unorm.png new file mode 100644 index 0000000..1233761 Binary files /dev/null and b/BCnEncTests/testImages/reference/bc7_unorm.png differ diff --git a/BCnEncTests/testImages/reference/bc7_unorm_alltypes.png b/BCnEncTests/testImages/reference/bc7_unorm_alltypes.png new file mode 100644 index 0000000..c12bd49 Binary files /dev/null and b/BCnEncTests/testImages/reference/bc7_unorm_alltypes.png differ diff --git a/BCnEncTests/testImages/reference/eac_r_unorm.png b/BCnEncTests/testImages/reference/eac_r_unorm.png new file mode 100644 index 0000000..00bb0a4 Binary files /dev/null and b/BCnEncTests/testImages/reference/eac_r_unorm.png differ diff --git a/BCnEncTests/testImages/reference/eac_rg_unorm.png b/BCnEncTests/testImages/reference/eac_rg_unorm.png new file mode 100644 index 0000000..dbeeae8 Binary files /dev/null and b/BCnEncTests/testImages/reference/eac_rg_unorm.png differ diff --git a/BCnEncTests/testImages/reference/etc1_unorm.png b/BCnEncTests/testImages/reference/etc1_unorm.png new file mode 100644 index 0000000..d96fd26 Binary files /dev/null and b/BCnEncTests/testImages/reference/etc1_unorm.png differ diff --git a/BCnEncTests/testImages/reference/etc2_r8g8b8_srgb.png b/BCnEncTests/testImages/reference/etc2_r8g8b8_srgb.png new file mode 100644 index 0000000..47d7111 Binary files /dev/null and b/BCnEncTests/testImages/reference/etc2_r8g8b8_srgb.png differ diff --git a/BCnEncTests/testImages/reference/etc2_r8g8b8a1_unorm.png b/BCnEncTests/testImages/reference/etc2_r8g8b8a1_unorm.png new file mode 100644 index 0000000..f410f3e Binary files /dev/null and b/BCnEncTests/testImages/reference/etc2_r8g8b8a1_unorm.png differ diff --git a/BCnEncTests/testImages/reference/etc2_r8g8b8a8_unorm.png b/BCnEncTests/testImages/reference/etc2_r8g8b8a8_unorm.png new file mode 100644 index 0000000..1e3522d Binary files /dev/null and b/BCnEncTests/testImages/reference/etc2_r8g8b8a8_unorm.png differ diff --git a/BCnEncTests/testImages/reference/hdr_1_rgbe.png b/BCnEncTests/testImages/reference/hdr_1_rgbe.png new file mode 100644 index 0000000..cab9a5a Binary files /dev/null and b/BCnEncTests/testImages/reference/hdr_1_rgbe.png differ diff --git a/BCnEncTests/testImages/reference/hdr_1_rgbe_linear.png b/BCnEncTests/testImages/reference/hdr_1_rgbe_linear.png new file mode 100644 index 0000000..9ca66e3 Binary files /dev/null and b/BCnEncTests/testImages/reference/hdr_1_rgbe_linear.png differ diff --git a/BCnEncTests/testImages/reference/hdr_1_xyze.png b/BCnEncTests/testImages/reference/hdr_1_xyze.png new file mode 100644 index 0000000..e3cd0e8 Binary files /dev/null and b/BCnEncTests/testImages/reference/hdr_1_xyze.png differ diff --git a/BCnEncTests/testImages/reference/hdr_2_rgbe.png b/BCnEncTests/testImages/reference/hdr_2_rgbe.png new file mode 100644 index 0000000..936bd44 Binary files /dev/null and b/BCnEncTests/testImages/reference/hdr_2_rgbe.png differ diff --git a/BCnEncTests/testImages/reference/raw_r16g16b16_sfloat.png b/BCnEncTests/testImages/reference/raw_r16g16b16_sfloat.png new file mode 100644 index 0000000..80bd61e Binary files /dev/null and b/BCnEncTests/testImages/reference/raw_r16g16b16_sfloat.png differ diff --git a/BCnEncTests/testImages/reference/raw_r8_unorm.png b/BCnEncTests/testImages/reference/raw_r8_unorm.png new file mode 100644 index 0000000..de749ba Binary files /dev/null and b/BCnEncTests/testImages/reference/raw_r8_unorm.png differ diff --git a/BCnEncTests/testImages/reference/raw_r8g8_unorm.png b/BCnEncTests/testImages/reference/raw_r8g8_unorm.png new file mode 100644 index 0000000..108412c Binary files /dev/null and b/BCnEncTests/testImages/reference/raw_r8g8_unorm.png differ diff --git a/BCnEncTests/testImages/reference/raw_r8g8b8_unorm.png b/BCnEncTests/testImages/reference/raw_r8g8b8_unorm.png new file mode 100644 index 0000000..a098847 Binary files /dev/null and b/BCnEncTests/testImages/reference/raw_r8g8b8_unorm.png differ diff --git a/BCnEncTests/testImages/reference/raw_r8g8b8a8_unorm.png b/BCnEncTests/testImages/reference/raw_r8g8b8a8_unorm.png new file mode 100644 index 0000000..f1e578a Binary files /dev/null and b/BCnEncTests/testImages/reference/raw_r8g8b8a8_unorm.png differ diff --git a/BCnEncTests/testImages/test_blur_1_512.jpg b/BCnEncTests/testImages/test_blur_1_512.jpg deleted file mode 100644 index c935a90..0000000 Binary files a/BCnEncTests/testImages/test_blur_1_512.jpg and /dev/null differ diff --git a/BCnEncTests/testImages/test_decompress_bc1.dds b/BCnEncTests/testImages/test_decompress_bc1.dds deleted file mode 100644 index f534406..0000000 Binary files a/BCnEncTests/testImages/test_decompress_bc1.dds and /dev/null differ diff --git a/BCnEncTests/testImages/test_decompress_bc1.ktx b/BCnEncTests/testImages/test_decompress_bc1.ktx deleted file mode 100644 index 151b2ba..0000000 Binary files a/BCnEncTests/testImages/test_decompress_bc1.ktx and /dev/null differ diff --git a/BCnEncTests/testImages/test_decompress_bc1a.dds b/BCnEncTests/testImages/test_decompress_bc1a.dds deleted file mode 100644 index 8a00fec..0000000 Binary files a/BCnEncTests/testImages/test_decompress_bc1a.dds and /dev/null differ diff --git a/BCnEncTests/testImages/test_decompress_bc1a.ktx b/BCnEncTests/testImages/test_decompress_bc1a.ktx deleted file mode 100644 index 1116980..0000000 Binary files a/BCnEncTests/testImages/test_decompress_bc1a.ktx and /dev/null differ diff --git a/BCnEncTests/testImages/test_decompress_bc2.ktx b/BCnEncTests/testImages/test_decompress_bc2.ktx deleted file mode 100644 index c0b8cd4..0000000 Binary files a/BCnEncTests/testImages/test_decompress_bc2.ktx and /dev/null differ diff --git a/BCnEncTests/testImages/test_decompress_bc3.ktx b/BCnEncTests/testImages/test_decompress_bc3.ktx deleted file mode 100644 index 070cdc3..0000000 Binary files a/BCnEncTests/testImages/test_decompress_bc3.ktx and /dev/null differ diff --git a/BCnEncTests/testImages/test_decompress_bc5_unorm_reference.bmp b/BCnEncTests/testImages/test_decompress_bc5_unorm_reference.bmp deleted file mode 100644 index 80a0328..0000000 Binary files a/BCnEncTests/testImages/test_decompress_bc5_unorm_reference.bmp and /dev/null differ diff --git a/BCnEncTests/testImages/test_decompress_bc7.dds b/BCnEncTests/testImages/test_decompress_bc7.dds deleted file mode 100644 index c7bed3e..0000000 Binary files a/BCnEncTests/testImages/test_decompress_bc7.dds and /dev/null differ diff --git a/BCnEncTests/testImages/test_decompress_bc7_rgb.ktx b/BCnEncTests/testImages/test_decompress_bc7_rgb.ktx deleted file mode 100644 index 0c11e7b..0000000 Binary files a/BCnEncTests/testImages/test_decompress_bc7_rgb.ktx and /dev/null differ diff --git a/BCnEncTests/testImages/test_decompress_rgba.dds b/BCnEncTests/testImages/test_decompress_rgba.dds deleted file mode 100644 index a211998..0000000 Binary files a/BCnEncTests/testImages/test_decompress_rgba.dds and /dev/null differ diff --git a/BCnEncTests/testImages/test_diffuse_1_512.jpg b/BCnEncTests/testImages/test_diffuse_1_512.jpg deleted file mode 100644 index 3a9ccf6..0000000 Binary files a/BCnEncTests/testImages/test_diffuse_1_512.jpg and /dev/null differ diff --git a/BCnEncTests/testImages/test_gradient_1_512.jpg b/BCnEncTests/testImages/test_gradient_1_512.jpg deleted file mode 100644 index fa77f52..0000000 Binary files a/BCnEncTests/testImages/test_gradient_1_512.jpg and /dev/null differ diff --git a/BCnEncTests/testImages/test_hdr_kiara.png b/BCnEncTests/testImages/test_hdr_kiara.png deleted file mode 100644 index 2f4264d..0000000 Binary files a/BCnEncTests/testImages/test_hdr_kiara.png and /dev/null differ diff --git a/BCnEncTests/testImages/test_hdr_kiara_bc6h_ktx.ktx b/BCnEncTests/testImages/test_hdr_kiara_bc6h_ktx.ktx deleted file mode 100644 index 646bcf1..0000000 Binary files a/BCnEncTests/testImages/test_hdr_kiara_bc6h_ktx.ktx and /dev/null differ diff --git a/BCnEncTests/testImages/test_height_1_512.jpg b/BCnEncTests/testImages/test_height_1_512.jpg deleted file mode 100644 index 8add891..0000000 Binary files a/BCnEncTests/testImages/test_height_1_512.jpg and /dev/null differ diff --git a/BCnEncTests/testImages/test_lenna_512.png b/BCnEncTests/testImages/test_lenna_512.png deleted file mode 100644 index de40188..0000000 Binary files a/BCnEncTests/testImages/test_lenna_512.png and /dev/null differ diff --git a/BCnEncTests/testImages/test_normal_1_512.jpg b/BCnEncTests/testImages/test_normal_1_512.jpg deleted file mode 100644 index fb51eb5..0000000 Binary files a/BCnEncTests/testImages/test_normal_1_512.jpg and /dev/null differ diff --git a/BCnEncoder.NET.ImageSharp/BCnDecoderExtensions.cs b/BCnEncoder.NET.ImageSharp/BCnDecoderExtensions.cs deleted file mode 100644 index 7834220..0000000 --- a/BCnEncoder.NET.ImageSharp/BCnDecoderExtensions.cs +++ /dev/null @@ -1,264 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using BCnEncoder.Decoder; -using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; -using Microsoft.Toolkit.HighPerformance; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; - -namespace BCnEncoder.ImageSharp -{ - public static class BCnDecoderExtensions - { - - /// - /// Decode a single encoded image from raw bytes. - /// This method will read the expected amount of bytes from the given input stream and decode it. - /// Make sure there is no file header information left in the stream before the encoded data. - /// - /// The stream containing the encoded image. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The format the encoded data is in. - /// The decoded Rgba32 image. - public static Image DecodeRawToImageRgba32(this BcDecoder decoder, Stream inputStream, int pixelWidth, int pixelHeight, CompressionFormat format) - { - return ColorMemoryToImage(decoder.DecodeRaw2D(inputStream, pixelWidth, pixelHeight, format)); - } - - /// - /// Decode a single encoded image from raw bytes. - /// - /// The array containing the encoded data. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The format the encoded data is in. - /// The decoded Rgba32 image. - public static Image DecodeRawToImageRgba32(this BcDecoder decoder, byte[] input, int pixelWidth, int pixelHeight, CompressionFormat format) - { - return ColorMemoryToImage(decoder.DecodeRaw2D(input, pixelWidth, pixelHeight, format)); - } - - /// - /// Read a Ktx or Dds file from a stream and decode the main image from it. - /// The type of file will be detected automatically. - /// - /// The stream that contains either ktx or dds file. - /// The decoded Rgba32 image. - public static Image DecodeToImageRgba32(this BcDecoder decoder, Stream inputStream) - { - return ColorMemoryToImage(decoder.Decode2D(inputStream)); - } - - /// - /// Read a Ktx or Dds file from a stream and decode all available mipmaps from it. - /// The type of file will be detected automatically. - /// - /// The stream that contains either ktx or dds file. - /// An array of decoded Rgba32 images. - public static Image[] DecodeAllMipMapsToImageRgba32(this BcDecoder decoder, Stream inputStream) - { - var decoded = decoder.DecodeAllMipMaps2D(inputStream); - var output = new Image[decoded.Length]; - for (var i = 0; i < decoded.Length; i++) - { - output[i] = ColorMemoryToImage(decoded[i]); - } - return output; - } - - - /// - /// Decode the main image from a Ktx file. - /// - /// The loaded Ktx file. - /// The decoded Rgba32 image. - public static Image DecodeToImageRgba32(this BcDecoder decoder, KtxFile file) - { - return ColorMemoryToImage(decoder.Decode2D(file)); - } - - /// - /// Decode all available mipmaps from a Ktx file. - /// - /// The loaded Ktx file. - /// An array of decoded Rgba32 images. - public static Image[] DecodeAllMipMapsToImageRgba32(this BcDecoder decoder, KtxFile file) - { - var decoded = decoder.DecodeAllMipMaps2D(file); - var output = new Image[decoded.Length]; - for (var i = 0; i < decoded.Length; i++) - { - output[i] = ColorMemoryToImage(decoded[i]); - } - return output; - } - - /// - /// Decode the main image from a Dds file. - /// - /// The loaded Dds file. - /// The decoded Rgba32 image. - public static Image DecodeToImageRgba32(this BcDecoder decoder, DdsFile file) - { - return ColorMemoryToImage(decoder.Decode2D(file)); - } - - /// - /// Decode all available mipmaps from a Dds file. - /// - /// The loaded Dds file. - /// An array of decoded Rgba32 images. - public static Image[] DecodeAllMipMapsToImageRgba32(this BcDecoder decoder, DdsFile file) - { - var decoded = decoder.DecodeAllMipMaps2D(file); - var output = new Image[decoded.Length]; - for (var i = 0; i < decoded.Length; i++) - { - output[i] = ColorMemoryToImage(decoded[i]); - } - return output; - } - - - - /// - /// Decode a single encoded image from raw bytes. - /// This method will read the expected amount of bytes from the given input stream and decode it. - /// Make sure there is no file header information left in the stream before the encoded data. - /// - /// The stream containing the encoded data. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The Format the encoded data is in. - /// The cancellation token for this asynchronous operation. - /// The decoded Rgba32 image. - public static async Task> DecodeRawToImageRgba32Async(this BcDecoder decoder, Stream inputStream, int pixelWidth, int pixelHeight, CompressionFormat format, CancellationToken token = default) - { - return ColorMemoryToImage(await decoder.DecodeRaw2DAsync(inputStream, pixelWidth, pixelHeight, format, token)); - } - - /// - /// Decode a single encoded image from raw bytes. - /// - /// The array containing the encoded data. - /// The pixelWidth of the image. - /// The pixelHeight of the image. - /// The Format the encoded data is in. - /// The cancellation token for this asynchronous operation. - /// The decoded Rgba32 image. - public static async Task> DecodeRawToImageRgba32Async(this BcDecoder decoder, byte[] input, int pixelWidth, int pixelHeight, CompressionFormat format, CancellationToken token = default) - { - return ColorMemoryToImage(await decoder.DecodeRaw2DAsync(input, pixelWidth, pixelHeight, format, token)); - } - - /// - /// Read a Ktx or Dds file from a stream and decode the main image from it. - /// The type of file will be detected automatically. - /// - /// The stream that contains either ktx or dds file. - /// The cancellation token for this asynchronous operation. - /// The decoded Rgba32 image. - public static async Task> DecodeToImageRgba32Async(this BcDecoder decoder, Stream inputStream, CancellationToken token = default) - { - return ColorMemoryToImage(await decoder.Decode2DAsync(inputStream, token)); - } - - /// - /// Read a Ktx or Dds file from a stream and decode all available mipmaps from it. - /// The type of file will be detected automatically. - /// - /// The stream that contains either ktx or dds file. - /// The cancellation token for this asynchronous operation. - /// An array of decoded Rgba32 images. - public static async Task[]> DecodeAllMipMapsToImageRgba32Async(this BcDecoder decoder, Stream inputStream, CancellationToken token = default) - { - var decoded = await decoder.DecodeAllMipMaps2DAsync(inputStream, token); - var output = new Image[decoded.Length]; - for (var i = 0; i < decoded.Length; i++) - { - output[i] = ColorMemoryToImage(decoded[i]); - } - return output; - } - - - /// - /// Decode the main image from a Ktx file. - /// - /// The loaded Ktx file. - /// The cancellation token for this asynchronous operation. - /// The decoded Rgba32 image. - public static async Task> DecodeToImageRgba32Async(this BcDecoder decoder, KtxFile file, CancellationToken token = default) - { - return ColorMemoryToImage(await decoder.Decode2DAsync(file, token)); - } - - /// - /// Decode all available mipmaps from a Ktx file. - /// - /// The loaded Ktx file. - /// The cancellation token for this asynchronous operation. - /// An array of decoded Rgba32 images. - public static async Task[]> DecodeAllMipMapsToImageRgba32Async(this BcDecoder decoder, KtxFile file, CancellationToken token = default) - { - var decoded = await decoder.DecodeAllMipMaps2DAsync(file, token); - var output = new Image[decoded.Length]; - for (var i = 0; i < decoded.Length; i++) - { - output[i] = ColorMemoryToImage(decoded[i]); - } - return output; - } - - /// - /// Decode the main image from a Dds file. - /// - /// The loaded Dds file. - /// The cancellation token for this asynchronous operation. - /// The decoded Rgba32 image. - public static async Task> DecodeToImageRgba32Async(this BcDecoder decoder, DdsFile file, CancellationToken token = default) - { - return ColorMemoryToImage(await decoder.Decode2DAsync(file, token)); - } - - /// - /// Decode all available mipmaps from a Dds file. - /// - /// The loaded Dds file. - /// The cancellation token for this asynchronous operation. - /// An array of decoded Rgba32 images. - public static async Task[]> DecodeAllMipMapsToImageRgba32Async(this BcDecoder decoder, DdsFile file, CancellationToken token = default) - { - var decoded = await decoder.DecodeAllMipMaps2DAsync(file, token); - var output = new Image[decoded.Length]; - for (var i = 0; i < decoded.Length; i++) - { - output[i] = ColorMemoryToImage(decoded[i]); - } - return output; - } - - - - private static Image ColorMemoryToImage(Memory2D colors) - { - var output = new Image(colors.Width, colors.Height); - for (var y = 0; y < colors.Height; y++) - { - var yPixels = output.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y); - var yColors = colors.Span.GetRowSpan(y); - - MemoryMarshal.Cast(yColors).CopyTo(yPixels); - } - return output; - } - } -} diff --git a/BCnEncoder.NET.ImageSharp/BCnEncoder.NET.ImageSharp.csproj b/BCnEncoder.NET.ImageSharp/BCnEncoder.NET.ImageSharp.csproj index 59e9a51..b7822fa 100644 --- a/BCnEncoder.NET.ImageSharp/BCnEncoder.NET.ImageSharp.csproj +++ b/BCnEncoder.NET.ImageSharp/BCnEncoder.NET.ImageSharp.csproj @@ -1,13 +1,14 @@ - + - netstandard2.1 + net9.0 true true snupkg true bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml + BCnEncoder.ImageSharp BCnEncoder.Net.ImageSharp BCnEncoder.Net.ImageSharp @@ -22,12 +23,12 @@ - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + diff --git a/BCnEncoder.NET.ImageSharp/BCnEncoderExtensions.cs b/BCnEncoder.NET.ImageSharp/BCnEncoderExtensions.cs deleted file mode 100644 index 39d215e..0000000 --- a/BCnEncoder.NET.ImageSharp/BCnEncoderExtensions.cs +++ /dev/null @@ -1,321 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using BCnEncoder.Encoder; -using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; -using Microsoft.Toolkit.HighPerformance; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; - -namespace BCnEncoder.ImageSharp -{ - public static class BCnEncoderExtensions - { - /// - /// Encodes all mipmap levels into a ktx or a dds file and writes it to the output stream. - /// - /// The image to encode. - /// The stream to write the encoded image to. - public static void EncodeToStream(this BcEncoder encoder, Image inputImage, Stream outputStream) - { - encoder.EncodeToStream(ImageToMemory2D(inputImage), outputStream); - } - - /// - /// Encodes all mipmap levels into a Ktx file. - /// - /// The image to encode. - /// The Ktx file containing the encoded image. - public static KtxFile EncodeToKtx(this BcEncoder encoder, Image inputImage) - { - return encoder.EncodeToKtx(ImageToMemory2D(inputImage)); - } - - /// - /// Encodes all mipmap levels into a Dds file. - /// - /// The image to encode. - /// The Dds file containing the encoded image. - public static DdsFile EncodeToDds(this BcEncoder encoder, Image inputImage) - { - return encoder.EncodeToDds(ImageToMemory2D(inputImage)); - } - - /// - /// Encodes all mipmap levels into an array of byte buffers. This data does not contain any file headers, just the raw encoded data. - /// - /// The image to encode. - /// A list of raw encoded data. - public static byte[][] EncodeToRawBytes(this BcEncoder encoder, Image inputImage) - { - return encoder.EncodeToRawBytes(ImageToMemory2D(inputImage)); - } - - /// - /// Encodes a single mip level of the input image to a byte buffer. This data does not contain any file headers, just the raw encoded data. - /// - /// The image to encode. - /// The mipmap to encode. - /// The width of the mipmap. - /// The height of the mipmap. - /// The raw encoded data. - public static byte[] EncodeToRawBytes(this BcEncoder encoder, Image inputImage, int mipLevel, out int mipWidth, out int mipHeight) - { - return encoder.EncodeToRawBytes(ImageToMemory2D(inputImage), mipLevel, out mipWidth, out mipHeight); - } - - /// - /// Encodes all cubemap faces and mipmap levels into either a ktx or a dds file and writes it to the output stream. - /// Order is +X, -X, +Y, -Y, +Z, -Z - /// - /// The right face of the cubemap. - /// The left face of the cubemap. - /// The top face of the cubemap. - /// The bottom face of the cubemap. - /// The back face of the cubemap. - /// The front face of the cubemap. - /// The stream to write the encoded image to. - public static void EncodeCubeMapToStream(this BcEncoder encoder, Image right, Image left, Image top, Image down, - Image back, Image front, Stream outputStream) - { - encoder.EncodeCubeMapToStream( - ImageToMemory2D(right), - ImageToMemory2D(left), - ImageToMemory2D(top), - ImageToMemory2D(down), - ImageToMemory2D(back), - ImageToMemory2D(front), - outputStream - ); - } - - /// - /// Encodes all cubemap faces and mipmap levels into a Ktx file. - /// Order is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. - /// - /// The right face of the cubemap. - /// The left face of the cubemap. - /// The top face of the cubemap. - /// The bottom face of the cubemap. - /// The back face of the cubemap. - /// The front face of the cubemap. - /// The Ktx file containing the encoded image. - public static KtxFile EncodeCubeMapToKtx(this BcEncoder encoder, Image right, Image left, Image top, Image down, - Image back, Image front) - { - return encoder.EncodeCubeMapToKtx( - ImageToMemory2D(right), - ImageToMemory2D(left), - ImageToMemory2D(top), - ImageToMemory2D(down), - ImageToMemory2D(back), - ImageToMemory2D(front) - ); - } - - /// - /// Encodes all cubemap faces and mipmap levels into a Dds file. - /// Order is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. - /// - /// The right face of the cubemap. - /// The left face of the cubemap. - /// The top face of the cubemap. - /// The bottom face of the cubemap. - /// The back face of the cubemap. - /// The front face of the cubemap. - /// The Dds file containing the encoded image. - public static DdsFile EncodeCubeMapToDds(this BcEncoder encoder, Image right, Image left, Image top, Image down, - Image back, Image front) - { - return encoder.EncodeCubeMapToDds( - ImageToMemory2D(right), - ImageToMemory2D(left), - ImageToMemory2D(top), - ImageToMemory2D(down), - ImageToMemory2D(back), - ImageToMemory2D(front) - ); - } - - /// - /// Encodes all mipmap levels into a ktx or a dds file and writes it to the output stream asynchronously. - /// - /// The image to encode. - /// The stream to write the encoded image to. - /// The cancellation token for this operation. Can be default, if the operation is not asynchronous. - public static Task EncodeToStreamAsync(this BcEncoder encoder, Image inputImage, Stream outputStream, CancellationToken token = default) - { - return encoder.EncodeToStreamAsync(ImageToMemory2D(inputImage), outputStream, token); - } - - /// - /// Encodes all mipmap levels into a Ktx file asynchronously. - /// - /// The image to encode. - /// The cancellation token for this operation. Can be default, if the operation is not asynchronous. - /// The Ktx file containing the encoded image. - public static Task EncodeToKtxAsync(this BcEncoder encoder, Image inputImage, CancellationToken token = default) - { - return encoder.EncodeToKtxAsync(ImageToMemory2D(inputImage), token); - } - - /// - /// Encodes all mipmap levels into a Dds file asynchronously. - /// - /// The image to encode. - /// The cancellation token for this operation. Can be default, if the operation is not asynchronous. - /// The Dds file containing the encoded image. - public static Task EncodeToDdsAsync(this BcEncoder encoder, Image inputImage, CancellationToken token = default) - { - return encoder.EncodeToDdsAsync(ImageToMemory2D(inputImage), token); - } - - /// - /// Encodes all mipmap levels into an array of byte buffers asynchronously. This data does not contain any file headers, just the raw encoded data. - /// - /// The image to encode. - /// The cancellation token for this operation. Can be default, if the operation is not asynchronous. - /// A list of raw encoded data. - public static Task EncodeToRawBytesAsync(this BcEncoder encoder, Image inputImage, CancellationToken token = default) - { - return encoder.EncodeToRawBytesAsync(ImageToMemory2D(inputImage), token); - } - - /// - /// Encodes a single mip level of the input image to a byte buffer asynchronously. This data does not contain any file headers, just the raw encoded data. - /// - /// The image to encode. - /// The mipmap to encode. - /// The cancellation token for this operation. Can be default, if the operation is not asynchronous. - /// The raw encoded data. - /// To get the width and height of the encoded mipLevel, see . - public static Task EncodeToRawBytesAsync(this BcEncoder encoder, Image inputImage, int mipLevel, CancellationToken token = default) - { - return encoder.EncodeToRawBytesAsync(ImageToMemory2D(inputImage), mipLevel, token); - } - - /// - /// Encodes all cubemap faces and mipmap levels into either a ktx or a dds file and writes it to the output stream asynchronously. - /// Order is +X, -X, +Y, -Y, +Z, -Z - /// - /// The right face of the cubemap. - /// The left face of the cubemap. - /// The top face of the cubemap. - /// The bottom face of the cubemap. - /// The back face of the cubemap. - /// The front face of the cubemap. - /// The stream to write the encoded image to. - /// The cancellation token for this operation. Can be default, if the operation is not asynchronous. - public static Task EncodeCubeMapToStreamAsync(this BcEncoder encoder, Image right, Image left, Image top, Image down, - Image back, Image front, Stream outputStream, CancellationToken token = default) - { - return encoder.EncodeCubeMapToStreamAsync( - ImageToMemory2D(right), - ImageToMemory2D(left), - ImageToMemory2D(top), - ImageToMemory2D(down), - ImageToMemory2D(back), - ImageToMemory2D(front), - outputStream, - token - ); - } - - /// - /// Encodes all cubemap faces and mipmap levels into a Ktx file asynchronously. - /// Order is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. - /// - /// The right face of the cubemap. - /// The left face of the cubemap. - /// The top face of the cubemap. - /// The bottom face of the cubemap. - /// The back face of the cubemap. - /// The front face of the cubemap. - /// The cancellation token for this operation. Can be default, if the operation is not asynchronous. - /// The Ktx file containing the encoded image. - public static Task EncodeCubeMapToKtxAsync(this BcEncoder encoder, Image right, Image left, Image top, - Image down, Image back, Image front, CancellationToken token = default) - { - return encoder.EncodeCubeMapToKtxAsync( - ImageToMemory2D(right), - ImageToMemory2D(left), - ImageToMemory2D(top), - ImageToMemory2D(down), - ImageToMemory2D(back), - ImageToMemory2D(front), - token - ); - } - - /// - /// Encodes all cubemap faces and mipmap levels into a Dds file asynchronously. - /// Order is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. - /// - /// The right face of the cubemap. - /// The left face of the cubemap. - /// The top face of the cubemap. - /// The bottom face of the cubemap. - /// The back face of the cubemap. - /// The front face of the cubemap. - /// The cancellation token for this operation. Can be default, if the operation is not asynchronous. - /// The Dds file containing the encoded image. - public static Task EncodeCubeMapToDdsAsync(this BcEncoder encoder, Image right, Image left, Image top, - Image down, Image back, Image front, CancellationToken token = default) - { - return encoder.EncodeCubeMapToDdsAsync( - ImageToMemory2D(right), - ImageToMemory2D(left), - ImageToMemory2D(top), - ImageToMemory2D(down), - ImageToMemory2D(back), - ImageToMemory2D(front), - token - ); - } - - /// - /// Calculates the number of mipmap levels that will be generated for the given input image. - /// - /// The image to use for the calculation. - /// The number of mipmap levels that will be generated for the input image. - public static int CalculateNumberOfMipLevels(this BcEncoder encoder, Image inputImage) - { - return encoder.CalculateNumberOfMipLevels(inputImage.Width, inputImage.Height); - } - - /// - /// Calculates the size of a given mipmap level. - /// - /// The image to use for the calculation. - /// The mipLevel to calculate (0 is original image) - /// The mipmap width calculated - /// The mipmap height calculated - public static void CalculateMipMapSize(this BcEncoder encoder, Image inputImage, int mipLevel, out int mipWidth, out int mipHeight) - { - encoder.CalculateMipMapSize(inputImage.Width, inputImage.Height, - mipLevel, out mipWidth, out mipHeight); - } - - - private static Memory2D ImageToMemory2D(Image inputImage) - { - var pixels = inputImage.GetPixelMemoryGroup()[0]; - var colors = new ColorRgba32[inputImage.Width * inputImage.Height]; - for (var y = 0; y < inputImage.Height; y++) - { - var yPixels = inputImage.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y); - var yColors = colors.AsSpan(y * inputImage.Width, inputImage.Width); - - MemoryMarshal.Cast(yPixels).CopyTo(yColors); - } - var memory = colors.AsMemory().AsMemory2D(inputImage.Height, inputImage.Width); - return memory; - } - } -} diff --git a/BCnEncoder.NET.ImageSharp/Extensions.cs b/BCnEncoder.NET.ImageSharp/Extensions.cs new file mode 100644 index 0000000..f01c87b --- /dev/null +++ b/BCnEncoder.NET.ImageSharp/Extensions.cs @@ -0,0 +1,663 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using BCnEncoder.Encoder; +using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; +using BCnEncoder.TextureFormats; +using CommunityToolkit.HighPerformance; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace BCnEncoder.ImageSharp +{ + public static class BCnTextureDataExtensions + { + /// + /// Wrap a single face and mipLevel as an . + /// The owner of the pixel memory is still the original texture , + /// so modifying the data in the affects the original . + /// + public static Image AsImage(this BCnTextureData data, int mipLevel = 0, int arrayIndex = 0, + CubeMapFaceDirection faceDirection = CubeMapFaceDirection.XPositive) + + { + Image image = null; + + switch (data.Format) + { + case CompressionFormat.Rgba32_sRGB: + case CompressionFormat.Rgba32: + image = AsImage(data, data.Format, faceDirection, mipLevel, arrayIndex); + break; + case CompressionFormat.RgbaFloat: + image = AsImage(data, data.Format, faceDirection, mipLevel, arrayIndex); + break; + case CompressionFormat.Bgra32: + case CompressionFormat.Bgra32_sRGB: + image = AsImage(data, data.Format, faceDirection, mipLevel, arrayIndex); + break; + case CompressionFormat.Rgb24: + case CompressionFormat.Rgb24_sRGB: + image = AsImage(data, data.Format, faceDirection, mipLevel, arrayIndex); + break; + case CompressionFormat.Bgr24: + case CompressionFormat.Bgr24_sRGB: + image = AsImage(data, data.Format, faceDirection, mipLevel, arrayIndex); + break; + default: + throw new ArgumentException($"Format {data.Format} is not supported for converting to Image!"); + } + + return image; + } + + + /// + /// Wrap a single face and mipLevel as a of . + /// Make sure first that is . + /// The owner of the pixel memory is still , + /// so modifying the data in the affects the original . + /// + public static Image AsImageRgba32(this BCnTextureData data, int mipLevel = 0, int arrayIndex = 0, CubeMapFaceDirection faceDirection = CubeMapFaceDirection.XPositive) + => AsImage(data, CompressionFormat.Rgba32, faceDirection, mipLevel, arrayIndex); + + /// + /// Wrap a single face and mipLevel as a of . + /// Make sure first that is . + /// The pixel data in the is reused, + /// so modifying the data in the affects the original . + /// + public static Image AsImageRgbaVector(this BCnTextureData data, int mipLevel = 0, + int arrayIndex = 0, CubeMapFaceDirection faceDirection = CubeMapFaceDirection.XPositive) + => AsImage(data, CompressionFormat.RgbaFloat, faceDirection, mipLevel, arrayIndex); + + private static Image AsImage(this BCnTextureData data, CompressionFormat expectedFormat, + CubeMapFaceDirection faceDirection, int mipLevel, int arrayElement) + where TPixel : unmanaged, IPixel + { + if (data.Format.ToNonSrgbFormat() != expectedFormat) + { + throw new ArgumentException( + $"The provided {nameof(BCnTextureData)} is not in CompressionFormat.{expectedFormat}. Use the {nameof(Shared.BCnTextureDataExtensions.ConvertTo)} method to convert it first!", + nameof(data)); + } + if (mipLevel >= data.NumMips) + { + throw new ArgumentException( + $"The provided {nameof(BCnTextureData)} does not have the requested mipLevel ({mipLevel})", + nameof(mipLevel)); + } + if (faceDirection > 0 && !data.IsCubeMap) + { + throw new ArgumentException( + $"The provided {nameof(BCnTextureData)} is not a cubeMap so it only has one face!", + nameof(mipLevel)); + } + + var mip = data.Mips[mipLevel][faceDirection, arrayElement]; + + return Image.WrapMemory(Configuration.Default, mip.AsMemory(), mip.Width, mip.Height); + } + } + + public static class ImageExtensions + { + /// + /// Copy the contents of a of type into . + /// The format of the data will be . + /// + public static BCnTextureData ToBCnTextureData(this Image image, bool srgb = true) + { + var bytes = new byte[image.Width * image.Height * 4L]; + image.CopyPixelDataTo(bytes); + AlphaChannelHint alphaChannelHint = EncoderExtensions.GetAlphaChannelHint(image); + return BCnTextureData.FromSingle(srgb ? CompressionFormat.Rgba32_sRGB : CompressionFormat.Rgba32, image.Width, image.Height, bytes, alphaChannelHint); + } + + /// + /// Copy the contents of a of type into . + /// The format of the data will be . + /// + public static BCnTextureData ToBCnTextureData(this Image image) + { + var bytes = new byte[image.Width * image.Height * 16L]; + image.CopyPixelDataTo(bytes); + AlphaChannelHint alphaChannelHint = EncoderExtensions.GetAlphaChannelHint(image); + return BCnTextureData.FromSingle(CompressionFormat.RgbaFloat, image.Width, image.Height, bytes, alphaChannelHint); + } + + /// + /// Copy the contents of a of type into . + /// The format of the data will be . + /// + public static BCnTextureData ToBCnTextureData(this Image image, bool srgb = true) + { + var bytes = new byte[image.Width * image.Height * 3L]; + image.CopyPixelDataTo(bytes); + AlphaChannelHint alphaChannelHint = EncoderExtensions.GetAlphaChannelHint(image); + return BCnTextureData.FromSingle(srgb ? CompressionFormat.Rgb24_sRGB : CompressionFormat.Rgb24, image.Width, image.Height, bytes, alphaChannelHint); + } + + /// + /// Copy the contents of a into . + /// The format of the data will be inferred from the type of . + /// If the type is , the format will be , + /// if the type is , the format will be , + /// if the type is , the format will be . + /// Otherwise, the image will be converted to first and then to the corresponding . + /// + public static BCnTextureData ToBCnTextureData(this Image image, bool srgb = true) + { + if (image is Image rgba32) + return rgba32.ToBCnTextureData(srgb); + if (image is Image rgbaVector) + return rgbaVector.ToBCnTextureData(); + if (image is Image rgb24) + return rgb24.ToBCnTextureData(srgb); + + // Fallback to copy. + Image vectorImage = image.CloneAs(); + return vectorImage.ToBCnTextureData(); + } + } + + public static class EncoderExtensions + { + /// + public static BCnTextureData Encode(this BcEncoder encoder, Image image) + => encoder.Encode(image.ToBCnTextureData()); + + /// + public static Task EncodeAsync(this BcEncoder encoder, Image image, CancellationToken token = default) + => encoder.EncodeAsync(image.ToBCnTextureData(), token); + + /// + public static byte[] EncodeToRawBytes(this BcEncoder encoder, Image input, int mipLevel, + out int mipWidth, out int mipHeight) + => encoder.EncodeToRawBytes( + input.ToBCnTextureData(), + mipLevel, + out mipWidth, + out mipHeight, + out _); + + /// + public static Task EncodeToRawBytesAsync(this BcEncoder encoder, Image input, int mipLevel, + CancellationToken token = default) + => encoder.EncodeToRawBytesAsync( + input.ToBCnTextureData(), + mipLevel, token); + + + /// + /// Encodes all mipmap levels into a texture file container. + /// + /// The input to encode represented by a . + public static TTextureFormat EncodeToTexture(this BcEncoder encoder, Image input) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsHdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support HDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + + var encoded = encoder.Encode(input); + tex.FromTextureData(encoded); + return tex; + } + + /// + /// Encodes all mipmap levels into a texture file container. + /// + /// The input to encode represented by a . + public static async Task EncodeToTextureAsync(this BcEncoder encoder, Image input, CancellationToken token = default) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsHdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support HDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + + var encoded = await encoder.EncodeAsync(input, token); + tex.FromTextureData(encoded); + return tex; + } + + /// + /// Encodes all mipmap levels into a texture file and writes it to the output stream. + /// + /// The input to encode represented by a . + /// The stream to write the encoded image to. + public static void EncodeToStream(this BcEncoder encoder, Image input, Stream outputStream) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + if (!tex.SupportsHdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support HDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + var encoded = encoder.Encode(input); + tex.FromTextureData(encoded); + tex.WriteToStream(outputStream); + } + + /// + /// Encodes all mipmap levels into a texture file and writes it to the output stream. + /// + /// The input to encode represented by a . + /// The stream to write the encoded image to. + public static async Task EncodeToStreamAsync(this BcEncoder encoder, Image input, Stream outputStream, CancellationToken token = default) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + if (!tex.SupportsHdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support HDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + var encoded = await encoder.EncodeAsync(input, token); + tex.FromTextureData(encoded); + tex.WriteToStream(outputStream); + } + + /// + /// Encodes all mipMaps of a cubeMap to a stream either in the specified texture format. + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// + /// The positive X-axis face of the cubeMap + /// The negative X-axis face of the cubeMap + /// The positive Y-axis face of the cubeMap + /// The negative Y-axis face of the cubeMap + /// The positive Z-axis face of the cubeMap + /// The negative Z-axis face of the cubeMap + public static TTextureFormat EncodeCubeMapToTextureHdr(this BcEncoder encoder, + Image right, Image left, + Image top, Image down, + Image back, Image front) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.SupportsHdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support HDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + + var encoded = encoder.Encode(BuildCubeMap(right, left, top, down, back, front)); + tex.FromTextureData(encoded); + return tex; + } + + /// + /// Encodes all mipMaps of a cubeMap to a stream either in the specified texture format. + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// + /// The positive X-axis face of the cubeMap + /// The negative X-axis face of the cubeMap + /// The positive Y-axis face of the cubeMap + /// The negative Y-axis face of the cubeMap + /// The positive Z-axis face of the cubeMap + /// The negative Z-axis face of the cubeMap + public static async Task EncodeCubeMapToTextureHdrAsync(this BcEncoder encoder, + Image right, Image left, + Image top, Image down, + Image back, Image front, CancellationToken token = default) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.SupportsHdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support HDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + + var encoded = await encoder.EncodeAsync(BuildCubeMap(right, left, top, down, back, front), token); + tex.FromTextureData(encoded); + return tex; + } + + /// + /// Encodes all mipMaps of a cubeMap to a stream either in the specified texture format. + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// + /// The positive X-axis face of the cubeMap + /// The negative X-axis face of the cubeMap + /// The positive Y-axis face of the cubeMap + /// The negative Y-axis face of the cubeMap + /// The positive Z-axis face of the cubeMap + /// The negative Z-axis face of the cubeMap + public static TTextureFormat EncodeCubeMapToTexture(this BcEncoder encoder, + Image right, Image left, + Image top, Image down, + Image back, Image front) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + var encoded = encoder.Encode(BuildCubeMap(right, left, top, down, back, front)); + tex.FromTextureData(encoded); + return tex; + } + + /// + /// Encodes all mipMaps of a cubeMap to a stream either in the specified texture format. + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// + /// The positive X-axis face of the cubeMap + /// The negative X-axis face of the cubeMap + /// The positive Y-axis face of the cubeMap + /// The negative Y-axis face of the cubeMap + /// The positive Z-axis face of the cubeMap + /// The negative Z-axis face of the cubeMap + public static async Task EncodeCubeMapToTextureAsync(this BcEncoder encoder, + Image right, Image left, + Image top, Image down, + Image back, Image front, CancellationToken token = default) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + var encoded = await encoder.EncodeAsync(BuildCubeMap(right, left, top, down, back, front), token); + tex.FromTextureData(encoded); + return tex; + } + + /// + /// Encodes all mipMaps of a cubeMap to a stream either in the specified texture format. + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// + /// The positive X-axis face of the cubeMap + /// The negative X-axis face of the cubeMap + /// The positive Y-axis face of the cubeMap + /// The negative Y-axis face of the cubeMap + /// The positive Z-axis face of the cubeMap + /// The negative Z-axis face of the cubeMap + /// The stream to write the encoded image to. + public static void EncodeCubeMapToStreamHdr(this BcEncoder encoder, + Image right, Image left, + Image top, Image down, + Image back, Image front, Stream outputStream) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.SupportsHdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support HDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + var encoded = encoder.Encode(BuildCubeMap(right, left, top, down, back, front)); + tex.FromTextureData(encoded); + tex.WriteToStream(outputStream); + } + + /// + /// Encodes all mipMaps of a cubeMap to a stream either in the specified texture format. + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// + /// The positive X-axis face of the cubeMap + /// The negative X-axis face of the cubeMap + /// The positive Y-axis face of the cubeMap + /// The negative Y-axis face of the cubeMap + /// The positive Z-axis face of the cubeMap + /// The negative Z-axis face of the cubeMap + /// The stream to write the encoded image to. + public static async Task EncodeCubeMapToStreamHdrAsync(this BcEncoder encoder, + Image right, Image left, + Image top, Image down, + Image back, Image front, Stream outputStream, CancellationToken token = default) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.SupportsHdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support HDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + var encoded = await encoder.EncodeAsync(BuildCubeMap(right, left, top, down, back, front), token); + tex.FromTextureData(encoded); + tex.WriteToStream(outputStream); + } + + /// + /// Encodes all mipMaps of a cubeMap to a stream either in the specified texture format. + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// + /// The positive X-axis face of the cubeMap + /// The negative X-axis face of the cubeMap + /// The positive Y-axis face of the cubeMap + /// The negative Y-axis face of the cubeMap + /// The positive Z-axis face of the cubeMap + /// The negative Z-axis face of the cubeMap + /// The stream to write the encoded image to. + public static void EncodeCubeMapToStream(this BcEncoder encoder, + Image right, Image left, + Image top, Image down, + Image back, Image front, Stream outputStream) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + + var encoded = encoder.Encode(BuildCubeMap(right, left, top, down, back, front)); + tex.FromTextureData(encoded); + tex.WriteToStream(outputStream); + } + + /// + /// Encodes all mipMaps of a cubeMap to a stream either in the specified texture format. + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// + /// The positive X-axis face of the cubeMap + /// The negative X-axis face of the cubeMap + /// The positive Y-axis face of the cubeMap + /// The negative Y-axis face of the cubeMap + /// The positive Z-axis face of the cubeMap + /// The negative Z-axis face of the cubeMap + /// The stream to write the encoded image to. + public static async Task EncodeCubeMapToStreamAsync(this BcEncoder encoder, + Image right, Image left, + Image top, Image down, + Image back, Image front, Stream outputStream, CancellationToken token = default) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + var encoded = await encoder.EncodeAsync(BuildCubeMap(right, left, top, down, back, front), token); + tex.FromTextureData(encoded); + tex.WriteToStream(outputStream); + } + + private static BCnTextureData BuildCubeMap( + Image right, Image left, + Image top, Image down, + Image back, Image front) + { + AlphaChannelHint alphaChannelHint = GetAlphaChannelHint(right); + + var data = new BCnTextureData(CompressionFormat.Rgba32, right.Width, right.Height, 1, 1, 1, true, true, alphaChannelHint); + + if ( + right.Width != left.Width || right.Height != left.Height || + right.Width != top.Width || right.Height != top.Height || + right.Width != down.Width || right.Height != down.Height || + right.Width != back.Width || right.Height != back.Height || + right.Width != front.Width || right.Height != front.Height + ) + { + throw new ArgumentException("All faces of a cubeMap must be of equal width and height!"); + } + + right.CopyPixelDataTo(data.Mips[0][CubeMapFaceDirection.XPositive].Data); + left.CopyPixelDataTo(data.Mips[0][CubeMapFaceDirection.XNegative].Data); + top.CopyPixelDataTo(data.Mips[0][CubeMapFaceDirection.YPositive].Data); + down.CopyPixelDataTo(data.Mips[0][CubeMapFaceDirection.YNegative].Data); + back.CopyPixelDataTo(data.Mips[0][CubeMapFaceDirection.ZPositive].Data); + front.CopyPixelDataTo(data.Mips[0][CubeMapFaceDirection.ZNegative].Data); + + return data; + } + + private static BCnTextureData BuildCubeMap( + Image right, Image left, + Image top, Image down, + Image back, Image front) + { + AlphaChannelHint alphaChannelHint = GetAlphaChannelHint(right); + + var data = new BCnTextureData(CompressionFormat.RgbaFloat, right.Width, right.Height, 1, 1, 1, true, true, alphaChannelHint); + + if ( + right.Width != left.Width || right.Height != left.Height || + right.Width != top.Width || right.Height != top.Height || + right.Width != down.Width || right.Height != down.Height || + right.Width != back.Width || right.Height != back.Height || + right.Width != front.Width || right.Height != front.Height + ) + { + throw new ArgumentException("All faces of a cubeMap must be of equal width and height!"); + } + + right.CopyPixelDataTo(data.Mips[0][CubeMapFaceDirection.XPositive].Data); + left.CopyPixelDataTo(data.Mips[0][CubeMapFaceDirection.XNegative].Data); + top.CopyPixelDataTo(data.Mips[0][CubeMapFaceDirection.YPositive].Data); + down.CopyPixelDataTo(data.Mips[0][CubeMapFaceDirection.YNegative].Data); + back.CopyPixelDataTo(data.Mips[0][CubeMapFaceDirection.ZPositive].Data); + front.CopyPixelDataTo(data.Mips[0][CubeMapFaceDirection.ZNegative].Data); + + + return data; + } + + internal static AlphaChannelHint GetAlphaChannelHint(Image image) + { + if (!image.PixelType.AlphaRepresentation.HasValue) + return AlphaChannelHint.Unknown; + + switch (image.PixelType.AlphaRepresentation.Value) + { + case (PixelAlphaRepresentation.None): + case PixelAlphaRepresentation.Unassociated: + return AlphaChannelHint.Straight; + case (PixelAlphaRepresentation.Associated): + return AlphaChannelHint.Premultiplied; + default: + return AlphaChannelHint.Unknown; + } + } + } +} diff --git a/BCnEncoder.Net.TextureFormats/AssemblyInfo.cs b/BCnEncoder.Net.TextureFormats/AssemblyInfo.cs new file mode 100644 index 0000000..ad14af0 --- /dev/null +++ b/BCnEncoder.Net.TextureFormats/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("BCnEncTests")] diff --git a/BCnEncoder.Net.TextureFormats/BCnEncoder.Net.TextureFormats.csproj b/BCnEncoder.Net.TextureFormats/BCnEncoder.Net.TextureFormats.csproj new file mode 100644 index 0000000..a56829f --- /dev/null +++ b/BCnEncoder.Net.TextureFormats/BCnEncoder.Net.TextureFormats.csproj @@ -0,0 +1,14 @@ + + + + net9.0 + enable + BCnEncoder.TextureFormats + True + + + + + + + diff --git a/BCnEncoder.Net.TextureFormats/Dds/D3DFormat.cs b/BCnEncoder.Net.TextureFormats/Dds/D3DFormat.cs new file mode 100644 index 0000000..7ef0ab6 --- /dev/null +++ b/BCnEncoder.Net.TextureFormats/Dds/D3DFormat.cs @@ -0,0 +1,179 @@ +namespace BCnEncoder.TextureFormats; + +using System.Collections.Generic; +using BCnEncoder.Shared; + +public enum D3DFormat : uint { + D3DFormatUnknown = 0, + + D3DFormatR8G8B8 = 20, + D3DFormatA8R8G8B8 = 21, + D3DFormatX8R8G8B8 = 22, + D3DFormatR5G6B5 = 23, + D3DFormatX1R5G5B5 = 24, + D3DFormatA1R5G5B5 = 25, + D3DFormatA4R4G4B4 = 26, + D3DFormatR3G3B2 = 27, + D3DFormatA8 = 28, + D3DFormatA8R3G3B2 = 29, + D3DFormatX4R4G4B4 = 30, + D3DFormatA2B10G10R10 = 31, + D3DFormatA8B8G8R8 = 32, + D3DFormatX8B8G8R8 = 33, + D3DFormatG16R16 = 34, + D3DFormatA2R10G10B10 = 35, + D3DFormatA16B16G16R16 = 36, + + D3DFormatA8P8 = 40, + D3DFormatP8 = 41, + + D3DFormatL8 = 50, + D3DFormatA8L8 = 51, + D3DFormatA4L4 = 52, + + D3DFormatV8U8 = 60, + D3DFormatL6V5U5 = 61, + D3DFormatX8L8V8U8 = 62, + D3DFormatQ8W8V8U8 = 63, + D3DFormatV16U16 = 64, + D3DFormatA2W10V10U10 = 67, + + // FourCC formats + D3DFormatUYVY = ('U' | ('Y' << 8) | ('V' << 16) | ('Y' << 24)), + D3DFormatR8G8_B8G8 = ('R' | ('G' << 8) | ('B' << 16) | ('G' << 24)), + D3DFormatYUY2 = ('Y' | ('U' << 8) | ('Y' << 16) | ('2' << 24)), + D3DFormatG8R8_G8B8 = ('G' | ('R' << 8) | ('G' << 16) | ('B' << 24)), + D3DFormatDXT1 = ('D' | ('X' << 8) | ('T' << 16) | ('1' << 24)), + D3DFormatDXT2 = ('D' | ('X' << 8) | ('T' << 16) | ('2' << 24)), + D3DFormatDXT3 = ('D' | ('X' << 8) | ('T' << 16) | ('3' << 24)), + D3DFormatDXT4 = ('D' | ('X' << 8) | ('T' << 16) | ('4' << 24)), + D3DFormatDXT5 = ('D' | ('X' << 8) | ('T' << 16) | ('5' << 24)), + + D3DFormatD16Lockable = 70, + D3DFormatD32 = 71, + D3DFormatD15S1 = 73, + D3DFormatD24S8 = 75, + D3DFormatD24X8 = 77, + D3DFormatD24X4S4 = 79, + D3DFormatD16 = 80, + + D3DFormatD32FLockable = 82, + D3DFormatD24FS8 = 83, + + D3DFormatL16 = 81, + + D3DFormatVertexData = 100, + D3DFormatIndex16 = 101, + D3DFormatIndex32 = 102, + + D3DFormatQ16W16V16U16 = 110, + + D3DFormatR16F = 111, + D3DFormatG16R16F = 112, + D3DFormatA16B16G16R16F = 113, + + D3DFormatR32F = 114, + D3DFormatG32R32F = 115, + D3DFormatA32B32G32R32F = 116, + + D3DFormatCxV8U8 = 117, +} + +public static class D3DFormatExtensions +{ + /// + /// Convert a CompressionFormat to D3DFormat + /// + public static D3DFormat ToD3DFormat(this CompressionFormat format) => + FormatMapping.TryGetValue(format, out var d3dFormat) ? d3dFormat : D3DFormat.D3DFormatUnknown; + + /// + /// Convert a D3DFormat to CompressionFormat + /// + public static CompressionFormat ToCompressionFormat(this D3DFormat format) => + FormatMappingReverse.TryGetValue(format, out var bcnFormat) ? bcnFormat : CompressionFormat.Unknown; + + private static Dictionary FormatMapping { get; } = + new() { + // Red-only formats + { CompressionFormat.R8, D3DFormat.D3DFormatL8 }, // L8 is closest match for R8 + { CompressionFormat.R16, D3DFormat.D3DFormatL16 }, // L16 is closest match for R16 + { CompressionFormat.R16F, D3DFormat.D3DFormatR16F }, // Direct match + { CompressionFormat.R32F, D3DFormat.D3DFormatR32F }, // Direct match + + // Red-green formats + { CompressionFormat.R8G8, D3DFormat.D3DFormatA8L8 }, // A8L8 is closest for R8G8 + { CompressionFormat.R16G16, D3DFormat.D3DFormatG16R16 }, // G16R16 in D3D9 has memory order RG + { CompressionFormat.R16G16F, D3DFormat.D3DFormatG16R16F }, // G16R16F in D3D9 has memory order RG + { CompressionFormat.R32G32F, D3DFormat.D3DFormatG32R32F }, // G32R32F in D3D9 has memory order RG + + // RGBA formats + { CompressionFormat.R10G10B10A2_Packed, D3DFormat.D3DFormatA2B10G10R10 }, // Note component order + { CompressionFormat.Rgba32, D3DFormat.D3DFormatA8B8G8R8 }, // A8B8G8R8 in D3D9 has memory order RGBA + { CompressionFormat.Rgba32_sRGB, D3DFormat.D3DFormatA8B8G8R8 }, // D3D9 doesn't have sRGB explicitly + { CompressionFormat.RgbaFloat, D3DFormat.D3DFormatA32B32G32R32F }, + { CompressionFormat.RgbaHalf, D3DFormat.D3DFormatA16B16G16R16F }, + + // BGR formats + { CompressionFormat.Bgr24, D3DFormat.D3DFormatR8G8B8 }, + { CompressionFormat.Bgr24_sRGB, D3DFormat.D3DFormatR8G8B8 }, + { CompressionFormat.B5G6R5_Packed, D3DFormat.D3DFormatR5G6B5 }, // D3D9 RGB order is BGR in memory + { CompressionFormat.Bgr24x8, D3DFormat.D3DFormatX8R8G8B8 }, + { CompressionFormat.Bgr24x8_sRGB, D3DFormat.D3DFormatX8R8G8B8 }, + + // BGRA formats + { CompressionFormat.B5G5R5A1_Packed, D3DFormat.D3DFormatA1R5G5B5 }, // D3D9 BGRA order is ARGB in memory (perfect match) + { CompressionFormat.B4G4R4A4_Packed, D3DFormat.D3DFormatA4R4G4B4 }, // D3D9 BGRA order is ARGB in memory (perfect match) + { CompressionFormat.Bgra32, D3DFormat.D3DFormatA8R8G8B8 }, // A8R8G8B8 in D3D9 has memory order BGRA + { CompressionFormat.Bgra32_sRGB, D3DFormat.D3DFormatA8R8G8B8 }, + + // BC formats + { CompressionFormat.Bc1, D3DFormat.D3DFormatDXT1 }, + { CompressionFormat.Bc1_sRGB, D3DFormat.D3DFormatDXT1 }, // D3D9 doesn't distinguish sRGB variants + { CompressionFormat.Bc1WithAlpha, D3DFormat.D3DFormatDXT1 }, + { CompressionFormat.Bc1WithAlpha_sRGB, D3DFormat.D3DFormatDXT1 }, + { CompressionFormat.Bc2, D3DFormat.D3DFormatDXT3 }, // DXT3 = BC2 + { CompressionFormat.Bc2_sRGB, D3DFormat.D3DFormatDXT3 }, + { CompressionFormat.Bc3, D3DFormat.D3DFormatDXT5 }, // DXT5 = BC3 + { CompressionFormat.Bc3_sRGB, D3DFormat.D3DFormatDXT5 }, + + // No D3D9 equivalents for the newer BC formats + // BC4, BC5, BC6, BC7 were introduced with Direct3D 10 + }; + + private static Dictionary FormatMappingReverse { get; } = + new() { + // Red-only formats + { D3DFormat.D3DFormatL8, CompressionFormat.R8 }, + { D3DFormat.D3DFormatL16, CompressionFormat.R16 }, + { D3DFormat.D3DFormatR16F, CompressionFormat.R16F }, + { D3DFormat.D3DFormatR32F, CompressionFormat.R32F }, + + // Red-green formats + { D3DFormat.D3DFormatA8L8, CompressionFormat.R8G8 }, + { D3DFormat.D3DFormatG16R16, CompressionFormat.R16G16 }, + { D3DFormat.D3DFormatG16R16F, CompressionFormat.R16G16F }, + { D3DFormat.D3DFormatG32R32F, CompressionFormat.R32G32F }, + + // RGBA formats + { D3DFormat.D3DFormatA2B10G10R10, CompressionFormat.R10G10B10A2_Packed }, + { D3DFormat.D3DFormatA8B8G8R8, CompressionFormat.Rgba32 }, + { D3DFormat.D3DFormatA32B32G32R32F, CompressionFormat.RgbaFloat }, + { D3DFormat.D3DFormatA16B16G16R16F, CompressionFormat.RgbaHalf }, + + // BGR formats + { D3DFormat.D3DFormatR8G8B8, CompressionFormat.Bgr24 }, + { D3DFormat.D3DFormatR5G6B5, CompressionFormat.B5G6R5_Packed }, + { D3DFormat.D3DFormatX8R8G8B8, CompressionFormat.Bgr24x8 }, + + // BGRA formats + { D3DFormat.D3DFormatA1R5G5B5, CompressionFormat.B5G5R5A1_Packed }, + { D3DFormat.D3DFormatA4R4G4B4, CompressionFormat.B4G4R4A4_Packed }, + { D3DFormat.D3DFormatA8R8G8B8, CompressionFormat.Bgra32 }, + + // BC formats + { D3DFormat.D3DFormatDXT1, CompressionFormat.Bc1 }, + { D3DFormat.D3DFormatDXT3, CompressionFormat.Bc2 }, + { D3DFormat.D3DFormatDXT5, CompressionFormat.Bc3 } + }; +} diff --git a/BCnEncoder.Net.TextureFormats/Dds/DdsFile.cs b/BCnEncoder.Net.TextureFormats/Dds/DdsFile.cs new file mode 100644 index 0000000..877a6ae --- /dev/null +++ b/BCnEncoder.Net.TextureFormats/Dds/DdsFile.cs @@ -0,0 +1,851 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using BCnEncoder.Shared; + +namespace BCnEncoder.TextureFormats +{ + public class DdsFile : ITextureFileFormat + { + /// + /// Direct X API levels + /// + public enum DxApiLevel + { + Dx9 = 9, + Dx10 = 10, + Dx11 = 11, + Dx12 = 12 + } + + /// + /// Which Minimum DirectX API level to use when writing DDS files. + /// + public static DxApiLevel PreferredMinApiLevel = DxApiLevel.Dx9; + + public DdsHeader header; + public DdsHeaderDx10 dx10Header; + public List ArrayElements { get; } = new List(); + + /// + public bool SupportsLdr => true; + + /// + public bool SupportsHdr => true; + + /// + public bool SupportsCubeMap => true; + + /// + public bool SupportsMipMaps => true; + + /// + public bool SupportsArrays => true; + + /// + public bool IsSupportedFormat(CompressionFormat format) + { + return format.ToDxgiFormat() != DxgiFormat.DxgiFormatUnknown; + } + + /// + public void FromTextureData(BCnTextureData textureData) + { + if (!IsSupportedFormat(textureData.Format)) + { + throw new ArgumentException($"Unsupported format {textureData.Format} in textureData.", + nameof(textureData)); + } + + if (textureData.Depth > 1) + throw new NotSupportedException("Depth > 1 is not supported."); + + (header, dx10Header) = DdsHeader.InitializeFor(textureData.Width, textureData.Height, textureData.Depth, textureData.Format, + textureData.IsArrayTexture, textureData.AlphaChannelHint); + + if (textureData.IsArrayTexture) + { + if (!header.ddsPixelFormat.IsDxt10Format) + throw new InvalidOperationException("Cannot create an array texture with a non-DXT10 supported format."); + + header.dwCaps |= HeaderCaps.DdscapsComplex; + dx10Header.arraySize = (uint)textureData.NumArrayElements; + } + + if (textureData.HasMipLevels) // MipMaps + { + header.dwCaps |= HeaderCaps.DdscapsMipmap | HeaderCaps.DdscapsComplex; + header.dwMipMapCount = (uint)textureData.NumMips; + } + + if (textureData.IsCubeMap) // CubeMap + { + header.dwCaps |= HeaderCaps.DdscapsComplex; + header.dwCaps2 |= HeaderCaps2.Ddscaps2Cubemap | + HeaderCaps2.Ddscaps2CubemapPositivex | + HeaderCaps2.Ddscaps2CubemapNegativex | + HeaderCaps2.Ddscaps2CubemapPositivey | + HeaderCaps2.Ddscaps2CubemapNegativey | + HeaderCaps2.Ddscaps2CubemapPositivez | + HeaderCaps2.Ddscaps2CubemapNegativez; + + if (header.ddsPixelFormat.IsDxt10Format) + { + dx10Header.arraySize *= 6; + } + } + + if ((header.dwFlags & HeaderFlags.DdsdLinearsize) != 0) + { + header.dwPitchOrLinearSize = (uint)textureData.Mips[0].SizeInBytes; + } + + var uWidth = (uint)textureData.Width; + var uHeight = (uint)textureData.Height; + var uDepth = (uint)textureData.Depth; + + ArrayElements.Clear(); + + for (var a = 0; a < textureData.NumArrayElements; a++) + { + for (var f = 0; f < textureData.NumFaces; f++) + { + var face = (CubeMapFaceDirection)f; + DdsArrayElement ddsArrayElement = new DdsArrayElement(uWidth, uHeight, uDepth, + (uint)textureData.Mips[0].SizeInBytes, textureData.NumMips); + + for (var m = 0; m < textureData.Mips.Length; m++) + { + ddsArrayElement.MipMaps[m] = new DdsMipMap( + textureData.Mips[m][face, a].Data, + (uint)textureData.Mips[m].Width, + (uint)textureData.Mips[m].Height, + (uint)textureData.Mips[m].Depth); + } + + ArrayElements.Add(ddsArrayElement); + } + } + } + + /// + public BCnTextureData ToTextureData() + { + GetBCnCompressionFormat(out CompressionFormat compressionFormat, out AlphaChannelHint alphaChannelHint); + + var isCubeMap = (header.dwCaps2 & HeaderCaps2.Ddscaps2Cubemap) != 0; + var faceCount = isCubeMap ? 6 : 1; + var arrayCount = 1; + + if (header.ddsPixelFormat.IsDxt10Format ) + { + if (dx10Header.arraySize <= 1) + arrayCount = 1; // Assume a single texture in array + else if (dx10Header.arraySize % faceCount != 0) + throw new FormatException( + "The DXT10 header contains invalid arraySize. Should be divisible by faceCount."); + else + arrayCount = (int)dx10Header.arraySize / faceCount; + } + + var width = (header.dwFlags & HeaderFlags.DdsdWidth) != 0 ? header.dwWidth : 1; + var height = (header.dwFlags & HeaderFlags.DdsdHeight) != 0 ? header.dwHeight : 1; + var depth = (header.dwFlags & HeaderFlags.DdsdDepth) != 0 ? header.dwDepth : 1; + + var data = new BCnTextureData( + compressionFormat, + (int)width, + (int)height, + (int)depth, + (int)header.MipMapCountOrOne, + arrayCount, + isCubeMap, + false, + alphaChannelHint); + + for (var a = 0; a < data.NumArrayElements; a++) + { + for (var f = 0; f < data.NumFaces; f++) + { + for (var m = 0; m < data.NumMips; m++) + { + int arrayIndex = f + a * data.NumFaces; + if (ArrayElements[arrayIndex].MipMaps[m].SizeInBytes != data.Mips[m].SizeInBytes) + { + throw new TextureFormatException("DdsFile mipmap size different from expected!"); + } + + data.Mips[m][(CubeMapFaceDirection)f, a].Data = ArrayElements[arrayIndex].MipMaps[m].Data; + } + } + } + + return data; + } + + /// + public void ReadFromStream(Stream inputStream) + { + using (var br = new BinaryReader(inputStream, Encoding.UTF8, true)) + { + var magic = br.ReadUInt32(); + if (magic != 0x20534444U) + { + throw new FormatException("The file does not contain a dds file."); + } + + header = br.ReadStruct(); + if (header.dwSize != 124) + { + throw new FormatException("The file header contains invalid dwSize."); + } + + var dx10Format = header.ddsPixelFormat.IsDxt10Format; + + if (dx10Format) + { + dx10Header = br.ReadStruct(); + } + + var mipMapCount = (header.dwCaps & HeaderCaps.DdscapsMipmap) != 0 ? header.dwMipMapCount : 1; + var faceCount = (header.dwCaps2 & HeaderCaps2.Ddscaps2Cubemap) != 0 ? 6u : 1u; + + uint arrayCount = 1; + if (dx10Format) + { + if (dx10Header.arraySize <= 1) + arrayCount = 1; // Assume a single texture in array + else if (dx10Header.arraySize % faceCount != 0) + throw new FormatException( + "The file header contains invalid arraySize. Should be divisible by faceCount."); + else + arrayCount = dx10Header.arraySize / faceCount; + } + var width = (header.dwFlags & HeaderFlags.DdsdWidth) != 0 ? header.dwWidth : 1; + var height = (header.dwFlags & HeaderFlags.DdsdHeight) != 0 ? header.dwHeight : 1; + var depth = (header.dwFlags & HeaderFlags.DdsdDepth) != 0 ? header.dwDepth : 1; + + GetBCnCompressionFormat(out CompressionFormat format, out _); + + for (var element = 0; element < faceCount * arrayCount; element++) + { + var sizeInBytes = GetSizeInBytes(format, width, height, depth); + + ArrayElements.Add(new DdsArrayElement(width, height, depth, sizeInBytes, (int)mipMapCount)); + + for (var mip = 0; mip < mipMapCount; mip++) + { + MipMapper.CalculateMipLevelSize( + (int)width, + (int)height, + (int)depth, + mip, + out var mipWidth, + out var mipHeight, + out var mipDepth); + + if (mip > 0) //Calculate new byteSize + { + sizeInBytes = GetSizeInBytes(format, (uint)mipWidth, (uint)mipHeight, (uint)mipDepth); + } + + var data = new byte[sizeInBytes]; + br.Read(data); + ArrayElements[element].MipMaps[mip] = new DdsMipMap(data, (uint)mipWidth, (uint)mipHeight, (uint)mipDepth); + } + } + } + } + + /// + public void WriteToStream(Stream outputStream) + { + if (ArrayElements.Count < 1 || ArrayElements[0].MipMaps.Length < 1) + { + throw new InvalidOperationException( + "The DDS structure should have at least 1 mipmap level and 1 Face before writing to file."); + } + + for (var i = 0; i < ArrayElements.Count; i++) + { + if (ArrayElements[i].Width != header.dwWidth || ArrayElements[i].Height != header.dwHeight || ArrayElements[i].Depth != header.dwDepth) + { + throw new InvalidOperationException("Faces with different sizes are not supported."); + } + } + + var arrayElementsCount = ArrayElements.Count; + var mipCount = header.MipMapCountOrOne; + + using (var bw = new BinaryWriter(outputStream, Encoding.UTF8, true)) + { + bw.Write(0x20534444U); // magic 'DDS ' + + bw.WriteStruct(header); + + if (header.ddsPixelFormat.IsDxt10Format) + { + bw.WriteStruct(dx10Header); + } + + for (var i = 0; i < arrayElementsCount; i++) + { + for (var mip = 0; mip < mipCount; mip++) + { + bw.Write(ArrayElements[i].MipMaps[mip].Data); + } + } + } + } + + public void GetBCnCompressionFormat(out CompressionFormat format, out AlphaChannelHint alphaChannelHint) + { + format = CompressionFormat.Unknown; + alphaChannelHint = AlphaChannelHint.Unknown; + + if (header.ddsPixelFormat.TryGetDx9(out format, out alphaChannelHint)) + { + return; + } + + DxgiFormat dxgiFormat = dx10Header.dxgiFormat; + format = dxgiFormat.ToCompressionFormat(); + + if (header.ddsPixelFormat.dwFlags.HasFlag(PixelFormatFlags.DdpfAlphaPixels)) + { + if (format == CompressionFormat.Bc1) + { + format = CompressionFormat.Bc1WithAlpha; + } + else if (format == CompressionFormat.Bc1_sRGB) + { + format = CompressionFormat.Bc1WithAlpha_sRGB; + } + } + + // Alpha channel hint. Only supported in DX12 and later + uint maskedAlphaMode = (dx10Header.miscFlags2 & 0x7); + + if (maskedAlphaMode == (uint)Dx10HeaderMiscFlags2.Dx10HeaderMiscFlags2AlphaModeUnknown) + { + alphaChannelHint = AlphaChannelHint.Unknown; + } + else if (maskedAlphaMode == (uint)Dx10HeaderMiscFlags2.Dx10HeaderMiscFlags2AlphaModePremultiplied) + { + alphaChannelHint = AlphaChannelHint.Premultiplied; + } + else + { + alphaChannelHint = AlphaChannelHint.Straight; + } + } + + private static uint GetSizeInBytes(CompressionFormat format, uint width, uint height, uint depth) + { + uint sizeInBytes; + if (format.IsBlockCompressedFormat()) + { + sizeInBytes = (uint)ImageToBlocks.CalculateNumOfBlocks(format, (int)width, (int)height, (int)depth); + sizeInBytes *= (uint)format.GetBytesPerBlock(); + } + else + { + sizeInBytes = width * height * depth; + sizeInBytes = (uint)(sizeInBytes * format.GetBytesPerBlock()); + } + + return sizeInBytes; + } + + public static DdsFile Load(Stream stream) + { + var tex = new DdsFile(); + tex.ReadFromStream(stream); + return tex; + } + } + + [StructLayout(LayoutKind.Sequential)] + public unsafe struct DdsHeader + { + /// + /// Has to be 124 + /// + public uint dwSize; + + public HeaderFlags dwFlags; + public uint dwHeight; + public uint dwWidth; + public uint dwPitchOrLinearSize; + public uint dwDepth; + public uint dwMipMapCount; + public fixed uint dwReserved1[11]; + public DdsPixelFormat ddsPixelFormat; + public HeaderCaps dwCaps; + public HeaderCaps2 dwCaps2; + public uint dwCaps3; + public uint dwCaps4; + public uint dwReserved2; + + public uint MipMapCountOrOne => (dwFlags & HeaderFlags.DdsdMipmapcount) != 0 ? dwMipMapCount : 1; + + public static (DdsHeader, DdsHeaderDx10) InitializeDx10Header(int width, int height, int depth, CompressionFormat format, + bool forceDxt10Header, AlphaChannelHint alphaChannelHint) + { + var header = new DdsHeader(); + var dxt10Header = new DdsHeaderDx10(); + + if (format == CompressionFormat.Unknown) + throw new ArgumentException("Format must be set"); + if (width < 1) + throw new ArgumentException("Width must be greater than 0"); + if (height < 1) + throw new ArgumentException("Height must be greater than 0"); + if (depth < 1) + throw new ArgumentException("Depth must be greater than 0"); + + header.dwSize = 124; + header.dwFlags = HeaderFlags.Required | HeaderFlags.DdsdMipmapcount; + header.dwWidth = (uint)width; + header.dwHeight = (uint)height; + header.dwDepth = (uint)depth; + header.dwMipMapCount = 1; + header.dwCaps = HeaderCaps.DdscapsTexture; + + // Set additional flags for 3D textures + if (depth > 1) + { + header.dwFlags |= HeaderFlags.DdsdDepth; + header.dwCaps |= HeaderCaps.DdscapsComplex; + header.dwCaps2 |= HeaderCaps2.Ddscaps2Volume; + } + + if (!format.IsBlockCompressedFormat()) + { + header.dwFlags |= HeaderFlags.DdsdPitch; + header.dwPitchOrLinearSize = (uint)((width * format.GetBytesPerBlock() * 8 + 7) / 8); + } + else + { + header.dwFlags |= HeaderFlags.DdsdLinearsize; + header.dwPitchOrLinearSize = (uint)(width * height * depth * format.GetBytesPerBlock()); + } + + + // Try to make a DX9 pixel format + bool isDx9Compatible = DdsPixelFormat.TryMakeDx9(format, alphaChannelHint, out DdsPixelFormat pixelFormat); + + if (forceDxt10Header || !isDx9Compatible || DdsFile.PreferredMinApiLevel >= DdsFile.DxApiLevel.Dx10) + { + // Use DX10 header if preferred or if DX9 is not compatible + + // Get the DXGI format + DxgiFormat dxgiFormat = format.ToDxgiFormat(); + + // Special case for ATC formats which don't have proper DXGI format definitions + switch (dxgiFormat) + { + case DxgiFormat.DxgiFormatAtcExt: + case DxgiFormat.DxgiFormatAtcExplicitAlphaExt: + case DxgiFormat.DxgiFormatAtcInterpolatedAlphaExt: + throw new NotSupportedException("ATC formats are not supported for DX10+ textures."); + + default: + header.ddsPixelFormat = new DdsPixelFormat + { + dwSize = 32, + dwFlags = PixelFormatFlags.DdpfFourcc, + dwFourCc = DdsPixelFormat.Dx10 + }; + dxt10Header.arraySize = 1; + dxt10Header.dxgiFormat = dxgiFormat; + + // Set the appropriate resource dimension based on the texture dimensions + if (depth > 1) + { + dxt10Header.resourceDimension = D3D10ResourceDimension.D3D10ResourceDimensionTexture3D; + } + else if (height == 1) + { + dxt10Header.resourceDimension = D3D10ResourceDimension.D3D10ResourceDimensionTexture1D; + } + else + { + dxt10Header.resourceDimension = D3D10ResourceDimension.D3D10ResourceDimensionTexture2D; + } + + // Set the alpha mode (only supported for DX12 and above) + if (DdsFile.PreferredMinApiLevel >= DdsFile.DxApiLevel.Dx12) + { + if (!format.SupportsAlpha()) + { + dxt10Header.miscFlags2 = (uint)Dx10HeaderMiscFlags2.Dx10HeaderMiscFlags2AlphaModeOpaque; + } + switch (alphaChannelHint) + { + case AlphaChannelHint.Unknown: + dxt10Header.miscFlags2 = (uint)Dx10HeaderMiscFlags2.Dx10HeaderMiscFlags2AlphaModeUnknown; + break; + case AlphaChannelHint.Premultiplied: + dxt10Header.miscFlags2 = (uint)Dx10HeaderMiscFlags2.Dx10HeaderMiscFlags2AlphaModePremultiplied; + break; + case AlphaChannelHint.Straight: + dxt10Header.miscFlags2 = (uint)Dx10HeaderMiscFlags2.Dx10HeaderMiscFlags2AlphaModeStraight; + break; + } + } + break; + } + } + else + { + // Use the DX9 pixel format + header.ddsPixelFormat = pixelFormat; + } + + return (header, dxt10Header); + } + + public static (DdsHeader, DdsHeaderDx10) InitializeFor(int width, int height, int depth, CompressionFormat format, bool forceDx10, AlphaChannelHint alphaChannelHint = AlphaChannelHint.Straight) + { + // Initialize the DX10 header + var (header, dxt10Header) = InitializeDx10Header(width, height, depth, format, forceDx10, alphaChannelHint); + + return (header, dxt10Header); + } + } + + public struct DdsPixelFormat + { + public static readonly uint Dx10 = MakeFourCc('D', 'X', '1', '0'); + + public static readonly uint Dxt1 = MakeFourCc('D', 'X', 'T', '1'); + public static readonly uint Dxt2 = MakeFourCc('D', 'X', 'T', '2'); + public static readonly uint Dxt3 = MakeFourCc('D', 'X', 'T', '3'); + public static readonly uint Dxt4 = MakeFourCc('D', 'X', 'T', '4'); + public static readonly uint Dxt5 = MakeFourCc('D', 'X', 'T', '5'); + public static readonly uint Ati1 = MakeFourCc('A', 'T', 'I', '1'); + public static readonly uint Ati2 = MakeFourCc('A', 'T', 'I', '2'); + public static readonly uint Atc = MakeFourCc('A', 'T', 'C', ' '); + public static readonly uint Atci = MakeFourCc('A', 'T', 'C', 'I'); + public static readonly uint Atca = MakeFourCc('A', 'T', 'C', 'A'); + + public static readonly uint Bc4S = MakeFourCc('B', 'C', '4', 'S'); + public static readonly uint Bc4U = MakeFourCc('B', 'C', '4', 'U'); + public static readonly uint Bc5S = MakeFourCc('B', 'C', '5', 'S'); + public static readonly uint Bc5U = MakeFourCc('B', 'C', '5', 'U'); + + public static readonly uint Rgbg = MakeFourCc('R', 'G', 'B', 'G'); + public static readonly uint Grgb = MakeFourCc('G', 'R', 'G', 'B'); + public static readonly uint Uyvy = MakeFourCc('U', 'Y', 'V', 'Y'); + public static readonly uint Yuy2 = MakeFourCc('Y', 'U', 'Y', '2'); + + public static readonly uint A16B16G16R16 = 36; + public static readonly uint Q16W16V16U16 = 110; + public static readonly uint R16F = 111; + public static readonly uint G16R16F = 112; + public static readonly uint A16B16G16R16F = 113; + public static readonly uint R32F = 114; + public static readonly uint G32R32F = 115; + public static readonly uint A32B32G32R32F = 116; + public static readonly uint CxV8U8 = 117; + + public static readonly (DxgiFormat dxgiFormat, D3DFormat d3dFormat, PixelFormatFlags flags, uint bitCount, uint rMask, uint gMask, uint bMask, uint aMask)[] MappingTable = new [] + { + (DxgiFormat.DxgiFormatR8G8B8A8Unorm, D3DFormat.D3DFormatA8B8G8R8, PixelFormatFlags.DdpfRgb | PixelFormatFlags.DdpfAlphaPixels, 32U, 0xffU, 0xff00U, 0xff0000U, 0xff000000U), + (DxgiFormat.DxgiFormatR16G16Unorm, D3DFormat.D3DFormatG16R16, PixelFormatFlags.DdpfRgb | PixelFormatFlags.DdpfAlphaPixels, 32U, 0xffffU, 0xffff0000U, 0x0U, 0x0U), + (DxgiFormat.DxgiFormatR10G10B10A2Unorm, D3DFormat.D3DFormatA2B10G10R10, PixelFormatFlags.DdpfRgb | PixelFormatFlags.DdpfAlphaPixels, 32U, 0x3ffU, 0xffc00U, 0x3ff00000U, 0xc0000000U), + (DxgiFormat.DxgiFormatR16G16Unorm, D3DFormat.D3DFormatG16R16, PixelFormatFlags.DdpfRgb, 32U, 0xffffU, 0xffff0000U, 0x0U, 0x0U), + (DxgiFormat.DxgiFormatB5G5R5A1Unorm, D3DFormat.D3DFormatA1R5G5B5, PixelFormatFlags.DdpfRgb | PixelFormatFlags.DdpfAlphaPixels, 16U, 0x7c00U, 0x3e0U, 0x1fU, 0x8000U), + (DxgiFormat.DxgiFormatB5G6R5Unorm, D3DFormat.D3DFormatR5G6B5, PixelFormatFlags.DdpfRgb, 16U, 0xf800U, 0x7e0U, 0x1fU, 0x0U), + (DxgiFormat.DxgiFormatA8Unorm, D3DFormat.D3DFormatA8, PixelFormatFlags.DdpfAlpha, 8U, 0x0U, 0x0U, 0x0U, 0xffU), + (DxgiFormat.DxgiFormatR8Unorm, D3DFormat.D3DFormatL8, PixelFormatFlags.DdpfLuminance, 8U, 0xffU, 0x0U, 0x0U, 0x0U), + (DxgiFormat.DxgiFormatR8G8Unorm, D3DFormat.D3DFormatA8L8, PixelFormatFlags.DdpfLuminance | PixelFormatFlags.DdpfAlphaPixels, 16U, 0xffU, 0x0U, 0x0U, 0xff00U), + (DxgiFormat.DxgiFormatUnknown, D3DFormat.D3DFormatA8R8G8B8, PixelFormatFlags.DdpfRgb | PixelFormatFlags.DdpfAlphaPixels, 32U, 0xff0000U, 0xff00U, 0xffU, 0xff000000U), + (DxgiFormat.DxgiFormatUnknown, D3DFormat.D3DFormatX8R8G8B8, PixelFormatFlags.DdpfRgb, 32U, 0xff0000U, 0xff00U, 0xffU, 0x0U), + (DxgiFormat.DxgiFormatUnknown, D3DFormat.D3DFormatX8B8G8R8, PixelFormatFlags.DdpfRgb, 32U, 0xffU, 0xff00U, 0xff0000U, 0x0U), + (DxgiFormat.DxgiFormatUnknown, D3DFormat.D3DFormatA2R10G10B10, PixelFormatFlags.DdpfRgb | PixelFormatFlags.DdpfAlphaPixels, 32U, 0x3ff00000U, 0xffc00U, 0x3ffU, 0xc0000000U), + (DxgiFormat.DxgiFormatUnknown, D3DFormat.D3DFormatR8G8B8, PixelFormatFlags.DdpfRgb, 24U, 0xff0000U, 0xff00U, 0xffU, 0x0U), + (DxgiFormat.DxgiFormatUnknown, D3DFormat.D3DFormatX1R5G5B5, PixelFormatFlags.DdpfRgb, 16U, 0x7c00U, 0x3e0U, 0x1fU, 0x0U), + (DxgiFormat.DxgiFormatUnknown, D3DFormat.D3DFormatA4R4G4B4, PixelFormatFlags.DdpfRgb | PixelFormatFlags.DdpfAlphaPixels, 16U, 0xf00U, 0xf0U, 0xfU, 0xf000U), + (DxgiFormat.DxgiFormatUnknown, D3DFormat.D3DFormatX4R4G4B4, PixelFormatFlags.DdpfRgb, 16U, 0xf00U, 0xf0U, 0xfU, 0x0U), + (DxgiFormat.DxgiFormatUnknown, D3DFormat.D3DFormatA8R3G3B2, PixelFormatFlags.DdpfRgb | PixelFormatFlags.DdpfAlphaPixels, 16U, 0xe0U, 0x1cU, 0x3U, 0xff00U), + (DxgiFormat.DxgiFormatUnknown, D3DFormat.D3DFormatL16, PixelFormatFlags.DdpfLuminance, 16U, 0xffffU, 0x0U, 0x0U, 0x0U), + (DxgiFormat.DxgiFormatIa44, D3DFormat.D3DFormatUnknown, PixelFormatFlags.DdpfLuminance | PixelFormatFlags.DdpfAlphaPixels, 8U, 0x0fU, 0x0U, 0x0U, 0xf0U), + (DxgiFormat.DxgiFormatUnknown, D3DFormat.D3DFormatA4L4, PixelFormatFlags.DdpfLuminance | PixelFormatFlags.DdpfAlphaPixels, 8U, 0xfU, 0x0U, 0x0U, 0xf0U) + }; + + /// + /// Mapping for FourCC codes to DXGI formats, with premultiplied alpha distinction. + /// DXT2 = Premultiplied DXT3 + /// DXT4 = Premultiplied DXT5 + /// + public static readonly (DxgiFormat dxgiFormat, uint standardFourCc, uint premultipliedFourCc)[] PremultipliedFourCcTable = new [] + { + (DxgiFormat.DxgiFormatBc2Unorm, Dxt3, Dxt2), + (DxgiFormat.DxgiFormatBc3Unorm, Dxt5, Dxt4), + }; + + /// + /// Mapping for FourCC codes to DXGI formats. + /// This is used for DDS files with FourCC codes. + /// + public static readonly (uint fourCc, DxgiFormat dxgiFormat)[] FourCcTable = new [] + { + // ATC formats. + (Atc, DxgiFormat.DxgiFormatAtcExt), + (Atca, DxgiFormat.DxgiFormatAtcExplicitAlphaExt), + (Atci, DxgiFormat.DxgiFormatAtcInterpolatedAlphaExt), + + // BC1 format. + (Dxt1, DxgiFormat.DxgiFormatBc1Unorm), + + // BC4 formats. Ati1 is earlier in the list for wider support, even if it doesn't differentiate between signed and unsigned + (Ati1, DxgiFormat.DxgiFormatBc4Unorm), + (Bc4S, DxgiFormat.DxgiFormatBc4Snorm), + (Bc4U, DxgiFormat.DxgiFormatBc4Unorm), + + // BC5 formats. Ati2 is higher up for the same reason. + (Ati2, DxgiFormat.DxgiFormatBc5Unorm), + (Bc5S, DxgiFormat.DxgiFormatBc5Snorm), + (Bc5U, DxgiFormat.DxgiFormatBc5Unorm), + + // YUV-like packed formats + (Rgbg, DxgiFormat.DxgiFormatR8G8B8G8Unorm), + (Grgb, DxgiFormat.DxgiFormatG8R8G8B8Unorm), + // Numeric format codes + (A16B16G16R16, DxgiFormat.DxgiFormatR16G16B16A16Unorm), + (Q16W16V16U16, DxgiFormat.DxgiFormatR16G16B16A16Snorm), + // Float formats + (R16F, DxgiFormat.DxgiFormatR16Float), + (G16R16F, DxgiFormat.DxgiFormatR16G16Float), + (A16B16G16R16F, DxgiFormat.DxgiFormatR16G16B16A16Float), + (R32F, DxgiFormat.DxgiFormatR32Float), + (G32R32F, DxgiFormat.DxgiFormatR32G32Float), + (A32B32G32R32F, DxgiFormat.DxgiFormatR32G32B32A32Float), + + // Unmapped formats + (CxV8U8, DxgiFormat.DxgiFormatUnknown), // Maps to D3DFMT_CxV8U8 + (Uyvy, DxgiFormat.DxgiFormatUnknown), // Maps to D3DFMT_UYVY + (Yuy2, DxgiFormat.DxgiFormatUnknown), // Maps to D3DFMT_YUY2 + }; + + public static uint MakeFourCc(char c0, char c1, char c2, char c3) + { + uint result = c0; + result |= (uint)c1 << 8; + result |= (uint)c2 << 16; + result |= (uint)c3 << 24; + return result; + } + + public uint dwSize; + public PixelFormatFlags dwFlags; + public uint dwFourCc; + public uint dwRgbBitCount; + public uint dwRBitMask; + public uint dwGBitMask; + public uint dwBBitMask; + public uint dwABitMask; + + /// + /// Attempt to determine the represented by the DDPixelFormat structure. + /// + /// The determined format, or if the format could not be determined. + /// The hint for the alpha channel of the format, or if the format could not be determined. + /// false if the format should be read from the DX10 header, true otherwise. + internal bool TryGetDx9(out CompressionFormat format, out AlphaChannelHint alphaChannelHint) + { + format = CompressionFormat.Unknown; + alphaChannelHint = AlphaChannelHint.Unknown; + + // Handle block-compressed formats with FourCC codes + if (dwFlags.HasFlag(PixelFormatFlags.DdpfFourcc)) + { + // If it's DX10, we can't determine the format from the pixel format alone + if (dwFourCc == Dx10) + { + return false; + } + + // Check against PremultipliedFourCcTable + foreach (var entry in PremultipliedFourCcTable) + { + if (dwFourCc == entry.standardFourCc || dwFourCc == entry.premultipliedFourCc) + { + // Convert from DxgiFormat to CompressionFormat + format = entry.dxgiFormat.ToCompressionFormat(); + alphaChannelHint = dwFourCc == entry.standardFourCc ? AlphaChannelHint.Straight : AlphaChannelHint.Premultiplied; + return true; + } + } + + // Check against rest of the FourCcs + foreach (var entry in FourCcTable) + { + if (dwFourCc == entry.fourCc) + { + // Convert from DxgiFormat to CompressionFormat + format = entry.dxgiFormat.ToCompressionFormat(); + alphaChannelHint = AlphaChannelHint.Straight; + return true; + } + } + } + else + { + // Handle non-FourCC formats using mapping table + foreach (var entry in MappingTable) + { + if (dwFlags == entry.flags && + dwRgbBitCount == entry.bitCount && + dwRBitMask == entry.rMask && + dwGBitMask == entry.gMask && + dwBBitMask == entry.bMask && + dwABitMask == entry.aMask) + { + if (entry.dxgiFormat != DxgiFormat.DxgiFormatUnknown) + { + // If there's a DXGI format, use that + format = entry.dxgiFormat.ToCompressionFormat(); + } + else + { + // Otherwise use the D3D format + format = entry.d3dFormat.ToCompressionFormat(); + } + + // If the format supports alpha, alpha channel is unknown, otherwise we know it's straight alpha. + alphaChannelHint = format.SupportsAlpha() ? AlphaChannelHint.Unknown : AlphaChannelHint.Straight; + + return true; + } + } + } + + // If we got here, we couldn't determine the format from the header, but we know it's not DX10. + return true; + } + + /// + /// Attempts to make a Direct3D 9 compatible pixel format structure from a compression format. + /// + /// The . + /// Unknown | Straight | Premultiplied + /// A Dx9-compatible filled structure, or Dx10 if not possible. + /// True if a Direct3D 9 compatible format was found, false if format requires DX10 extension. + internal static bool TryMakeDx9(CompressionFormat format, AlphaChannelHint alphaChannelHint, + out DdsPixelFormat pixelFormat) + { + // Initialize the pixel format structure + pixelFormat = new DdsPixelFormat + { + dwSize = 32 // Size of the structure + }; + + // Convert CompressionFormat to DxgiFormat to check formats in the table + DxgiFormat dxgiFormat = format.ToDxgiFormat(); + D3DFormat d3dFormat = format.ToD3DFormat(); + + // First check if we can use a FourCC code with premultiplied distinction + foreach (var mapping in PremultipliedFourCcTable) + { + if (mapping.dxgiFormat == dxgiFormat) + { + pixelFormat.dwFlags = PixelFormatFlags.DdpfFourcc; + // Use premultiplied FourCC if specified + pixelFormat.dwFourCc = alphaChannelHint == AlphaChannelHint.Premultiplied + ? mapping.premultipliedFourCc + : mapping.standardFourCc; + return true; + } + } + + // Next, check the normal FourCC table + foreach (var mapping in FourCcTable) + { + if (mapping.dxgiFormat == dxgiFormat) + { + pixelFormat.dwFlags = PixelFormatFlags.DdpfFourcc; + pixelFormat.dwFourCc = mapping.fourCc; + return true; + } + } + + // Try to find a match in the format mapping table for non-FourCC formats + foreach (var mapping in MappingTable) + { + // Check if either the DXGI format matches or the D3D format matches + if ((mapping.dxgiFormat != DxgiFormat.DxgiFormatUnknown && mapping.dxgiFormat == dxgiFormat) || + (mapping.d3dFormat != D3DFormat.D3DFormatUnknown && mapping.d3dFormat == d3dFormat)) + { + pixelFormat.dwFlags = mapping.flags; + pixelFormat.dwRgbBitCount = mapping.bitCount; + pixelFormat.dwRBitMask = mapping.rMask; + pixelFormat.dwGBitMask = mapping.gMask; + pixelFormat.dwBBitMask = mapping.bMask; + pixelFormat.dwABitMask = mapping.aMask; + return true; + } + } + + // If no appropriate format is found, use DX10 extension + pixelFormat.dwFlags = PixelFormatFlags.DdpfFourcc; + pixelFormat.dwFourCc = Dx10; + return false; + } + + public bool IsDxt10Format => (dwFlags & PixelFormatFlags.DdpfFourcc) == PixelFormatFlags.DdpfFourcc + && dwFourCc == Dx10; + } + + public struct DdsHeaderDx10 + { + public DxgiFormat dxgiFormat; + public D3D10ResourceDimension resourceDimension; + public uint miscFlag; + public uint arraySize; + public uint miscFlags2; + } + + /// + /// An array element in a texture array, or a face in a cubemap. + /// + public class DdsArrayElement + { + public uint Width { get; set; } + public uint Height { get; set; } + public uint Depth { get; set; } + public uint SizeInBytes { get; } + public DdsMipMap[] MipMaps { get; } + + public DdsArrayElement(uint width, uint height, uint depth, uint sizeInBytes, int numMipMaps) + { + Width = width; + Height = height; + Depth = depth; + SizeInBytes = sizeInBytes; + MipMaps = new DdsMipMap[numMipMaps]; + } + } + + public class DdsMipMap + { + public uint Width { get; set; } + public uint Height { get; set; } + public uint Depth { get; set; } + public uint SizeInBytes { get; } + public byte[] Data { get; } + + public DdsMipMap(byte[] data, uint width, uint height, uint depth) + { + Width = width; + Height = height; + Depth = depth; + SizeInBytes = (uint)data.Length; + Data = data; + } + } +} diff --git a/BCnEncoder.Net.TextureFormats/Dds/DxgiFormat.cs b/BCnEncoder.Net.TextureFormats/Dds/DxgiFormat.cs new file mode 100644 index 0000000..46e9965 --- /dev/null +++ b/BCnEncoder.Net.TextureFormats/Dds/DxgiFormat.cs @@ -0,0 +1,640 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using BCnEncoder.Shared; + +namespace BCnEncoder.TextureFormats +{ + /// + /// Flags to indicate which members contain valid data. + /// + [Flags] + public enum HeaderFlags : uint + { + /// + /// Required in every .dds file. + /// + DdsdCaps = 0x1, + /// + /// Required in every .dds file. + /// + DdsdHeight = 0x2, + /// + /// Required in every .dds file. + /// + DdsdWidth = 0x4, + /// + /// Required when pitch is provided for an uncompressed texture. + /// + DdsdPitch = 0x8, + /// + /// Required in every .dds file. + /// + DdsdPixelformat = 0x1000, + /// + /// Required in a mipmapped texture. + /// + DdsdMipmapcount = 0x20000, + /// + /// Required when pitch is provided for a compressed texture. + /// + DdsdLinearsize = 0x80000, + /// + /// Required in a depth texture. + /// + DdsdDepth = 0x800000, + + Required = DdsdCaps | DdsdHeight | DdsdWidth | DdsdPixelformat + } + + /// + /// Specifies the complexity of the surfaces stored. + /// + [Flags] + public enum HeaderCaps : uint + { + /// + /// Optional; must be used on any file that contains more than one surface (a mipmap, a cubic environment map, or mipmapped volume texture). + /// + DdscapsComplex = 0x8, + /// + /// Optional; should be used for a mipmap. + /// + DdscapsMipmap = 0x400000, + /// + /// Required + /// + DdscapsTexture = 0x1000 + } + + /// + /// Additional detail about the surfaces stored. + /// + [Flags] + public enum HeaderCaps2 : uint + { + /// + /// Required for a cube map. + /// + Ddscaps2Cubemap = 0x200, + /// + /// Required when these surfaces are stored in a cube map. + /// + Ddscaps2CubemapPositivex = 0x400, + /// + /// Required when these surfaces are stored in a cube map. + /// + Ddscaps2CubemapNegativex = 0x800, + /// + /// Required when these surfaces are stored in a cube map. + /// + Ddscaps2CubemapPositivey = 0x1000, + /// + /// Required when these surfaces are stored in a cube map. + /// + Ddscaps2CubemapNegativey = 0x2000, + /// + /// Required when these surfaces are stored in a cube map. + /// + Ddscaps2CubemapPositivez = 0x4000, + /// + /// Required when these surfaces are stored in a cube map. + /// + Ddscaps2CubemapNegativez = 0x8000, + /// + /// Required for a volume texture. + /// + Ddscaps2Volume = 0x200000 + } + + [Flags] + public enum PixelFormatFlags : uint + { + /// + /// Texture contains alpha data; dwRGBAlphaBitMask contains valid data. + /// + DdpfAlphaPixels = 0x1, + /// + /// Used in some older DDS files for alpha channel only uncompressed data (dwRGBBitCount contains the alpha channel bitcount; dwABitMask contains valid data) + /// + DdpfAlpha = 0x2, + /// + /// Texture contains compressed RGB data; dwFourCC contains valid data. + /// + DdpfFourcc = 0x4, + /// + /// Texture contains uncompressed RGB data; dwRGBBitCount and the RGB masks (dwRBitMask, dwGBitMask, dwBBitMask) contain valid data. + /// + DdpfRgb = 0x40, + /// + /// Used in some older DDS files for YUV uncompressed data (dwRGBBitCount contains the YUV bit count; dwRBitMask contains the Y mask, dwGBitMask contains the U mask, dwBBitMask contains the V mask) + /// + DdpfYuv = 0x200, + /// + /// Used in some older DDS files for single channel color uncompressed data (dwRGBBitCount contains the luminance channel bit count; dwRBitMask contains the channel mask). Can be combined with DDPF_ALPHAPIXELS for a two channel DDS file. + /// + DdpfLuminance = 0x20000, + DdpfRgba = DdpfRgb | DdpfAlphaPixels + } + + // Note only avaialble on DX12+ dds files. + // Any decoder that only support dx11 or lower, will fail. + public enum Dx10HeaderMiscFlags2 : uint + { + Dx10HeaderMiscFlags2AlphaModeUnknown = 0x0, + Dx10HeaderMiscFlags2AlphaModeStraight = 0x1, + Dx10HeaderMiscFlags2AlphaModePremultiplied = 0x2, + Dx10HeaderMiscFlags2AlphaModeOpaque = 0x3, + Dx10HeaderMiscFlags2AlphaModeCustom = 0x4 + } + + public enum D3D10ResourceDimension : uint + { + D3D10ResourceDimensionUnknown, + D3D10ResourceDimensionBuffer, + D3D10ResourceDimensionTexture1D, + D3D10ResourceDimensionTexture2D, + D3D10ResourceDimensionTexture3D + }; + + public enum DxgiFormat : uint + { + DxgiFormatUnknown, + DxgiFormatR32G32B32A32Typeless, + DxgiFormatR32G32B32A32Float, + DxgiFormatR32G32B32A32Uint, + DxgiFormatR32G32B32A32Sint, + DxgiFormatR32G32B32Typeless, + DxgiFormatR32G32B32Float, + DxgiFormatR32G32B32Uint, + DxgiFormatR32G32B32Sint, + DxgiFormatR16G16B16A16Typeless, + DxgiFormatR16G16B16A16Float, + DxgiFormatR16G16B16A16Unorm, + DxgiFormatR16G16B16A16Uint, + DxgiFormatR16G16B16A16Snorm, + DxgiFormatR16G16B16A16Sint, + DxgiFormatR32G32Typeless, + DxgiFormatR32G32Float, + DxgiFormatR32G32Uint, + DxgiFormatR32G32Sint, + DxgiFormatR32G8X24Typeless, + DxgiFormatD32FloatS8X24Uint, + DxgiFormatR32FloatX8X24Typeless, + DxgiFormatX32TypelessG8X24Uint, + DxgiFormatR10G10B10A2Typeless, + DxgiFormatR10G10B10A2Unorm, + DxgiFormatR10G10B10A2Uint, + DxgiFormatR11G11B10Float, + DxgiFormatR8G8B8A8Typeless, + DxgiFormatR8G8B8A8Unorm, + DxgiFormatR8G8B8A8UnormSrgb, + DxgiFormatR8G8B8A8Uint, + DxgiFormatR8G8B8A8Snorm, + DxgiFormatR8G8B8A8Sint, + DxgiFormatR16G16Typeless, + DxgiFormatR16G16Float, + DxgiFormatR16G16Unorm, + DxgiFormatR16G16Uint, + DxgiFormatR16G16Snorm, + DxgiFormatR16G16Sint, + DxgiFormatR32Typeless, + DxgiFormatD32Float, + DxgiFormatR32Float, + DxgiFormatR32Uint, + DxgiFormatR32Sint, + DxgiFormatR24G8Typeless, + DxgiFormatD24UnormS8Uint, + DxgiFormatR24UnormX8Typeless, + DxgiFormatX24TypelessG8Uint, + DxgiFormatR8G8Typeless, + DxgiFormatR8G8Unorm, + DxgiFormatR8G8Uint, + DxgiFormatR8G8Snorm, + DxgiFormatR8G8Sint, + DxgiFormatR16Typeless, + DxgiFormatR16Float, + DxgiFormatD16Unorm, + DxgiFormatR16Unorm, + DxgiFormatR16Uint, + DxgiFormatR16Snorm, + DxgiFormatR16Sint, + DxgiFormatR8Typeless, + DxgiFormatR8Unorm, + DxgiFormatR8Uint, + DxgiFormatR8Snorm, + DxgiFormatR8Sint, + DxgiFormatA8Unorm, + DxgiFormatR1Unorm, + DxgiFormatR9G9B9E5Sharedexp, + DxgiFormatR8G8B8G8Unorm, + DxgiFormatG8R8G8B8Unorm, + DxgiFormatBc1Typeless, + DxgiFormatBc1Unorm, + DxgiFormatBc1UnormSrgb, + DxgiFormatBc2Typeless, + DxgiFormatBc2Unorm, + DxgiFormatBc2UnormSrgb, + DxgiFormatBc3Typeless, + DxgiFormatBc3Unorm, + DxgiFormatBc3UnormSrgb, + DxgiFormatBc4Typeless, + DxgiFormatBc4Unorm, + DxgiFormatBc4Snorm, + DxgiFormatBc5Typeless, + DxgiFormatBc5Unorm, + DxgiFormatBc5Snorm, + DxgiFormatB5G6R5Unorm, + DxgiFormatB5G5R5A1Unorm, + DxgiFormatB8G8R8A8Unorm, + DxgiFormatB8G8R8X8Unorm, + DxgiFormatR10G10B10XrBiasA2Unorm, + DxgiFormatB8G8R8A8Typeless, + DxgiFormatB8G8R8A8UnormSrgb, + DxgiFormatB8G8R8X8Typeless, + DxgiFormatB8G8R8X8UnormSrgb, + DxgiFormatBc6HTypeless, + DxgiFormatBc6HUf16, + DxgiFormatBc6HSf16, + DxgiFormatBc7Typeless, + DxgiFormatBc7Unorm, + DxgiFormatBc7UnormSrgb, + DxgiFormatAyuv, + DxgiFormatY410, + DxgiFormatY416, + DxgiFormatNv12, + DxgiFormatP010, + DxgiFormatP016, + DxgiFormat420Opaque, + DxgiFormatYuy2, + DxgiFormatY210, + DxgiFormatY216, + DxgiFormatNv11, + DxgiFormatAi44, + DxgiFormatIa44, + DxgiFormatP8, + DxgiFormatA8P8, + DxgiFormatB4G4R4A4Unorm, + DxgiFormatP208, + DxgiFormatV208, + DxgiFormatV408, + DxgiFormatForceUint, + + // Added here due to lack of an official value + DxgiFormatAtcExt = 300, + DxgiFormatAtcExplicitAlphaExt, + DxgiFormatAtcInterpolatedAlphaExt + }; + + public static class DxgiFormatExtensions + { + public static int GetByteSize(this DxgiFormat format) + { + switch (format) + { + case DxgiFormat.DxgiFormatUnknown: + return 4; + case DxgiFormat.DxgiFormatR32G32B32A32Typeless: + return 4 * 4; + case DxgiFormat.DxgiFormatR32G32B32A32Float: + return 4 * 4; + case DxgiFormat.DxgiFormatR32G32B32A32Uint: + return 4 * 4; + case DxgiFormat.DxgiFormatR32G32B32A32Sint: + return 4 * 4; + case DxgiFormat.DxgiFormatR32G32B32Typeless: + return 4 * 3; + case DxgiFormat.DxgiFormatR32G32B32Float: + return 4 * 3; + case DxgiFormat.DxgiFormatR32G32B32Uint: + return 4 * 3; + case DxgiFormat.DxgiFormatR32G32B32Sint: + return 4 * 3; + case DxgiFormat.DxgiFormatR16G16B16A16Typeless: + return 4 * 2; + case DxgiFormat.DxgiFormatR16G16B16A16Float: + return 4 * 2; + case DxgiFormat.DxgiFormatR16G16B16A16Unorm: + return 4 * 2; + case DxgiFormat.DxgiFormatR16G16B16A16Uint: + return 4 * 2; + case DxgiFormat.DxgiFormatR16G16B16A16Snorm: + return 4 * 2; + case DxgiFormat.DxgiFormatR16G16B16A16Sint: + return 4 * 2; + case DxgiFormat.DxgiFormatR32G32Typeless: + return 4 * 2; + case DxgiFormat.DxgiFormatR32G32Float: + return 4 * 2; + case DxgiFormat.DxgiFormatR32G32Uint: + return 4 * 2; + case DxgiFormat.DxgiFormatR32G32Sint: + return 4 * 2; + case DxgiFormat.DxgiFormatR32G8X24Typeless: + return 4 * 2; + case DxgiFormat.DxgiFormatD32FloatS8X24Uint: + return 4; + case DxgiFormat.DxgiFormatR32FloatX8X24Typeless: + return 4; + case DxgiFormat.DxgiFormatX32TypelessG8X24Uint: + return 4; + case DxgiFormat.DxgiFormatR10G10B10A2Typeless: + return 4; + case DxgiFormat.DxgiFormatR10G10B10A2Unorm: + return 4; + case DxgiFormat.DxgiFormatR10G10B10A2Uint: + return 4; + case DxgiFormat.DxgiFormatR11G11B10Float: + return 4; + case DxgiFormat.DxgiFormatR8G8B8A8Typeless: + return 4; + case DxgiFormat.DxgiFormatR8G8B8A8Unorm: + return 4; + case DxgiFormat.DxgiFormatR8G8B8A8UnormSrgb: + return 4; + case DxgiFormat.DxgiFormatR8G8B8A8Uint: + return 4; + case DxgiFormat.DxgiFormatR8G8B8A8Snorm: + return 4; + case DxgiFormat.DxgiFormatR8G8B8A8Sint: + return 4; + case DxgiFormat.DxgiFormatR16G16Typeless: + return 4; + case DxgiFormat.DxgiFormatR16G16Float: + return 4; + case DxgiFormat.DxgiFormatR16G16Unorm: + return 4; + case DxgiFormat.DxgiFormatR16G16Uint: + return 4; + case DxgiFormat.DxgiFormatR16G16Snorm: + return 4; + case DxgiFormat.DxgiFormatR16G16Sint: + return 4; + case DxgiFormat.DxgiFormatR32Typeless: + return 4; + case DxgiFormat.DxgiFormatD32Float: + return 4; + case DxgiFormat.DxgiFormatR32Float: + return 4; + case DxgiFormat.DxgiFormatR32Uint: + return 4; + case DxgiFormat.DxgiFormatR32Sint: + return 4; + case DxgiFormat.DxgiFormatR24G8Typeless: + return 4; + case DxgiFormat.DxgiFormatD24UnormS8Uint: + return 4; + case DxgiFormat.DxgiFormatR24UnormX8Typeless: + return 4; + case DxgiFormat.DxgiFormatX24TypelessG8Uint: + return 4; + case DxgiFormat.DxgiFormatR8G8Typeless: + return 2; + case DxgiFormat.DxgiFormatR8G8Unorm: + return 2; + case DxgiFormat.DxgiFormatR8G8Uint: + return 2; + case DxgiFormat.DxgiFormatR8G8Snorm: + return 2; + case DxgiFormat.DxgiFormatR8G8Sint: + return 2; + case DxgiFormat.DxgiFormatR16Typeless: + return 2; + case DxgiFormat.DxgiFormatR16Float: + return 2; + case DxgiFormat.DxgiFormatD16Unorm: + return 2; + case DxgiFormat.DxgiFormatR16Unorm: + return 2; + case DxgiFormat.DxgiFormatR16Uint: + return 2; + case DxgiFormat.DxgiFormatR16Snorm: + return 2; + case DxgiFormat.DxgiFormatR16Sint: + return 2; + case DxgiFormat.DxgiFormatIa44: + return 1; + case DxgiFormat.DxgiFormatR8Typeless: + return 1; + case DxgiFormat.DxgiFormatR8Unorm: + return 1; + case DxgiFormat.DxgiFormatR8Uint: + return 1; + case DxgiFormat.DxgiFormatR8Snorm: + return 1; + case DxgiFormat.DxgiFormatR8Sint: + return 1; + case DxgiFormat.DxgiFormatA8Unorm: + return 1; + case DxgiFormat.DxgiFormatR1Unorm: + return 1; + case DxgiFormat.DxgiFormatR9G9B9E5Sharedexp: + return 4; + case DxgiFormat.DxgiFormatR8G8B8G8Unorm: + return 4; + case DxgiFormat.DxgiFormatG8R8G8B8Unorm: + return 4; + case DxgiFormat.DxgiFormatBc1Typeless: + return 8; + case DxgiFormat.DxgiFormatBc1Unorm: + return 8; + case DxgiFormat.DxgiFormatBc1UnormSrgb: + return 8; + case DxgiFormat.DxgiFormatBc2Typeless: + return 16; + case DxgiFormat.DxgiFormatBc2Unorm: + return 16; + case DxgiFormat.DxgiFormatBc2UnormSrgb: + return 16; + case DxgiFormat.DxgiFormatBc3Typeless: + return 16; + case DxgiFormat.DxgiFormatBc3Unorm: + return 16; + case DxgiFormat.DxgiFormatBc3UnormSrgb: + return 16; + case DxgiFormat.DxgiFormatBc4Typeless: + return 8; + case DxgiFormat.DxgiFormatBc4Unorm: + return 8; + case DxgiFormat.DxgiFormatBc4Snorm: + return 8; + case DxgiFormat.DxgiFormatBc5Typeless: + return 16; + case DxgiFormat.DxgiFormatBc5Unorm: + return 16; + case DxgiFormat.DxgiFormatBc5Snorm: + return 16; + case DxgiFormat.DxgiFormatB5G6R5Unorm: + return 2; + case DxgiFormat.DxgiFormatB5G5R5A1Unorm: + return 2; + case DxgiFormat.DxgiFormatB8G8R8A8Unorm: + return 4; + case DxgiFormat.DxgiFormatB8G8R8X8Unorm: + return 4; + case DxgiFormat.DxgiFormatR10G10B10XrBiasA2Unorm: + return 4; + case DxgiFormat.DxgiFormatB8G8R8A8Typeless: + return 4; + case DxgiFormat.DxgiFormatB8G8R8A8UnormSrgb: + return 4; + case DxgiFormat.DxgiFormatB8G8R8X8Typeless: + return 4; + case DxgiFormat.DxgiFormatB8G8R8X8UnormSrgb: + return 4; + case DxgiFormat.DxgiFormatBc6HTypeless: + return 16; + case DxgiFormat.DxgiFormatBc6HUf16: + return 16; + case DxgiFormat.DxgiFormatBc6HSf16: + return 16; + case DxgiFormat.DxgiFormatBc7Typeless: + return 16; + case DxgiFormat.DxgiFormatBc7Unorm: + return 16; + case DxgiFormat.DxgiFormatBc7UnormSrgb: + return 16; + case DxgiFormat.DxgiFormatP8: + return 1; + case DxgiFormat.DxgiFormatA8P8: + return 2; + case DxgiFormat.DxgiFormatB4G4R4A4Unorm: + return 2; + case DxgiFormat.DxgiFormatAtcExt: + return 8; + case DxgiFormat.DxgiFormatAtcExplicitAlphaExt: + return 16; + case DxgiFormat.DxgiFormatAtcInterpolatedAlphaExt: + return 16; + } + return 4; + } + + public static bool IsCompressedFormat(this DxgiFormat format) + { + switch (format) + { + case DxgiFormat.DxgiFormatBc1Typeless: + case DxgiFormat.DxgiFormatBc1Unorm: + case DxgiFormat.DxgiFormatBc1UnormSrgb: + case DxgiFormat.DxgiFormatBc2Typeless: + case DxgiFormat.DxgiFormatBc2Unorm: + case DxgiFormat.DxgiFormatBc2UnormSrgb: + case DxgiFormat.DxgiFormatBc3Typeless: + case DxgiFormat.DxgiFormatBc3Unorm: + case DxgiFormat.DxgiFormatBc3UnormSrgb: + case DxgiFormat.DxgiFormatBc4Typeless: + case DxgiFormat.DxgiFormatBc4Unorm: + case DxgiFormat.DxgiFormatBc4Snorm: + case DxgiFormat.DxgiFormatBc5Typeless: + case DxgiFormat.DxgiFormatBc5Unorm: + case DxgiFormat.DxgiFormatBc5Snorm: + case DxgiFormat.DxgiFormatBc6HTypeless: + case DxgiFormat.DxgiFormatBc6HUf16: + case DxgiFormat.DxgiFormatBc6HSf16: + case DxgiFormat.DxgiFormatBc7Typeless: + case DxgiFormat.DxgiFormatBc7Unorm: + case DxgiFormat.DxgiFormatBc7UnormSrgb: + case DxgiFormat.DxgiFormatAtcExt: + case DxgiFormat.DxgiFormatAtcExplicitAlphaExt: + case DxgiFormat.DxgiFormatAtcInterpolatedAlphaExt: + return true; + + default: + return false; + } + } + + public static DxgiFormat ToDxgiFormat(this CompressionFormat format) => + FormatMapping.TryGetValue(format, out var dxgiFormat) ? dxgiFormat : DxgiFormat.DxgiFormatUnknown; + + public static CompressionFormat ToCompressionFormat(this DxgiFormat format) => + FormatMappingReverse.TryGetValue(format, out var bcnFormat) ? bcnFormat : CompressionFormat.Unknown; + + private static Dictionary FormatMapping { get; } = + new() { + // Red-only formats + { CompressionFormat.R8, DxgiFormat.DxgiFormatR8Unorm }, + { CompressionFormat.R8S, DxgiFormat.DxgiFormatR8Snorm }, + { CompressionFormat.R16, DxgiFormat.DxgiFormatR16Unorm }, + { CompressionFormat.R16S, DxgiFormat.DxgiFormatR16Snorm }, + { CompressionFormat.R16F, DxgiFormat.DxgiFormatR16Float }, + { CompressionFormat.R32F, DxgiFormat.DxgiFormatR32Float }, + + // Red-green formats + { CompressionFormat.R4G4_Packed, DxgiFormat.DxgiFormatIa44 }, + { CompressionFormat.R8G8, DxgiFormat.DxgiFormatR8G8Unorm }, + { CompressionFormat.R8G8S, DxgiFormat.DxgiFormatR8G8Snorm }, + { CompressionFormat.R16G16, DxgiFormat.DxgiFormatR16G16Unorm }, + { CompressionFormat.R16G16S, DxgiFormat.DxgiFormatR16G16Snorm }, + { CompressionFormat.R16G16F, DxgiFormat.DxgiFormatR16G16Float }, + { CompressionFormat.R32G32F, DxgiFormat.DxgiFormatR32G32Float }, + + // RGB formats + { CompressionFormat.RgbFloat, DxgiFormat.DxgiFormatR32G32B32Float }, + { CompressionFormat.R11G11B10UF_Packed, DxgiFormat.DxgiFormatR11G11B10Float }, + { CompressionFormat.R9G9B9E5_Packed, DxgiFormat.DxgiFormatR9G9B9E5Sharedexp }, + { CompressionFormat.R32G32B32UI, DxgiFormat.DxgiFormatR32G32B32Uint }, + + // BGR formats + { CompressionFormat.B5G6R5_Packed, DxgiFormat.DxgiFormatB5G6R5Unorm }, + { CompressionFormat.Bgr24x8, DxgiFormat.DxgiFormatB8G8R8X8Unorm }, + { CompressionFormat.Bgr24x8_sRGB, DxgiFormat.DxgiFormatB8G8R8X8UnormSrgb }, + + // RGBA formats + { CompressionFormat.R10G10B10A2_Packed, DxgiFormat.DxgiFormatR10G10B10A2Unorm }, + { CompressionFormat.Rgba32, DxgiFormat.DxgiFormatR8G8B8A8Unorm }, + { CompressionFormat.Rgba32_sRGB, DxgiFormat.DxgiFormatR8G8B8A8UnormSrgb }, + { CompressionFormat.RgbaFloat, DxgiFormat.DxgiFormatR32G32B32A32Float }, + { CompressionFormat.RgbaHalf, DxgiFormat.DxgiFormatR16G16B16A16Float }, + { CompressionFormat.R16G16B16A16, DxgiFormat.DxgiFormatR16G16B16A16Unorm }, + { CompressionFormat.R32G32B32A32UI, DxgiFormat.DxgiFormatR32G32B32A32Uint }, + + // BGRA formats + { CompressionFormat.B5G5R5A1_Packed, DxgiFormat.DxgiFormatB5G5R5A1Unorm }, + { CompressionFormat.B4G4R4A4_Packed, DxgiFormat.DxgiFormatB4G4R4A4Unorm }, + { CompressionFormat.Bgra32, DxgiFormat.DxgiFormatB8G8R8A8Unorm }, + { CompressionFormat.Bgra32_sRGB, DxgiFormat.DxgiFormatB8G8R8A8UnormSrgb }, + + // RGB formats without direct DXGI equivalents + // { CompressionFormat.Rgb24, DxgiFormat.DxgiFormatUnknown }, + // { CompressionFormat.Rgb24_sRGB, DxgiFormat.DxgiFormatUnknown }, + // { CompressionFormat.Bgr24, DxgiFormat.DxgiFormatUnknown }, + // { CompressionFormat.Bgr24_sRGB, DxgiFormat.DxgiFormatUnknown }, + // { CompressionFormat.Rgbe, DxgiFormat.DxgiFormatUnknown }, + // { CompressionFormat.Xyze, DxgiFormat.DxgiFormatUnknown }, + // { CompressionFormat.RgbHalf, DxgiFormat.DxgiFormatUnknown }, + + // BC formats + { CompressionFormat.Bc1, DxgiFormat.DxgiFormatBc1Unorm }, + { CompressionFormat.Bc1_sRGB, DxgiFormat.DxgiFormatBc1UnormSrgb }, + { CompressionFormat.Bc1WithAlpha, DxgiFormat.DxgiFormatBc1Unorm }, + { CompressionFormat.Bc1WithAlpha_sRGB, DxgiFormat.DxgiFormatBc1UnormSrgb }, + { CompressionFormat.Bc2, DxgiFormat.DxgiFormatBc2Unorm }, + { CompressionFormat.Bc2_sRGB, DxgiFormat.DxgiFormatBc2UnormSrgb }, + { CompressionFormat.Bc3, DxgiFormat.DxgiFormatBc3Unorm }, + { CompressionFormat.Bc3_sRGB, DxgiFormat.DxgiFormatBc3UnormSrgb }, + { CompressionFormat.Bc4, DxgiFormat.DxgiFormatBc4Unorm }, + { CompressionFormat.Bc4S, DxgiFormat.DxgiFormatBc4Snorm }, + { CompressionFormat.Bc5, DxgiFormat.DxgiFormatBc5Unorm }, + { CompressionFormat.Bc5S, DxgiFormat.DxgiFormatBc5Snorm }, + { CompressionFormat.Bc6U, DxgiFormat.DxgiFormatBc6HUf16 }, + { CompressionFormat.Bc6S, DxgiFormat.DxgiFormatBc6HSf16 }, + { CompressionFormat.Bc7, DxgiFormat.DxgiFormatBc7Unorm }, + { CompressionFormat.Bc7_sRGB, DxgiFormat.DxgiFormatBc7UnormSrgb }, + + // ATC formats + { CompressionFormat.Atc, DxgiFormat.DxgiFormatAtcExt }, + { CompressionFormat.AtcExplicitAlpha, DxgiFormat.DxgiFormatAtcExplicitAlphaExt }, + { CompressionFormat.AtcInterpolatedAlpha, DxgiFormat.DxgiFormatAtcInterpolatedAlphaExt } + }; + + private static Dictionary FormatMappingReverse { get; } = + FormatMapping.Where(x => + // Filter out formats that map to the same DXGI format + x.Key != CompressionFormat.Bc1WithAlpha && + x.Key != CompressionFormat.Bc1WithAlpha_sRGB + ).ToDictionary(x => x.Value, x => x.Key); + } +} diff --git a/BCnEncoder.Net.TextureFormats/Extensions.cs b/BCnEncoder.Net.TextureFormats/Extensions.cs new file mode 100644 index 0000000..4d53b54 --- /dev/null +++ b/BCnEncoder.Net.TextureFormats/Extensions.cs @@ -0,0 +1,672 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using BCnEncoder.Decoder; +using BCnEncoder.Encoder; +using BCnEncoder.Encoder.Options; +using BCnEncoder.Shared; +using BCnEncoder.Shared.Colors; +using CommunityToolkit.HighPerformance; + +namespace BCnEncoder.TextureFormats +{ + public static class EncoderExtensions + { + /// + /// Encodes all mipmap levels into a texture file container. + /// + /// The input to encode represented by a . + public static TTextureFormat EncodeToTexture(this BcEncoder encoder, BCnTextureData inputData) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (inputData.IsCubeMap && !tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + + var encoded = encoder.Encode(inputData); + tex.FromTextureData(encoded); + return tex; + } + + /// + /// Encodes all mipmap levels into a texture file container. + /// + /// The input to encode represented by a . + public static async Task EncodeToTextureAsync(this BcEncoder encoder, BCnTextureData inputData, CancellationToken token = default) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (inputData.IsCubeMap && !tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + + var encoded = await encoder.EncodeAsync(inputData, token); + tex.FromTextureData(encoded); + return tex; + } + + /// + /// Encodes all mipmap levels into a texture file and writes it to the output stream. + /// + /// The input to encode represented by a . + /// The stream to write the encoded image to. + public static void EncodeToStream(this BcEncoder encoder, BCnTextureData inputData, Stream outputStream) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (inputData.IsCubeMap && !tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + + var encoded = encoder.Encode(inputData); + tex.FromTextureData(encoded); + tex.WriteToStream(outputStream); + } + + /// + /// Encodes all mipmap levels into a texture file and writes it to the output stream. + /// + /// The input to encode represented by a . + /// The stream to write the encoded image to. + public static async Task EncodeToStreamAsync(this BcEncoder encoder, BCnTextureData inputData, Stream outputStream, CancellationToken token = default) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (inputData.IsCubeMap && !tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + + var encoded = await encoder.EncodeAsync(inputData, token); + tex.FromTextureData(encoded); + tex.WriteToStream(outputStream); + } + + /// + /// Encodes all mipmap levels into a texture file container. + /// + /// The input to encode represented by a . + public static TTextureFormat EncodeToTextureHdr(this BcEncoder encoder, ReadOnlyMemory2D input) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsHdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support HDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + + var encoded = encoder.EncodeHdr(input); + tex.FromTextureData(encoded); + return tex; + } + + /// + /// Encodes all mipmap levels into a texture file container. + /// + /// The input to encode represented by a . + public static async Task EncodeToTextureHdrAsync(this BcEncoder encoder, ReadOnlyMemory2D input, CancellationToken token = default) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsHdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support HDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + + var encoded = await encoder.EncodeHdrAsync(input, token); + tex.FromTextureData(encoded); + return tex; + } + + /// + /// Encodes all mipmap levels into a texture file container. + /// + /// The input to encode represented by a . + public static TTextureFormat EncodeToTexture(this BcEncoder encoder, ReadOnlyMemory2D input) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + if (!tex.SupportsLdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support LDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + var encoded = encoder.Encode(input); + tex.FromTextureData(encoded); + return tex; + } + + /// + /// Encodes all mipmap levels into a texture file container. + /// + /// The input to encode represented by a . + public static async Task EncodeToTextureAsync(this BcEncoder encoder, ReadOnlyMemory2D input, CancellationToken token = default) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + if (!tex.SupportsLdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support LDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + var encoded = await encoder.EncodeAsync(input, token); + tex.FromTextureData(encoded); + return tex; + } + + /// + /// Encodes all mipmap levels into a texture file and writes it to the output stream. + /// + /// The input to encode represented by a . + /// The stream to write the encoded image to. + public static void EncodeToStreamHdr(this BcEncoder encoder, ReadOnlyMemory2D input, Stream outputStream) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + if (!tex.SupportsHdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support HDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + var encoded = encoder.EncodeHdr(input); + tex.FromTextureData(encoded); + tex.WriteToStream(outputStream); + } + + /// + /// Encodes all mipmap levels into a texture file and writes it to the output stream. + /// + /// The input to encode represented by a . + /// The stream to write the encoded image to. + public static async Task EncodeToStreamHdrAsync(this BcEncoder encoder, ReadOnlyMemory2D input, Stream outputStream, CancellationToken token = default) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + if (!tex.SupportsHdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support HDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + var encoded = await encoder.EncodeHdrAsync(input, token); + tex.FromTextureData(encoded); + tex.WriteToStream(outputStream); + } + + /// + /// Encodes all mipmap levels into a texture file and writes it to the output stream. + /// + /// The input to encode represented by a . + /// The stream to write the encoded image to. + public static void EncodeToStream(this BcEncoder encoder, ReadOnlyMemory2D input, Stream outputStream) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + if (!tex.SupportsLdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support LDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + var encoded = encoder.Encode(input); + tex.FromTextureData(encoded); + tex.WriteToStream(outputStream); + } + + /// + /// Encodes all mipmap levels into a texture file and writes it to the output stream. + /// + /// The input to encode represented by a . + /// The stream to write the encoded image to. + public static async Task EncodeToStreamAsync(this BcEncoder encoder, ReadOnlyMemory2D input, Stream outputStream, CancellationToken token = default) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + if (!tex.SupportsLdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support LDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + var encoded = await encoder.EncodeAsync(input, token); + tex.FromTextureData(encoded); + tex.WriteToStream(outputStream); + } + + + + + + + /// + /// Encodes all mipMaps of a cubeMap to a stream either in the specified texture format. + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// + /// The positive X-axis face of the cubeMap + /// The negative X-axis face of the cubeMap + /// The positive Y-axis face of the cubeMap + /// The negative Y-axis face of the cubeMap + /// The positive Z-axis face of the cubeMap + /// The negative Z-axis face of the cubeMap + public static TTextureFormat EncodeCubeMapToTextureHdr(this BcEncoder encoder, + ReadOnlyMemory2D right, ReadOnlyMemory2D left, + ReadOnlyMemory2D top, ReadOnlyMemory2D down, + ReadOnlyMemory2D back, ReadOnlyMemory2D front) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.SupportsHdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support HDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + + var encoded = encoder.EncodeCubeMapHdr(right, left, top, down, back, front); + tex.FromTextureData(encoded); + return tex; + } + + /// + /// Encodes all mipMaps of a cubeMap to a stream either in the specified texture format. + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// + /// The positive X-axis face of the cubeMap + /// The negative X-axis face of the cubeMap + /// The positive Y-axis face of the cubeMap + /// The negative Y-axis face of the cubeMap + /// The positive Z-axis face of the cubeMap + /// The negative Z-axis face of the cubeMap + public static async Task EncodeCubeMapToTextureHdrAsync(this BcEncoder encoder, + ReadOnlyMemory2D right, ReadOnlyMemory2D left, + ReadOnlyMemory2D top, ReadOnlyMemory2D down, + ReadOnlyMemory2D back, ReadOnlyMemory2D front, CancellationToken token = default) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.SupportsHdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support HDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + + var encoded = await encoder.EncodeCubeMapHdrAsync(right, left, top, down, back, front, token); + tex.FromTextureData(encoded); + return tex; + } + + /// + /// Encodes all mipMaps of a cubeMap to a stream either in the specified texture format. + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// + /// The positive X-axis face of the cubeMap + /// The negative X-axis face of the cubeMap + /// The positive Y-axis face of the cubeMap + /// The negative Y-axis face of the cubeMap + /// The positive Z-axis face of the cubeMap + /// The negative Z-axis face of the cubeMap + public static TTextureFormat EncodeCubeMapToTexture(this BcEncoder encoder, + ReadOnlyMemory2D right, ReadOnlyMemory2D left, + ReadOnlyMemory2D top, ReadOnlyMemory2D down, + ReadOnlyMemory2D back, ReadOnlyMemory2D front) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.SupportsLdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support LDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + var encoded = encoder.EncodeCubeMap(right, left, top, down, back, front); + tex.FromTextureData(encoded); + return tex; + } + + /// + /// Encodes all mipMaps of a cubeMap to a stream either in the specified texture format. + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// + /// The positive X-axis face of the cubeMap + /// The negative X-axis face of the cubeMap + /// The positive Y-axis face of the cubeMap + /// The negative Y-axis face of the cubeMap + /// The positive Z-axis face of the cubeMap + /// The negative Z-axis face of the cubeMap + public static async Task EncodeCubeMapToTextureAsync(this BcEncoder encoder, + ReadOnlyMemory2D right, ReadOnlyMemory2D left, + ReadOnlyMemory2D top, ReadOnlyMemory2D down, + ReadOnlyMemory2D back, ReadOnlyMemory2D front, CancellationToken token = default) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.SupportsLdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support LDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + var encoded = await encoder.EncodeCubeMapAsync(right, left, top, down, back, front, token); + tex.FromTextureData(encoded); + return tex; + } + + /// + /// Encodes all mipMaps of a cubeMap to a stream either in the specified texture format. + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// + /// The positive X-axis face of the cubeMap + /// The negative X-axis face of the cubeMap + /// The positive Y-axis face of the cubeMap + /// The negative Y-axis face of the cubeMap + /// The positive Z-axis face of the cubeMap + /// The negative Z-axis face of the cubeMap + /// The stream to write the encoded image to. + public static void EncodeCubeMapToStreamHdr(this BcEncoder encoder, + ReadOnlyMemory2D right, ReadOnlyMemory2D left, + ReadOnlyMemory2D top, ReadOnlyMemory2D down, + ReadOnlyMemory2D back, ReadOnlyMemory2D front, Stream outputStream) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.SupportsHdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support HDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + var encoded = encoder.EncodeCubeMapHdr(right, left, top, down, back, front); + tex.FromTextureData(encoded); + tex.WriteToStream(outputStream); + } + + /// + /// Encodes all mipMaps of a cubeMap to a stream either in the specified texture format. + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// + /// The positive X-axis face of the cubeMap + /// The negative X-axis face of the cubeMap + /// The positive Y-axis face of the cubeMap + /// The negative Y-axis face of the cubeMap + /// The positive Z-axis face of the cubeMap + /// The negative Z-axis face of the cubeMap + /// The stream to write the encoded image to. + public static async Task EncodeCubeMapToStreamHdrAsync(this BcEncoder encoder, + ReadOnlyMemory2D right, ReadOnlyMemory2D left, + ReadOnlyMemory2D top, ReadOnlyMemory2D down, + ReadOnlyMemory2D back, ReadOnlyMemory2D front, Stream outputStream, CancellationToken token = default) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.SupportsHdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support HDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + var encoded = await encoder.EncodeCubeMapHdrAsync(right, left, top, down, back, front, token); + tex.FromTextureData(encoded); + tex.WriteToStream(outputStream); + } + + /// + /// Encodes all mipMaps of a cubeMap to a stream either in the specified texture format. + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// + /// The positive X-axis face of the cubeMap + /// The negative X-axis face of the cubeMap + /// The positive Y-axis face of the cubeMap + /// The negative Y-axis face of the cubeMap + /// The positive Z-axis face of the cubeMap + /// The negative Z-axis face of the cubeMap + /// The stream to write the encoded image to. + public static void EncodeCubeMapToStream(this BcEncoder encoder, + ReadOnlyMemory2D right, ReadOnlyMemory2D left, + ReadOnlyMemory2D top, ReadOnlyMemory2D down, + ReadOnlyMemory2D back, ReadOnlyMemory2D front, Stream outputStream) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.SupportsLdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support LDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + + var encoded = encoder.EncodeCubeMap(right, left, top, down, back, front); + tex.FromTextureData(encoded); + tex.WriteToStream(outputStream); + } + + /// + /// Encodes all mipMaps of a cubeMap to a stream either in the specified texture format. + /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. + /// + /// The positive X-axis face of the cubeMap + /// The negative X-axis face of the cubeMap + /// The positive Y-axis face of the cubeMap + /// The negative Y-axis face of the cubeMap + /// The positive Z-axis face of the cubeMap + /// The negative Z-axis face of the cubeMap + /// The stream to write the encoded image to. + public static async Task EncodeCubeMapToStreamAsync(this BcEncoder encoder, + ReadOnlyMemory2D right, ReadOnlyMemory2D left, + ReadOnlyMemory2D top, ReadOnlyMemory2D down, + ReadOnlyMemory2D back, ReadOnlyMemory2D front, Stream outputStream, CancellationToken token = default) + where TTextureFormat : class, ITextureFileFormat, new() + { + var tex = new TTextureFormat(); + + if (!tex.SupportsCubeMap) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support cubemaps!"); + } + if (!tex.SupportsLdr) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support LDR formats!"); + } + if (!tex.IsSupportedFormat(encoder.OutputOptions.Format)) + { + throw new InvalidOperationException( + $"{typeof(TTextureFormat).Name} does not support the {encoder.OutputOptions.Format} format!"); + } + var encoded = await encoder.EncodeCubeMapAsync(right, left, top, down, back, front, token); + tex.FromTextureData(encoded); + tex.WriteToStream(outputStream); + } + } + + public static class DecoderExtensions + { + /// + public static BCnTextureData Decode(this BcDecoder decoder, T textureFile, CompressionFormat outputFormat = CompressionFormat.RgbaFloat) + where T : class, ITextureFileFormat, new() + { + var encoded = textureFile.ToTextureData(); + return decoder.Decode(encoded, outputFormat); + } + + /// + public static BCnTextureData Decode(this BcDecoder decoder, Stream inputStream, CompressionFormat outputFormat = CompressionFormat.RgbaFloat) + where T : class, ITextureFileFormat, new() + { + var tex = new T(); + tex.ReadFromStream(inputStream); + return Decode(decoder, tex, outputFormat); + } + + /// + /// Decode the first mip level of a textureFile into a of . + /// + /// + public static TOut[] DecodeFirst(this BcDecoder decoder, T textureFile, CompressionFormat outputFormat, out int pixelWidth, out int pixelHeight) + where T : ITextureFileFormat + where TOut : unmanaged + { + var encoded = textureFile.ToTextureData(); + + pixelWidth = encoded.First.Width; + pixelHeight = encoded.First.Height; + + return decoder.DecodeRaw(encoded.First.Data, pixelWidth, pixelHeight, encoded.Format, outputFormat); + } + + /// + public static Task DecodeAsync(this BcDecoder decoder, T textureFile, CompressionFormat outputFormat, CancellationToken token = default) + where T : ITextureFileFormat + { + var encoded = textureFile.ToTextureData(); + return decoder.DecodeAsync(encoded, outputFormat, token); + } + } +} diff --git a/BCnEncoder.Net.TextureFormats/Khronos/GlFormat.cs b/BCnEncoder.Net.TextureFormats/Khronos/GlFormat.cs new file mode 100644 index 0000000..d0a00ed --- /dev/null +++ b/BCnEncoder.Net.TextureFormats/Khronos/GlFormat.cs @@ -0,0 +1,558 @@ +using System; +using System.Collections.Generic; +using System.Text; +using BCnEncoder.Shared; + +namespace BCnEncoder.TextureFormats +{ + public enum GlFormat : uint + { + GlRed = 0x1903, + GlBgra = 0x80E1, + GlRgb = 0x1907, + GlRgba = 0x1908, + GlRg = 0x8227, + GlRedInteger = 0x8D94, + GlRgInteger = 0x8228, + GlRedSnorm = 0x8F90, + GlRgSnorm = 0x8F91, + GlRgbSnorm = 0x8F92, + GlRgbaSnorm = 0x8F93, + GlRgbInteger = 0x8D98, + GlRgbaInteger = 0x8D99, + } + + public enum GlType : uint + { + None = 0, + GlByte = 5120, + GlUnsignedByte = 5121, + GlShort = 5122, + GlUnsignedShort = 5123, + GlInt = 5124, + GlUnsignedInt = 5125, + GlFloat = 5126, + GlHalfFloat = 5131, + GlUnsignedByte233Rev = 33634, + GlUnsignedByte332 = 32818, + GlUnsignedInt1010102 = 32822, + GlUnsignedInt2101010Rev = 33640, + GlUnsignedInt8888 = 32821, + GlUnsignedInt8888Rev = 33639, + GlUnsignedShort1555Rev = 33638, + GlUnsignedShort4444 = 32819, + GlUnsignedShort4444Rev = 33637, + GlUnsignedShort5551 = 32820, + GlUnsignedShort565 = 33635, + GlUnsignedShort565Rev = 33636, + GlUnsignedInt10F11F11FRev = 0x8C3B, + GlUnsignedInt5999Rev = 0x8C3E, + } + + public enum GlInternalFormat : uint + { + GlRgba4 = 0x8056, + GlRgb5 = 0x8050, + GlRgb565 = 0x8D62, + GlRgba8 = 0x8058, + GlRgb5A1 = 0x8057, + GlRgba16 = 0x805B, + GlDepthComponent16 = 0x81A5, + GlDepthComponent24 = 0x81A6, + GlDepthComponent32F = 36012, + GlStencilIndex8 = 36168, + GlDepth24Stencil8 = 0x88F0, + GlDepth32FStencil8 = 36013, + + GlR8 = 0x8229, + GlRg8 = 0x822B, + GlRg16 = 0x822C, + GlR16F = 0x822D, + GlR32F = 0x822E, + GlRg16F = 0x822F, + GlRg32F = 0x8230, + GlRgba32F = 0x8814, + GlRgba16F = 0x881A, + + GlR8Ui = 33330, + GlR8I = 33329, + GlR16 = 33322, + GlR16I = 33331, + GlR16Ui = 33332, + GlR32I = 33333, + GlR32Ui = 33334, + + GlRg8I = 33335, + GlRg8Ui = 33336, + GlRg16I = 33337, + GlRg16Ui = 33338, + GlRg32I = 33339, + GlRg32Ui = 33340, + + GlRgb8 = 32849, + GlRgb8I = 36239, + GlRgb8Ui = 36221, + + GlRgba12 = 32858, + GlRgba2 = 32853, + GlRgba8I = 36238, + GlRgba8Ui = 36220, + + GlRgba16I = 36232, + GlRgba16Ui = 36214, + GlRgba32I = 36226, + GlRgba32Ui = 36208, + + + GlR8Snorm = 0x8F94, + GlRg8Snorm = 0x8F95, + GlRgb8Snorm = 0x8F96, + GlRgba8Snorm = 0x8F97, + GlR16Snorm = 0x8F98, + GlRg16Snorm = 0x8F99, + GlRgb16Snorm = 0x8F9A, + GlRgba16Snorm = 0x8F9B, + + GlRgb10A2 = 32857, + GlRgb10A2Ui = 36975, + + GlRgb16 = 32852, + GlRgb16F = 34843, + GlRgb16I = 36233, + GlRgb16Ui = 36215, + + GlRgb32F = 34837, + GlRgb32I = 36227, + GlRgb32Ui = 36209, + + // EXT_texture_sRGB formats + GlSrgb = 0x8C40, + GlSrgb8 = 0x8C41, + GlSrgbAlpha = 0x8C42, + GlSrgb8Alpha8 = 0x8C43, + GlSluminanceAlpha = 0x8C44, + GlSluminance8Alpha8 = 0x8C45, + GlSluminance = 0x8C46, + GlSluminance8 = 0x8C47, + GlCompressedSrgb = 0x8C48, + GlCompressedSrgbAlpha = 0x8C49, + GlCompressedSluminance = 0x8C4A, + GlCompressedSluminanceAlpha = 0x8C4B, + + // EXT_packed_float + GlR11G11B10F = 0x8C3A, + + // EXT_texture_shared_exponent + GlRgb9E5 = 0x8C3D, + + //BC1 + GlCompressedRgbS3TcDxt1Ext = 0x83F0, + GlCompressedSrgbS3TcDxt1Ext = 0x8C4C, + GlCompressedRgbaS3TcDxt1Ext = 0x83F1, + GlCompressedSrgbAlphaS3TcDxt1Ext = 0x8C4D, + + //BC2 + GlCompressedRgbaS3TcDxt3Ext = 0x83F2, + GlCompressedSrgbAlphaS3TcDxt3Ext = 0x8C4E, + + //BC3 + GlCompressedRgbaS3TcDxt5Ext = 0x83F3, + GlCompressedSrgbAlphaS3TcDxt5Ext = 0x8C4F, + + //BC4 & BC5 + GlCompressedRedGreenRgtc2Ext = 36285, + GlCompressedRedRgtc1Ext = 36283, + GlCompressedSignedRedGreenRgtc2Ext = 36286, + GlCompressedSignedRedRgtc1Ext = 36284, + + //BC6 & BC7 + GlCompressedRgbBptcSignedFloatArb = 36494, + GlCompressedRgbBptcUnsignedFloatArb = 36495, + GlCompressedRgbaBptcUnormArb = 36492, + GlCompressedSrgbAlphaBptcUnormArb = 36493, + + GlCompressedRgbAtc = 0x8C92, + GlCompressedRgbaAtcExplicitAlpha = 0x8C93, + GlCompressedRgbaAtcInterpolatedAlpha = 0x87EE, + + // ETC1 & 2 + GlEtc1Rgb8Oes = 0x8D64, + + GlCompressedR11Eac = 0x9270, + GlCompressedSignedR11Eac = 0x9271, + GlCompressedRg11Eac = 0x9272, + GlCompressedSignedRg11Eac = 0x9273, + + GlCompressedRgb8Etc2 = 0x9274, + GlCompressedSrgb8Etc2 = 0x9275, + GlCompressedRgb8PunchthroughAlpha1Etc2 = 0x9276, + GlCompressedSrgb8PunchthroughAlpha1Etc2 = 0x9277, + GlCompressedRgba8Etc2Eac = 0x9278, + GlCompressedSrgb8Alpha8Etc2Eac = 0x9279, + + // Apple extension BGRA8 + GlBgra8Extension = 0x93A1, + GlFormatBgra = 0x80E1, + + // ASTC + GlCompressedRgbaAstc4X4Khr = 0x93B0, + GlCompressedRgbaAstc5X4Khr = 0x93B1, + GlCompressedRgbaAstc5X5Khr = 0x93B2, + GlCompressedRgbaAstc6X5Khr = 0x93B3, + GlCompressedRgbaAstc6X6Khr = 0x93B4, + GlCompressedRgbaAstc8X5Khr = 0x93B5, + GlCompressedRgbaAstc8X6Khr = 0x93B6, + GlCompressedRgbaAstc8X8Khr = 0x93B7, + GlCompressedRgbaAstc10X5Khr = 0x93B8, + GlCompressedRgbaAstc10X6Khr = 0x93B9, + GlCompressedRgbaAstc10X8Khr = 0x93BA, + GlCompressedRgbaAstc10X10Khr = 0x93BB, + GlCompressedRgbaAstc12X10Khr = 0x93BC, + GlCompressedRgbaAstc12X12Khr = 0x93BD, + + GlCompressedSrgb8Alpha8Astc4X4Khr = 0x93D0, + GlCompressedSrgb8Alpha8Astc5X4Khr = 0x93D1, + GlCompressedSrgb8Alpha8Astc5X5Khr = 0x93D2, + GlCompressedSrgb8Alpha8Astc6X5Khr = 0x93D3, + GlCompressedSrgb8Alpha8Astc6X6Khr = 0x93D4, + GlCompressedSrgb8Alpha8Astc8X5Khr = 0x93D5, + GlCompressedSrgb8Alpha8Astc8X6Khr = 0x93D6, + GlCompressedSrgb8Alpha8Astc8X8Khr = 0x93D7, + GlCompressedSrgb8Alpha8Astc10X5Khr = 0x93D8, + GlCompressedSrgb8Alpha8Astc10X6Khr = 0x93D9, + GlCompressedSrgb8Alpha8Astc10X8Khr = 0x93DA, + GlCompressedSrgb8Alpha8Astc10X10Khr = 0x93DB, + GlCompressedSrgb8Alpha8Astc12X10Khr = 0x93DC, + GlCompressedSrgb8Alpha8Astc12X12Khr = 0x93DD + } + + public static class GLFormatHelpers + { + public static (GlFormat, GlInternalFormat, GlType) GetGlFormat(this CompressionFormat format) => + FormatMapping.TryGetValue(format, out var glFormat) ? glFormat : (0, 0, 0); + + public static uint GetTypeSize(this GlType type) + { + switch (type) + { + case GlType.None: + case GlType.GlByte: + case GlType.GlUnsignedByte: + case GlType.GlUnsignedByte233Rev: + case GlType.GlUnsignedByte332: + return 1; + + case GlType.GlShort: + case GlType.GlUnsignedShort: + case GlType.GlHalfFloat: + case GlType.GlUnsignedShort1555Rev: + case GlType.GlUnsignedShort4444: + case GlType.GlUnsignedShort4444Rev: + case GlType.GlUnsignedShort5551: + case GlType.GlUnsignedShort565: + case GlType.GlUnsignedShort565Rev: + return 2; + + case GlType.GlInt: + case GlType.GlUnsignedInt: + case GlType.GlFloat: + case GlType.GlUnsignedInt1010102: + case GlType.GlUnsignedInt2101010Rev: + case GlType.GlUnsignedInt8888: + case GlType.GlUnsignedInt8888Rev: + return 4; + + default: + return 1; // Default to 1 byte for unknown types + } + } + + public static CompressionFormat GetCompressionFormat(this GlInternalFormat internalFormat, GlFormat format = 0, GlType type = GlType.None) + { + // First try with exact match of provided format and type + if (format != 0 && FormatMappingReverse.TryGetValue((internalFormat, format, type), out var bcnFormat)) + { + return bcnFormat; + } + + // Fallback: Try any format that matches the internal format + foreach (var key in FormatMappingReverse.Keys) + { + if (key.internalFormat == internalFormat) + { + FormatMappingReverse.TryGetValue(key, out bcnFormat); + return bcnFormat; + } + } + + return CompressionFormat.Unknown; + } + + public static bool IsCompressedFormat(this GlInternalFormat internalFormat) + { + switch (internalFormat) + { + //BC1 + case GlInternalFormat.GlCompressedRgbS3TcDxt1Ext: + case GlInternalFormat.GlCompressedSrgbS3TcDxt1Ext: + case GlInternalFormat.GlCompressedRgbaS3TcDxt1Ext: + case GlInternalFormat.GlCompressedSrgbAlphaS3TcDxt1Ext: + + //BC2 + case GlInternalFormat.GlCompressedRgbaS3TcDxt3Ext: + case GlInternalFormat.GlCompressedSrgbAlphaS3TcDxt3Ext: + + //BC3 + case GlInternalFormat.GlCompressedRgbaS3TcDxt5Ext: + case GlInternalFormat.GlCompressedSrgbAlphaS3TcDxt5Ext: + + //BC4 & BC5 + case GlInternalFormat.GlCompressedRedGreenRgtc2Ext: + case GlInternalFormat.GlCompressedRedRgtc1Ext: + case GlInternalFormat.GlCompressedSignedRedGreenRgtc2Ext: + case GlInternalFormat.GlCompressedSignedRedRgtc1Ext: + + //BC6 & BC7 + case GlInternalFormat.GlCompressedRgbBptcSignedFloatArb: + case GlInternalFormat.GlCompressedRgbBptcUnsignedFloatArb: + case GlInternalFormat.GlCompressedRgbaBptcUnormArb: + case GlInternalFormat.GlCompressedSrgbAlphaBptcUnormArb: + + case GlInternalFormat.GlCompressedRgbAtc: + case GlInternalFormat.GlCompressedRgbaAtcExplicitAlpha: + case GlInternalFormat.GlCompressedRgbaAtcInterpolatedAlpha: + + // ETC1 & 2 + case GlInternalFormat.GlEtc1Rgb8Oes: + + case GlInternalFormat.GlCompressedR11Eac: + case GlInternalFormat.GlCompressedSignedR11Eac: + case GlInternalFormat.GlCompressedRg11Eac: + case GlInternalFormat.GlCompressedSignedRg11Eac: + case GlInternalFormat.GlCompressedRgb8Etc2: + case GlInternalFormat.GlCompressedSrgb8Etc2: + case GlInternalFormat.GlCompressedRgb8PunchthroughAlpha1Etc2: + case GlInternalFormat.GlCompressedSrgb8PunchthroughAlpha1Etc2: + case GlInternalFormat.GlCompressedRgba8Etc2Eac: + case GlInternalFormat.GlCompressedSrgb8Alpha8Etc2Eac: + + // ASTC + case GlInternalFormat.GlCompressedRgbaAstc4X4Khr : + case GlInternalFormat.GlCompressedRgbaAstc5X4Khr : + case GlInternalFormat.GlCompressedRgbaAstc5X5Khr : + case GlInternalFormat.GlCompressedRgbaAstc6X5Khr : + case GlInternalFormat.GlCompressedRgbaAstc6X6Khr : + case GlInternalFormat.GlCompressedRgbaAstc8X5Khr : + case GlInternalFormat.GlCompressedRgbaAstc8X6Khr : + case GlInternalFormat.GlCompressedRgbaAstc8X8Khr : + case GlInternalFormat.GlCompressedRgbaAstc10X5Khr: + case GlInternalFormat.GlCompressedRgbaAstc10X6Khr: + case GlInternalFormat.GlCompressedRgbaAstc10X8Khr: + case GlInternalFormat.GlCompressedRgbaAstc10X10Khr: + case GlInternalFormat.GlCompressedRgbaAstc12X10Khr: + case GlInternalFormat.GlCompressedRgbaAstc12X12Khr: + + case GlInternalFormat.GlCompressedSrgb8Alpha8Astc4X4Khr: + case GlInternalFormat.GlCompressedSrgb8Alpha8Astc5X4Khr: + case GlInternalFormat.GlCompressedSrgb8Alpha8Astc5X5Khr: + case GlInternalFormat.GlCompressedSrgb8Alpha8Astc6X5Khr: + case GlInternalFormat.GlCompressedSrgb8Alpha8Astc6X6Khr: + case GlInternalFormat.GlCompressedSrgb8Alpha8Astc8X5Khr: + case GlInternalFormat.GlCompressedSrgb8Alpha8Astc8X6Khr: + case GlInternalFormat.GlCompressedSrgb8Alpha8Astc8X8Khr: + case GlInternalFormat.GlCompressedSrgb8Alpha8Astc10X5Khr: + case GlInternalFormat.GlCompressedSrgb8Alpha8Astc10X6Khr: + case GlInternalFormat.GlCompressedSrgb8Alpha8Astc10X8Khr: + case GlInternalFormat.GlCompressedSrgb8Alpha8Astc10X10Khr: + case GlInternalFormat.GlCompressedSrgb8Alpha8Astc12X10Khr: + case GlInternalFormat.GlCompressedSrgb8Alpha8Astc12X12Khr: + return true; + default: + return false; + } + } + + private static Dictionary FormatMapping { get; } = + new() { + // Red-only formats + { CompressionFormat.R8, (GlFormat.GlRed, GlInternalFormat.GlR8, GlType.GlUnsignedByte) }, + { CompressionFormat.R8S, (GlFormat.GlRed, GlInternalFormat.GlR8Snorm, GlType.GlByte) }, + { CompressionFormat.R16, (GlFormat.GlRed, GlInternalFormat.GlR16, GlType.GlUnsignedShort) }, + { CompressionFormat.R16S, (GlFormat.GlRed, GlInternalFormat.GlR16Snorm, GlType.GlShort) }, + { CompressionFormat.R16F, (GlFormat.GlRed, GlInternalFormat.GlR16F, GlType.GlHalfFloat) }, + { CompressionFormat.R32F, (GlFormat.GlRed, GlInternalFormat.GlR32F, GlType.GlFloat) }, + + // Red-green formats + { CompressionFormat.R8G8, (GlFormat.GlRg, GlInternalFormat.GlRg8, GlType.GlUnsignedByte) }, + { CompressionFormat.R8G8S, (GlFormat.GlRg, GlInternalFormat.GlRg8Snorm, GlType.GlByte) }, + { CompressionFormat.R16G16, (GlFormat.GlRg, GlInternalFormat.GlRg16, GlType.GlUnsignedShort) }, + { CompressionFormat.R16G16S, (GlFormat.GlRg, GlInternalFormat.GlRg16Snorm, GlType.GlShort) }, + { CompressionFormat.R16G16F, (GlFormat.GlRg, GlInternalFormat.GlRg16F, GlType.GlHalfFloat) }, + { CompressionFormat.R32G32F, (GlFormat.GlRg, GlInternalFormat.GlRg32F, GlType.GlFloat) }, + + // RGB formats + { CompressionFormat.Rgb24, (GlFormat.GlRgb, GlInternalFormat.GlRgb8, GlType.GlUnsignedByte) }, + { CompressionFormat.Rgb24_sRGB, (GlFormat.GlRgb, GlInternalFormat.GlSrgb8, GlType.GlUnsignedByte) }, + { CompressionFormat.RgbFloat, (GlFormat.GlRgb, GlInternalFormat.GlRgb32F, GlType.GlFloat) }, + { CompressionFormat.RgbHalf, (GlFormat.GlRgb, GlInternalFormat.GlRgb16F, GlType.GlHalfFloat) }, + { CompressionFormat.R11G11B10UF_Packed, (GlFormat.GlRgb, GlInternalFormat.GlR11G11B10F, GlType.GlUnsignedInt10F11F11FRev) }, + { CompressionFormat.R9G9B9E5_Packed, (GlFormat.GlRgb, GlInternalFormat.GlRgb9E5, GlType.GlUnsignedInt5999Rev) }, + { CompressionFormat.R5G6B5_Packed, (GlFormat.GlRgb, GlInternalFormat.GlRgb565, GlType.GlUnsignedShort565Rev) }, + { CompressionFormat.R16G16B16, (GlFormat.GlRgb, GlInternalFormat.GlRgb16, GlType.GlUnsignedShort) }, + { CompressionFormat.R32G32B32UI, (GlFormat.GlRgbInteger, GlInternalFormat.GlRgb32Ui, GlType.GlUnsignedInt) }, + + // RGBA formats + { CompressionFormat.R10G10B10A2_Packed, (GlFormat.GlRgba, GlInternalFormat.GlRgb10A2, GlType.GlUnsignedInt2101010Rev) }, + { CompressionFormat.Rgba32, (GlFormat.GlRgba, GlInternalFormat.GlRgba8, GlType.GlUnsignedByte) }, + { CompressionFormat.Rgba32_sRGB, (GlFormat.GlRgba, GlInternalFormat.GlSrgb8Alpha8, GlType.GlUnsignedByte) }, + { CompressionFormat.RgbaFloat, (GlFormat.GlRgba, GlInternalFormat.GlRgba32F, GlType.GlFloat) }, + { CompressionFormat.RgbaHalf, (GlFormat.GlRgba, GlInternalFormat.GlRgba16F, GlType.GlHalfFloat) }, + { CompressionFormat.R16G16B16A16, (GlFormat.GlRgba, GlInternalFormat.GlRgba16, GlType.GlUnsignedShort) }, + { CompressionFormat.R32G32B32A32UI, (GlFormat.GlRgbaInteger, GlInternalFormat.GlRgba32Ui, GlType.GlUnsignedInt) }, + { CompressionFormat.R5G5B5A1_Packed, (GlFormat.GlRgba, GlInternalFormat.GlRgb5A1, GlType.GlUnsignedShort1555Rev) }, + { CompressionFormat.R4G4B4A4_Packed, (GlFormat.GlRgba, GlInternalFormat.GlRgba4, GlType.GlUnsignedShort4444Rev) }, + { CompressionFormat.A1B5G5R5_Packed, (GlFormat.GlRgba, GlInternalFormat.GlRgb5A1, GlType.GlUnsignedShort5551) }, + { CompressionFormat.A4B4G4R4_Packed, (GlFormat.GlRgba, GlInternalFormat.GlRgba4, GlType.GlUnsignedShort4444) }, + + // BGR formats + { CompressionFormat.Bgr24, (GlFormat.GlBgra, GlInternalFormat.GlRgb8, GlType.GlUnsignedByte) }, + { CompressionFormat.B5G6R5_Packed, (GlFormat.GlRgb, GlInternalFormat.GlRgb565, GlType.GlUnsignedShort565) }, + + // BGRA formats + { CompressionFormat.Bgra32, (GlFormat.GlBgra, GlInternalFormat.GlRgba8, GlType.GlUnsignedByte) }, + { CompressionFormat.Bgra32_sRGB, (GlFormat.GlBgra, GlInternalFormat.GlSrgb8Alpha8, GlType.GlUnsignedByte) }, + { CompressionFormat.B10G10R10A2_Packed, (GlFormat.GlBgra, GlInternalFormat.GlRgb10A2, GlType.GlUnsignedInt2101010Rev) }, + { CompressionFormat.B5G5R5A1_Packed, (GlFormat.GlBgra, GlInternalFormat.GlRgb5A1, GlType.GlUnsignedShort1555Rev) }, + { CompressionFormat.B4G4R4A4_Packed, (GlFormat.GlBgra, GlInternalFormat.GlRgba4, GlType.GlUnsignedShort4444Rev) }, + { CompressionFormat.A1R5G5B5_Packed, (GlFormat.GlBgra, GlInternalFormat.GlRgb5A1, GlType.GlUnsignedShort5551) }, + { CompressionFormat.A4R4G4B4_Packed, (GlFormat.GlBgra, GlInternalFormat.GlRgba4, GlType.GlUnsignedShort4444) }, + + // Special formats (Rgbe and Xyze don't have direct OpenGL equivalents) + { CompressionFormat.Rgbe32, (GlFormat.GlRgba, GlInternalFormat.GlRgba8, GlType.GlUnsignedByte) }, // Best approximation + { CompressionFormat.Xyze32, (GlFormat.GlRgba, GlInternalFormat.GlRgba8, GlType.GlUnsignedByte) }, // Best approximation + + // BC formats + { CompressionFormat.Bc1, (GlFormat.GlRgb, GlInternalFormat.GlCompressedRgbS3TcDxt1Ext, GlType.None) }, + { CompressionFormat.Bc1_sRGB, (GlFormat.GlRgb, GlInternalFormat.GlCompressedSrgbS3TcDxt1Ext, GlType.None) }, + { CompressionFormat.Bc1WithAlpha, (GlFormat.GlRgba, GlInternalFormat.GlCompressedRgbaS3TcDxt1Ext, GlType.None) }, + { CompressionFormat.Bc1WithAlpha_sRGB, (GlFormat.GlRgba, GlInternalFormat.GlCompressedSrgbAlphaS3TcDxt1Ext, GlType.None) }, + { CompressionFormat.Bc2, (GlFormat.GlRgba, GlInternalFormat.GlCompressedRgbaS3TcDxt3Ext, GlType.None) }, + { CompressionFormat.Bc2_sRGB, (GlFormat.GlRgba, GlInternalFormat.GlCompressedSrgbAlphaS3TcDxt3Ext, GlType.None) }, + { CompressionFormat.Bc3, (GlFormat.GlRgba, GlInternalFormat.GlCompressedRgbaS3TcDxt5Ext, GlType.None) }, + { CompressionFormat.Bc3_sRGB, (GlFormat.GlRgba, GlInternalFormat.GlCompressedSrgbAlphaS3TcDxt5Ext, GlType.None) }, + { CompressionFormat.Bc4, (GlFormat.GlRed, GlInternalFormat.GlCompressedRedRgtc1Ext, GlType.None) }, + { CompressionFormat.Bc4S, (GlFormat.GlRed, GlInternalFormat.GlCompressedSignedRedRgtc1Ext, GlType.None) }, + { CompressionFormat.Bc5, (GlFormat.GlRg, GlInternalFormat.GlCompressedRedGreenRgtc2Ext, GlType.None) }, + { CompressionFormat.Bc5S, (GlFormat.GlRg, GlInternalFormat.GlCompressedSignedRedGreenRgtc2Ext, GlType.None) }, + { CompressionFormat.Bc6U, (GlFormat.GlRgb, GlInternalFormat.GlCompressedRgbBptcUnsignedFloatArb, GlType.None) }, + { CompressionFormat.Bc6S, (GlFormat.GlRgb, GlInternalFormat.GlCompressedRgbBptcSignedFloatArb, GlType.None) }, + { CompressionFormat.Bc7, (GlFormat.GlRgba, GlInternalFormat.GlCompressedRgbaBptcUnormArb, GlType.None) }, + { CompressionFormat.Bc7_sRGB, (GlFormat.GlRgba, GlInternalFormat.GlCompressedSrgbAlphaBptcUnormArb, GlType.None) }, + + // ATC formats + { CompressionFormat.Atc, (GlFormat.GlRgb, GlInternalFormat.GlCompressedRgbAtc, GlType.None) }, + { CompressionFormat.AtcExplicitAlpha, (GlFormat.GlRgba, GlInternalFormat.GlCompressedRgbaAtcExplicitAlpha, GlType.None) }, + { CompressionFormat.AtcInterpolatedAlpha, (GlFormat.GlRgba, GlInternalFormat.GlCompressedRgbaAtcInterpolatedAlpha, GlType.None) } + }; + + private static Dictionary<(GlInternalFormat internalFormat, GlFormat format, GlType glType), CompressionFormat> FormatMappingReverse { get; } = + new() { + // Red-only formats + { (GlInternalFormat.GlR8, GlFormat.GlRed, GlType.GlUnsignedByte), CompressionFormat.R8 }, + { (GlInternalFormat.GlR8Ui, GlFormat.GlRed, GlType.GlUnsignedByte), CompressionFormat.R8 }, + { (GlInternalFormat.GlR8I, GlFormat.GlRed, GlType.GlByte), CompressionFormat.R8S }, + { (GlInternalFormat.GlR8Snorm, GlFormat.GlRed, GlType.GlByte), CompressionFormat.R8S }, + { (GlInternalFormat.GlR16, GlFormat.GlRed, GlType.GlUnsignedShort), CompressionFormat.R16 }, + { (GlInternalFormat.GlR16Ui, GlFormat.GlRed, GlType.GlUnsignedShort), CompressionFormat.R16 }, + { (GlInternalFormat.GlR16I, GlFormat.GlRed, GlType.GlShort), CompressionFormat.R16S }, + { (GlInternalFormat.GlR16Snorm, GlFormat.GlRed, GlType.GlShort), CompressionFormat.R16S }, + { (GlInternalFormat.GlR16F, GlFormat.GlRed, GlType.GlHalfFloat), CompressionFormat.R16F }, + { (GlInternalFormat.GlR32F, GlFormat.GlRed, GlType.GlFloat), CompressionFormat.R32F }, + + // Red-green formats + { (GlInternalFormat.GlRg8, GlFormat.GlRg, GlType.GlUnsignedByte), CompressionFormat.R8G8 }, + { (GlInternalFormat.GlRg8Ui, GlFormat.GlRg, GlType.GlUnsignedByte), CompressionFormat.R8G8 }, + { (GlInternalFormat.GlRg8I, GlFormat.GlRg, GlType.GlByte), CompressionFormat.R8G8S }, + { (GlInternalFormat.GlRg8Snorm, GlFormat.GlRg, GlType.GlByte), CompressionFormat.R8G8S }, + { (GlInternalFormat.GlRg16, GlFormat.GlRg, GlType.GlUnsignedShort), CompressionFormat.R16G16 }, + { (GlInternalFormat.GlRg16Ui, GlFormat.GlRg, GlType.GlUnsignedShort), CompressionFormat.R16G16 }, + { (GlInternalFormat.GlRg16I, GlFormat.GlRg, GlType.GlShort), CompressionFormat.R16G16S }, + { (GlInternalFormat.GlRg16Snorm, GlFormat.GlRg, GlType.GlShort), CompressionFormat.R16G16S }, + { (GlInternalFormat.GlRg16F, GlFormat.GlRg, GlType.GlHalfFloat), CompressionFormat.R16G16F }, + { (GlInternalFormat.GlRg32F, GlFormat.GlRg, GlType.GlFloat), CompressionFormat.R32G32F }, + + // RGB formats + { (GlInternalFormat.GlRgb8, GlFormat.GlRgb, GlType.GlUnsignedByte), CompressionFormat.Rgb24 }, + { (GlInternalFormat.GlRgb8I, GlFormat.GlRgb, GlType.GlByte), CompressionFormat.Rgb24 }, + { (GlInternalFormat.GlRgb8Ui, GlFormat.GlRgb, GlType.GlUnsignedByte), CompressionFormat.Rgb24 }, + { (GlInternalFormat.GlRgb8Snorm, GlFormat.GlRgb, GlType.GlByte), CompressionFormat.Rgb24 }, + { (GlInternalFormat.GlSrgb8, GlFormat.GlRgb, GlType.GlUnsignedByte), CompressionFormat.Rgb24_sRGB }, + { (GlInternalFormat.GlSrgb, GlFormat.GlRgb, GlType.GlUnsignedByte), CompressionFormat.Rgb24_sRGB }, + { (GlInternalFormat.GlRgb32F, GlFormat.GlRgb, GlType.GlFloat), CompressionFormat.RgbFloat }, + { (GlInternalFormat.GlRgb16F, GlFormat.GlRgb, GlType.GlHalfFloat), CompressionFormat.RgbHalf }, + { (GlInternalFormat.GlR11G11B10F, GlFormat.GlRgb, GlType.GlUnsignedInt10F11F11FRev), CompressionFormat.R11G11B10UF_Packed }, // Note: OpenGL follows DirectX naming here + { (GlInternalFormat.GlRgb9E5, GlFormat.GlRgb, GlType.GlUnsignedInt5999Rev), CompressionFormat.R9G9B9E5_Packed }, // Note: OpenGL follows DirectX naming here + { (GlInternalFormat.GlRgb565, GlFormat.GlRgb, GlType.GlUnsignedShort565Rev), CompressionFormat.R5G6B5_Packed }, + { (GlInternalFormat.GlRgb16, GlFormat.GlRgb, GlType.GlUnsignedShort), CompressionFormat.R16G16B16 }, + { (GlInternalFormat.GlRgb32Ui, GlFormat.GlRgbInteger, GlType.GlUnsignedInt), CompressionFormat.R32G32B32UI }, + + // RGBA formats + { (GlInternalFormat.GlRgb10A2, GlFormat.GlRgba, GlType.GlUnsignedInt2101010Rev), CompressionFormat.R10G10B10A2_Packed }, // Note: OpenGL follows DirectX naming here + { (GlInternalFormat.GlRgb10A2Ui, GlFormat.GlRgba, GlType.GlUnsignedInt2101010Rev), CompressionFormat.R10G10B10A2_Packed }, // Note: OpenGL follows DirectX naming here + { (GlInternalFormat.GlRgba8, GlFormat.GlRgba, GlType.GlUnsignedByte), CompressionFormat.Rgba32 }, + { (GlInternalFormat.GlRgba8I, GlFormat.GlRgba, GlType.GlByte), CompressionFormat.Rgba32 }, + { (GlInternalFormat.GlRgba8Ui, GlFormat.GlRgba, GlType.GlUnsignedByte), CompressionFormat.Rgba32 }, + { (GlInternalFormat.GlRgba8Snorm, GlFormat.GlRgba, GlType.GlByte), CompressionFormat.Rgba32 }, + { (GlInternalFormat.GlSrgb8Alpha8, GlFormat.GlRgba, GlType.GlUnsignedByte), CompressionFormat.Rgba32_sRGB }, + { (GlInternalFormat.GlSrgbAlpha, GlFormat.GlRgba, GlType.GlUnsignedByte), CompressionFormat.Rgba32_sRGB }, + { (GlInternalFormat.GlRgba32F, GlFormat.GlRgba, GlType.GlFloat), CompressionFormat.RgbaFloat }, + { (GlInternalFormat.GlRgba16F, GlFormat.GlRgba, GlType.GlHalfFloat), CompressionFormat.RgbaHalf }, + { (GlInternalFormat.GlRgba16, GlFormat.GlRgba, GlType.GlUnsignedShort), CompressionFormat.R16G16B16A16 }, + { (GlInternalFormat.GlRgba32Ui, GlFormat.GlRgbaInteger, GlType.GlUnsignedInt), CompressionFormat.R32G32B32A32UI }, + { (GlInternalFormat.GlRgb5A1, GlFormat.GlRgba, GlType.GlUnsignedShort5551), CompressionFormat.A1B5G5R5_Packed }, + { (GlInternalFormat.GlRgb5A1, GlFormat.GlRgba, GlType.GlUnsignedShort1555Rev), CompressionFormat.R5G5B5A1_Packed }, + { (GlInternalFormat.GlRgba4, GlFormat.GlRgba, GlType.GlUnsignedShort4444), CompressionFormat.A4B4G4R4_Packed }, + { (GlInternalFormat.GlRgba4, GlFormat.GlRgba, GlType.GlUnsignedShort4444Rev), CompressionFormat.R4G4B4A4_Packed }, + + // BGR formats + { (GlInternalFormat.GlRgb8, GlFormat.GlBgra, GlType.GlUnsignedByte), CompressionFormat.Bgr24 }, + { (GlInternalFormat.GlRgb565, GlFormat.GlRgb, GlType.GlUnsignedShort565), CompressionFormat.B5G6R5_Packed }, + { (GlInternalFormat.GlRgb565, GlFormat.GlBgra, GlType.GlUnsignedShort565), CompressionFormat.R5G6B5_Packed }, + + // BGRA formats + { (GlInternalFormat.GlBgra8Extension, GlFormat.GlBgra, GlType.GlUnsignedByte), CompressionFormat.Bgra32 }, + { (GlInternalFormat.GlFormatBgra, GlFormat.GlBgra, GlType.GlUnsignedByte), CompressionFormat.Bgra32 }, + { (GlInternalFormat.GlRgba8, GlFormat.GlBgra, GlType.GlUnsignedByte), CompressionFormat.Bgra32 }, + { (GlInternalFormat.GlSrgb8Alpha8, GlFormat.GlBgra, GlType.GlUnsignedByte), CompressionFormat.Bgra32_sRGB }, + { (GlInternalFormat.GlSrgbAlpha, GlFormat.GlBgra, GlType.GlUnsignedByte), CompressionFormat.Bgra32_sRGB }, + { (GlInternalFormat.GlRgba4, GlFormat.GlBgra, GlType.GlUnsignedShort4444), CompressionFormat.A4R4G4B4_Packed }, + { (GlInternalFormat.GlRgba4, GlFormat.GlBgra, GlType.GlUnsignedShort4444Rev), CompressionFormat.B4G4R4A4_Packed }, + { (GlInternalFormat.GlRgb5A1, GlFormat.GlBgra, GlType.GlUnsignedShort5551), CompressionFormat.A1R5G5B5_Packed }, + { (GlInternalFormat.GlRgb5A1, GlFormat.GlBgra, GlType.GlUnsignedShort1555Rev), CompressionFormat.B5G5R5A1_Packed }, + { (GlInternalFormat.GlRgb10A2, GlFormat.GlBgra, GlType.GlUnsignedInt2101010Rev), CompressionFormat.B10G10R10A2_Packed }, + + // BC formats + { (GlInternalFormat.GlCompressedRgbS3TcDxt1Ext, GlFormat.GlRgb, GlType.None), CompressionFormat.Bc1 }, + { (GlInternalFormat.GlCompressedSrgbS3TcDxt1Ext, GlFormat.GlRgb, GlType.None), CompressionFormat.Bc1_sRGB }, + { (GlInternalFormat.GlCompressedRgbaS3TcDxt1Ext, GlFormat.GlRgba, GlType.None), CompressionFormat.Bc1WithAlpha }, + { (GlInternalFormat.GlCompressedSrgbAlphaS3TcDxt1Ext, GlFormat.GlRgba, GlType.None), CompressionFormat.Bc1WithAlpha_sRGB }, + { (GlInternalFormat.GlCompressedRgbaS3TcDxt3Ext, GlFormat.GlRgba, GlType.None), CompressionFormat.Bc2 }, + { (GlInternalFormat.GlCompressedSrgbAlphaS3TcDxt3Ext, GlFormat.GlRgba, GlType.None), CompressionFormat.Bc2_sRGB }, + { (GlInternalFormat.GlCompressedRgbaS3TcDxt5Ext, GlFormat.GlRgba, GlType.None), CompressionFormat.Bc3 }, + { (GlInternalFormat.GlCompressedSrgbAlphaS3TcDxt5Ext, GlFormat.GlRgba, GlType.None), CompressionFormat.Bc3_sRGB }, + { (GlInternalFormat.GlCompressedRedRgtc1Ext, GlFormat.GlRed, GlType.None), CompressionFormat.Bc4 }, + { (GlInternalFormat.GlCompressedSignedRedRgtc1Ext, GlFormat.GlRed, GlType.None), CompressionFormat.Bc4S }, + { (GlInternalFormat.GlCompressedRedGreenRgtc2Ext, GlFormat.GlRg, GlType.None), CompressionFormat.Bc5 }, + { (GlInternalFormat.GlCompressedSignedRedGreenRgtc2Ext, GlFormat.GlRg, GlType.None), CompressionFormat.Bc5S }, + { (GlInternalFormat.GlCompressedRgbBptcUnsignedFloatArb, GlFormat.GlRgb, GlType.None), CompressionFormat.Bc6U }, + { (GlInternalFormat.GlCompressedRgbBptcSignedFloatArb, GlFormat.GlRgb, GlType.None), CompressionFormat.Bc6S }, + { (GlInternalFormat.GlCompressedRgbaBptcUnormArb, GlFormat.GlRgba, GlType.None), CompressionFormat.Bc7 }, + { (GlInternalFormat.GlCompressedSrgbAlphaBptcUnormArb, GlFormat.GlRgba, GlType.None), CompressionFormat.Bc7_sRGB }, + + // ATC formats + { (GlInternalFormat.GlCompressedRgbAtc, GlFormat.GlRgb, GlType.None), CompressionFormat.Atc }, + { (GlInternalFormat.GlCompressedRgbaAtcExplicitAlpha, GlFormat.GlRgba, GlType.None), CompressionFormat.AtcExplicitAlpha }, + { (GlInternalFormat.GlCompressedRgbaAtcInterpolatedAlpha, GlFormat.GlRgba, GlType.None), CompressionFormat.AtcInterpolatedAlpha } + }; + } +} diff --git a/BCnEncoder.Net.TextureFormats/Khronos/KhrBasicDataFormatDescriptor.cs b/BCnEncoder.Net.TextureFormats/Khronos/KhrBasicDataFormatDescriptor.cs new file mode 100644 index 0000000..d63a090 --- /dev/null +++ b/BCnEncoder.Net.TextureFormats/Khronos/KhrBasicDataFormatDescriptor.cs @@ -0,0 +1,773 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace BCnEncoder.TextureFormats +{ + // From https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html + public class KhrDataFormatDescriptor + { + internal const int KHR_DF_WORD_SAMPLESTART = 6; + internal const int KHR_DF_WORD_SAMPLEWORDS = 4; + public uint[] data; + + public KhrDfVendorId VendorId + { + get => (KhrDfVendorId)AccessGet(KhrDataFormatDescriptorAccessor.VendorId); + set => AccessSet(KhrDataFormatDescriptorAccessor.VendorId, (uint)value); + } + public KhrDfKhrDescriptorType DescriptorType + { + get => (KhrDfKhrDescriptorType)AccessGet(KhrDataFormatDescriptorAccessor.DescriptorType); + set => AccessSet(KhrDataFormatDescriptorAccessor.DescriptorType, (uint)value); + } + public uint VersionNumber + { + get => AccessGet(KhrDataFormatDescriptorAccessor.VersionNumber); + set => AccessSet(KhrDataFormatDescriptorAccessor.VersionNumber, value); + } + public uint DescriptorBlockSize + { + get => AccessGet(KhrDataFormatDescriptorAccessor.DescriptorBlockSize); + set => AccessSet(KhrDataFormatDescriptorAccessor.DescriptorBlockSize, value); + } + + internal uint AccessGet(in KhrDataFormatDescriptorAccessor accessor) + { + return (data[accessor.offset] >> accessor.shift) & accessor.mask; + } + internal void AccessSet(in KhrDataFormatDescriptorAccessor accessor, uint value) + { + var u = data[accessor.offset]; + u &= ~(accessor.mask << accessor.shift); + u |= (value & accessor.mask) << accessor.shift; + data[accessor.offset] = u; + } + + public void Write(BinaryWriter writer) + { + Debug.Assert(data.Length * 4 == DescriptorBlockSize, "Data length did not match DescriptorBlockSize"); + foreach (var u in data) + { + writer.Write(u); + } + } + + public static KhrDataFormatDescriptor Read(BinaryReader reader) + { + var descriptor = new KhrDataFormatDescriptor(); + // Read first two bytes + descriptor.data = new uint[2]; + descriptor.data[0] = reader.ReadUInt32(); + descriptor.data[1] = reader.ReadUInt32(); + + var oldData = descriptor.data; + + // Basic descriptor + if (descriptor.VendorId == KhrDfVendorId.KhrDfVendoridKhronos && + descriptor.DescriptorType == KhrDfKhrDescriptorType.KhrDfKhrDescriptortypeBasicformat) + { + var numSamples = (int)(((descriptor.DescriptorBlockSize >> 2) - KHR_DF_WORD_SAMPLESTART) / KHR_DF_WORD_SAMPLEWORDS); + + descriptor = new KhrBasicDataFormatDescriptor(numSamples); + oldData.CopyTo(descriptor.data.AsSpan()); + } + // Unknown descriptor + else + { + var dataLength = (int)(descriptor.DescriptorBlockSize / 4); + var newData = new uint[dataLength]; + oldData.CopyTo(newData.AsSpan()); + descriptor.data = newData; + } + + // Read rest of descriptor + for (var i = 2; i < descriptor.data.Length; i++) + { + descriptor.data[i] = reader.ReadUInt32(); + } + + return descriptor; + } + } + + public class KhrBasicDataFormatDescriptor : KhrDataFormatDescriptor + { + public KhrBasicDataFormatDescriptor(int numSamples) + { + data = new uint[KHR_DF_WORD_SAMPLESTART + numSamples * KHR_DF_WORD_SAMPLEWORDS]; + DescriptorBlockSize = 4U * (uint)data.Length; + VersionNumber = 2; + DescriptorType = KhrDfKhrDescriptorType.KhrDfKhrDescriptortypeBasicformat; + VendorId = KhrDfVendorId.KhrDfVendoridKhronos; + } + + public class SampleAccessor + { + internal KhrBasicDataFormatDescriptor descriptor; + internal int sampleIdx; + internal SampleAccessor(KhrBasicDataFormatDescriptor descriptor, int sampleIdx) + { + this.descriptor = descriptor; + this.sampleIdx = sampleIdx; + } + + public uint BitOffset + { + get => descriptor.AccessSampleGet(sampleIdx, KhrDataFormatDescriptorAccessor.SampleBitOffset); + set => descriptor.AccessSampleSet(sampleIdx, KhrDataFormatDescriptorAccessor.SampleBitOffset, value); + } + /// + /// This is actual BitLength - 1 (eg. 7 here is actually 8) + /// + public uint BitLength + { + get => descriptor.AccessSampleGet(sampleIdx, KhrDataFormatDescriptorAccessor.SampleBitLength); + set => descriptor.AccessSampleSet(sampleIdx, KhrDataFormatDescriptorAccessor.SampleBitLength, value); + } + + /// + /// Bottom 4 bits are , + /// upper 4 bits are + /// + public uint ChannelType + { + get => descriptor.AccessSampleGet(sampleIdx, KhrDataFormatDescriptorAccessor.SampleChannelType); + set => descriptor.AccessSampleSet(sampleIdx, KhrDataFormatDescriptorAccessor.SampleChannelType, value); + } + + public KhrDfModelChannels ChannelTypeChannel => (KhrDfModelChannels)(ChannelType & 0xFU); + public KhrDfSampleDatatypeQualifiers ChannelTypeDataType => (KhrDfSampleDatatypeQualifiers)(ChannelType & 0xF0U); + + public uint Position0 + { + get => descriptor.AccessSampleGet(sampleIdx, KhrDataFormatDescriptorAccessor.SamplePosition0); + set => descriptor.AccessSampleSet(sampleIdx, KhrDataFormatDescriptorAccessor.SamplePosition0, value); + } + public uint Position1 + { + get => descriptor.AccessSampleGet(sampleIdx, KhrDataFormatDescriptorAccessor.SamplePosition1); + set => descriptor.AccessSampleSet(sampleIdx, KhrDataFormatDescriptorAccessor.SamplePosition1, value); + } + public uint Position2 + { + get => descriptor.AccessSampleGet(sampleIdx, KhrDataFormatDescriptorAccessor.SamplePosition2); + set => descriptor.AccessSampleSet(sampleIdx, KhrDataFormatDescriptorAccessor.SamplePosition2, value); + } + public uint Position3 + { + get => descriptor.AccessSampleGet(sampleIdx, KhrDataFormatDescriptorAccessor.SamplePosition3); + set => descriptor.AccessSampleSet(sampleIdx, KhrDataFormatDescriptorAccessor.SamplePosition3, value); + } + public uint SampleLower + { + get => descriptor.AccessSampleGet(sampleIdx, KhrDataFormatDescriptorAccessor.SampleLower); + set => descriptor.AccessSampleSet(sampleIdx, KhrDataFormatDescriptorAccessor.SampleLower, value); + } + public uint SampleUpper + { + get => descriptor.AccessSampleGet(sampleIdx, KhrDataFormatDescriptorAccessor.SampleUpper); + set => descriptor.AccessSampleSet(sampleIdx, KhrDataFormatDescriptorAccessor.SampleUpper, value); + } + } + + public struct SampleArrayAccessor + { + internal KhrBasicDataFormatDescriptor descriptor; + + internal SampleArrayAccessor(KhrBasicDataFormatDescriptor descriptor) + { + this.descriptor = descriptor; + } + + public SampleAccessor this[int index] => new SampleAccessor(descriptor, index); + } + + + + public SampleArrayAccessor Samples => new SampleArrayAccessor(this); + public uint NumSamples => (((DescriptorBlockSize >> 2) - KHR_DF_WORD_SAMPLESTART) / KHR_DF_WORD_SAMPLEWORDS); + + public KhrDfModel ColorModel + { + get => (KhrDfModel)AccessGet(KhrDataFormatDescriptorAccessor.ColorModel); + set => AccessSet(KhrDataFormatDescriptorAccessor.ColorModel, (uint)value); + } + public KhrDfPrimaries ColorPrimaries + { + get => (KhrDfPrimaries)AccessGet(KhrDataFormatDescriptorAccessor.ColorPrimaries); + set => AccessSet(KhrDataFormatDescriptorAccessor.ColorPrimaries, (uint)value); + } + public KhrDfTransfer TransferFunction + { + get => (KhrDfTransfer)AccessGet(KhrDataFormatDescriptorAccessor.TransferFunction); + set => AccessSet(KhrDataFormatDescriptorAccessor.TransferFunction, (uint)value); + } + public KhrDfFlags Flags + { + get => (KhrDfFlags)AccessGet(KhrDataFormatDescriptorAccessor.Flags); + set => AccessSet(KhrDataFormatDescriptorAccessor.Flags, (uint)value); + } + public uint TexelBlockDimension0 + { + get => AccessGet(KhrDataFormatDescriptorAccessor.TexelBlockDimension0); + set => AccessSet(KhrDataFormatDescriptorAccessor.TexelBlockDimension0, value); + } + public uint TexelBlockDimension1 + { + get => AccessGet(KhrDataFormatDescriptorAccessor.TexelBlockDimension1); + set => AccessSet(KhrDataFormatDescriptorAccessor.TexelBlockDimension1, value); + } + public uint TexelBlockDimension2 + { + get => AccessGet(KhrDataFormatDescriptorAccessor.TexelBlockDimension2); + set => AccessSet(KhrDataFormatDescriptorAccessor.TexelBlockDimension2, value); + } + public uint TexelBlockDimension3 + { + get => AccessGet(KhrDataFormatDescriptorAccessor.TexelBlockDimension3); + set => AccessSet(KhrDataFormatDescriptorAccessor.TexelBlockDimension3, value); + } + public uint BytesPlane0 + { + get => AccessGet(KhrDataFormatDescriptorAccessor.BytesPlane0); + set => AccessSet(KhrDataFormatDescriptorAccessor.BytesPlane0, value); + } + public uint BytesPlane1 + { + get => AccessGet(KhrDataFormatDescriptorAccessor.BytesPlane1); + set => AccessSet(KhrDataFormatDescriptorAccessor.BytesPlane1, value); + } + public uint BytesPlane2 + { + get => AccessGet(KhrDataFormatDescriptorAccessor.BytesPlane2); + set => AccessSet(KhrDataFormatDescriptorAccessor.BytesPlane2, value); + } + public uint BytesPlane3 + { + get => AccessGet(KhrDataFormatDescriptorAccessor.BytesPlane3); + set => AccessSet(KhrDataFormatDescriptorAccessor.BytesPlane3, value); + } + public uint BytesPlane4 + { + get => AccessGet(KhrDataFormatDescriptorAccessor.BytesPlane4); + set => AccessSet(KhrDataFormatDescriptorAccessor.BytesPlane4, value); + } + public uint BytesPlane5 + { + get => AccessGet(KhrDataFormatDescriptorAccessor.BytesPlane5); + set => AccessSet(KhrDataFormatDescriptorAccessor.BytesPlane5, value); + } + public uint BytesPlane6 + { + get => AccessGet(KhrDataFormatDescriptorAccessor.BytesPlane6); + set => AccessSet(KhrDataFormatDescriptorAccessor.BytesPlane6, value); + } + public uint BytesPlane7 + { + get => AccessGet(KhrDataFormatDescriptorAccessor.BytesPlane7); + set => AccessSet(KhrDataFormatDescriptorAccessor.BytesPlane7, value); + } + + private uint AccessSampleGet(int sampleIdx, in KhrDataFormatDescriptorAccessor accessor) + { + var offset = KHR_DF_WORD_SAMPLESTART + sampleIdx * KHR_DF_WORD_SAMPLEWORDS + accessor.offset; + return (data[offset] >> accessor.shift) & accessor.mask; + } + private void AccessSampleSet(int sampleIdx, in KhrDataFormatDescriptorAccessor accessor, uint value) + { + var offset = KHR_DF_WORD_SAMPLESTART + sampleIdx * KHR_DF_WORD_SAMPLEWORDS + accessor.offset; + var u = data[offset]; + u &= ~(accessor.mask << accessor.shift); + u |= (value & accessor.mask) << accessor.shift; + data[offset] = u; + } + } + + internal readonly struct KhrDataFormatDescriptorAccessor + { + public readonly uint offset; + public readonly int shift; + public readonly uint mask; + + public KhrDataFormatDescriptorAccessor(uint offset, int shift, uint mask) + { + this.offset = offset; + this.shift = shift; + this.mask = mask; + } + + public static readonly KhrDataFormatDescriptorAccessor VendorId = new KhrDataFormatDescriptorAccessor + ( + offset: 0, + shift: 0, + mask: 0x1FFFFU + ); + public static readonly KhrDataFormatDescriptorAccessor DescriptorType = new KhrDataFormatDescriptorAccessor + ( + offset: 0, + shift: 17, + mask: 0x7FFFU + ); + public static readonly KhrDataFormatDescriptorAccessor VersionNumber = new KhrDataFormatDescriptorAccessor + ( + offset: 1, + shift: 0, + mask: 0xFFFFU + ); + public static readonly KhrDataFormatDescriptorAccessor DescriptorBlockSize = new KhrDataFormatDescriptorAccessor + ( + offset: 1, + shift: 16, + mask: 0xFFFFU + ); + public static readonly KhrDataFormatDescriptorAccessor ColorModel = new KhrDataFormatDescriptorAccessor + ( + offset: 2, + shift: 0, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor ColorPrimaries = new KhrDataFormatDescriptorAccessor + ( + offset: 2, + shift: 8, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor TransferFunction = new KhrDataFormatDescriptorAccessor + ( + offset: 2, + shift: 16, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor Flags = new KhrDataFormatDescriptorAccessor + ( + offset: 2, + shift: 24, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor TexelBlockDimension0 = new KhrDataFormatDescriptorAccessor + ( + offset: 3, + shift: 0, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor TexelBlockDimension1 = new KhrDataFormatDescriptorAccessor + ( + offset: 3, + shift: 8, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor TexelBlockDimension2 = new KhrDataFormatDescriptorAccessor + ( + offset: 3, + shift: 16, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor TexelBlockDimension3 = new KhrDataFormatDescriptorAccessor + ( + offset: 3, + shift: 24, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor BytesPlane0 = new KhrDataFormatDescriptorAccessor + ( + offset: 4, + shift: 0, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor BytesPlane1 = new KhrDataFormatDescriptorAccessor + ( + offset: 4, + shift: 8, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor BytesPlane2 = new KhrDataFormatDescriptorAccessor + ( + offset: 4, + shift: 16, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor BytesPlane3 = new KhrDataFormatDescriptorAccessor + ( + offset: 4, + shift: 24, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor BytesPlane4 = new KhrDataFormatDescriptorAccessor + ( + offset: 5, + shift: 0, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor BytesPlane5 = new KhrDataFormatDescriptorAccessor + ( + offset: 5, + shift: 8, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor BytesPlane6 = new KhrDataFormatDescriptorAccessor + ( + offset: 5, + shift: 16, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor BytesPlane7 = new KhrDataFormatDescriptorAccessor + ( + offset: 5, + shift: 24, + mask: 0xFFU + ); + + // Samples + public static readonly KhrDataFormatDescriptorAccessor SampleBitOffset = new KhrDataFormatDescriptorAccessor + ( + offset: 0, + shift: 0, + mask: 0xFFFFU + ); + public static readonly KhrDataFormatDescriptorAccessor SampleBitLength = new KhrDataFormatDescriptorAccessor + ( + offset: 0, + shift: 16, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor SampleChannelType = new KhrDataFormatDescriptorAccessor + ( + offset: 0, + shift: 24, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor SamplePosition0 = new KhrDataFormatDescriptorAccessor + ( + offset: 1, + shift: 0, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor SamplePosition1 = new KhrDataFormatDescriptorAccessor + ( + offset: 1, + shift: 8, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor SamplePosition2 = new KhrDataFormatDescriptorAccessor + ( + offset: 1, + shift: 16, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor SamplePosition3 = new KhrDataFormatDescriptorAccessor + ( + offset: 1, + shift: 24, + mask: 0xFFU + ); + public static readonly KhrDataFormatDescriptorAccessor SampleLower = new KhrDataFormatDescriptorAccessor + ( + offset: 2, + shift: 0, + mask: 0xFFFFFFFFU + ); + public static readonly KhrDataFormatDescriptorAccessor SampleUpper = new KhrDataFormatDescriptorAccessor + ( + offset: 3, + shift: 0, + mask: 0xFFFFFFFFU + ); + } + + public static class DataFormatDescriptorLibrary + { + internal const uint FloatNegativeOne = 0xBF800000U; // IEEE 754 floating-point representation for -1.0f + internal const uint FloatPositiveOne = 0x3F800000U; // IEEE 754 floating-point representation for 1.0f + //internal const uint FloatMaxNonInf = 0x7F7FFFFFU; // 3.40282e+38 + //internal const uint FloatMinNegNonInf = 0xFF7FFFFFU; // -3.40282e+38 + //internal const uint FloatZero = 0; + + public static KhrBasicDataFormatDescriptor R8 + { + get + { + var d = new KhrBasicDataFormatDescriptor(1) + { + BytesPlane0 = 1, + ColorModel = KhrDfModel.KhrDfModelRgbsda, + ColorPrimaries = KhrDfPrimaries.KhrDfPrimariesSrgb, + TransferFunction = KhrDfTransfer.KhrDfTransferLinear + }; + d.Samples[0].BitOffset = 0; + d.Samples[0].BitLength = 7; + d.Samples[0].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaR; + d.Samples[0].SampleLower = 0; + d.Samples[0].SampleUpper = 255; + + return d; + } + } + public static KhrBasicDataFormatDescriptor R8G8 + { + get + { + var d = new KhrBasicDataFormatDescriptor(2) + { + BytesPlane0 = 2, + ColorModel = KhrDfModel.KhrDfModelRgbsda, + ColorPrimaries = KhrDfPrimaries.KhrDfPrimariesSrgb, + TransferFunction = KhrDfTransfer.KhrDfTransferLinear + }; + d.Samples[0].BitOffset = 0; + d.Samples[0].BitLength = 7; + d.Samples[0].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaR; + d.Samples[0].SampleLower = 0; + d.Samples[0].SampleUpper = 255; + + d.Samples[1].BitOffset = 8; + d.Samples[1].BitLength = 7; + d.Samples[1].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaG; + d.Samples[1].SampleLower = 0; + d.Samples[1].SampleUpper = 255; + + return d; + } + } + public static KhrBasicDataFormatDescriptor Rgb24 + { + get + { + var d = new KhrBasicDataFormatDescriptor(3) + { + BytesPlane0 = 3, + ColorModel = KhrDfModel.KhrDfModelRgbsda, + ColorPrimaries = KhrDfPrimaries.KhrDfPrimariesSrgb, + TransferFunction = KhrDfTransfer.KhrDfTransferLinear + }; + d.Samples[0].BitOffset = 0; + d.Samples[0].BitLength = 7; + d.Samples[0].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaR; + d.Samples[0].SampleLower = 0; + d.Samples[0].SampleUpper = 255; + + d.Samples[1].BitOffset = 8; + d.Samples[1].BitLength = 7; + d.Samples[1].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaG; + d.Samples[1].SampleLower = 0; + d.Samples[1].SampleUpper = 255; + + d.Samples[2].BitOffset = 16; + d.Samples[2].BitLength = 7; + d.Samples[2].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaB; + d.Samples[2].SampleLower = 0; + d.Samples[2].SampleUpper = 255; + + return d; + } + } + public static KhrBasicDataFormatDescriptor Rgba32 + { + get + { + var d = new KhrBasicDataFormatDescriptor(4) + { + BytesPlane0 = 4, + ColorModel = KhrDfModel.KhrDfModelRgbsda, + ColorPrimaries = KhrDfPrimaries.KhrDfPrimariesSrgb, + TransferFunction = KhrDfTransfer.KhrDfTransferLinear + }; + d.Samples[0].BitOffset = 0; + d.Samples[0].BitLength = 7; + d.Samples[0].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaR; + d.Samples[0].SampleLower = 0; + d.Samples[0].SampleUpper = 255; + + d.Samples[1].BitOffset = 8; + d.Samples[1].BitLength = 7; + d.Samples[1].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaG; + d.Samples[1].SampleLower = 0; + d.Samples[1].SampleUpper = 255; + + d.Samples[2].BitOffset = 16; + d.Samples[2].BitLength = 7; + d.Samples[2].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaB; + d.Samples[2].SampleLower = 0; + d.Samples[2].SampleUpper = 255; + + d.Samples[3].BitOffset = 24; + d.Samples[3].BitLength = 7; + d.Samples[3].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaA; + d.Samples[3].SampleLower = 0; + d.Samples[3].SampleUpper = 255; + + return d; + } + } + public static KhrBasicDataFormatDescriptor Bgra32 + { + get + { + var d = Rgba32; + // Swap channels + d.Samples[0].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaB; + d.Samples[2].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaR; + return d; + } + } + public static KhrBasicDataFormatDescriptor RgbaFloat + { + get + { + var d = new KhrBasicDataFormatDescriptor(4) + { + BytesPlane0 = 16, + ColorModel = KhrDfModel.KhrDfModelRgbsda, + ColorPrimaries = KhrDfPrimaries.KhrDfPrimariesSrgb, + TransferFunction = KhrDfTransfer.KhrDfTransferLinear + }; + d.Samples[0].BitOffset = 0; + d.Samples[0].BitLength = 31; + d.Samples[0].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaR + | (uint)KhrDfSampleDatatypeQualifiers.KhrDfSampleDatatypeFloat + | (uint)KhrDfSampleDatatypeQualifiers.KhrDfSampleDatatypeSigned; + d.Samples[0].SampleLower = FloatNegativeOne; + d.Samples[0].SampleUpper = FloatPositiveOne; + + d.Samples[1].BitOffset = 32; + d.Samples[1].BitLength = 31; + d.Samples[1].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaG + | (uint)KhrDfSampleDatatypeQualifiers.KhrDfSampleDatatypeFloat + | (uint)KhrDfSampleDatatypeQualifiers.KhrDfSampleDatatypeSigned; + d.Samples[1].SampleLower = FloatNegativeOne; + d.Samples[1].SampleUpper = FloatPositiveOne; + + d.Samples[2].BitOffset = 64; + d.Samples[2].BitLength = 31; + d.Samples[2].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaB + | (uint)KhrDfSampleDatatypeQualifiers.KhrDfSampleDatatypeFloat + | (uint)KhrDfSampleDatatypeQualifiers.KhrDfSampleDatatypeSigned; + d.Samples[2].SampleLower = FloatNegativeOne; + d.Samples[2].SampleUpper = FloatPositiveOne; + + d.Samples[3].BitOffset = 96; + d.Samples[3].BitLength = 31; + d.Samples[3].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaA + | (uint)KhrDfSampleDatatypeQualifiers.KhrDfSampleDatatypeFloat + | (uint)KhrDfSampleDatatypeQualifiers.KhrDfSampleDatatypeSigned; + d.Samples[3].SampleLower = FloatNegativeOne; + d.Samples[3].SampleUpper = FloatPositiveOne; + + return d; + } + } + + public static KhrBasicDataFormatDescriptor R8G8B8E8 // TODO: ?? + { + get + { + var d = new KhrBasicDataFormatDescriptor(6) + { + BytesPlane0 = 4, + ColorModel = KhrDfModel.KhrDfModelRgbsda, + ColorPrimaries = KhrDfPrimaries.KhrDfPrimariesSrgb, + TransferFunction = KhrDfTransfer.KhrDfTransferLinear + }; + + d.Samples[0].BitOffset = 0; + d.Samples[0].BitLength = 7; + d.Samples[0].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaR; + d.Samples[0].SampleLower = 0; + d.Samples[0].SampleUpper = 255; + + d.Samples[1].BitOffset = 24; + d.Samples[1].BitLength = 7; + d.Samples[1].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaR | + (uint)KhrDfSampleDatatypeQualifiers.KhrDfSampleDatatypexponent; + d.Samples[1].SampleLower = 128; + d.Samples[1].SampleUpper = 255; + + d.Samples[2].BitOffset = 8; + d.Samples[2].BitLength = 7; + d.Samples[2].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaG; + d.Samples[2].SampleLower = 0; + d.Samples[2].SampleUpper = 255; + + d.Samples[3].BitOffset = 24; + d.Samples[3].BitLength = 7; + d.Samples[3].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaG | + (uint)KhrDfSampleDatatypeQualifiers.KhrDfSampleDatatypexponent; + d.Samples[3].SampleLower = 128; + d.Samples[3].SampleUpper = 255; + + d.Samples[4].BitOffset = 16; + d.Samples[4].BitLength = 7; + d.Samples[4].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaB; + d.Samples[4].SampleLower = 0; + d.Samples[4].SampleUpper = 255; + + d.Samples[5].BitOffset = 24; + d.Samples[5].BitLength = 7; + d.Samples[5].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaB | + (uint)KhrDfSampleDatatypeQualifiers.KhrDfSampleDatatypexponent; + d.Samples[5].SampleLower = 128; + d.Samples[5].SampleUpper = 255; + + return d; + } + } + + public static KhrBasicDataFormatDescriptor R9G9B9E5 + { + get + { + var d = new KhrBasicDataFormatDescriptor(6) + { + BytesPlane0 = 4, + ColorModel = KhrDfModel.KhrDfModelRgbsda, + ColorPrimaries = KhrDfPrimaries.KhrDfPrimariesSrgb, + TransferFunction = KhrDfTransfer.KhrDfTransferLinear + }; + + d.Samples[0].BitOffset = 0; + d.Samples[0].BitLength = 8; + d.Samples[0].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaR; + d.Samples[0].SampleLower = 0; + d.Samples[0].SampleUpper = 256; + + d.Samples[1].BitOffset = 27; + d.Samples[1].BitLength = 4; + d.Samples[1].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaR | + (uint)KhrDfSampleDatatypeQualifiers.KhrDfSampleDatatypexponent; + d.Samples[1].SampleLower = 15; + d.Samples[1].SampleUpper = 31; + + d.Samples[2].BitOffset = 9; + d.Samples[2].BitLength = 8; + d.Samples[2].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaG; + d.Samples[2].SampleLower = 0; + d.Samples[2].SampleUpper = 256; + + d.Samples[3].BitOffset = 27; + d.Samples[3].BitLength = 4; + d.Samples[3].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaG | + (uint)KhrDfSampleDatatypeQualifiers.KhrDfSampleDatatypexponent; + d.Samples[3].SampleLower = 15; + d.Samples[3].SampleUpper = 31; + + d.Samples[4].BitOffset = 18; + d.Samples[4].BitLength = 8; + d.Samples[4].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaB; + d.Samples[4].SampleLower = 0; + d.Samples[4].SampleUpper = 256; + + d.Samples[5].BitOffset = 27; + d.Samples[5].BitLength = 4; + d.Samples[5].ChannelType = (uint)KhrDfModelChannels.KhrDfChannelRgbsdaB | + (uint)KhrDfSampleDatatypeQualifiers.KhrDfSampleDatatypexponent; + d.Samples[5].SampleLower = 15; + d.Samples[5].SampleUpper = 31; + + return d; + } + } + + } +} diff --git a/BCnEncoder.Net.TextureFormats/Khronos/KhrDataFormatDescriptorEnums.cs b/BCnEncoder.Net.TextureFormats/Khronos/KhrDataFormatDescriptorEnums.cs new file mode 100644 index 0000000..75cb87d --- /dev/null +++ b/BCnEncoder.Net.TextureFormats/Khronos/KhrDataFormatDescriptorEnums.cs @@ -0,0 +1,437 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BCnEncoder.TextureFormats +{ + /* The Khronos Data Format Specification (version 1.1) */ + /* + ** Copyright (c) 2015 The Khronos Group Inc. + ** + ** Permission is hereby granted, free of charge, to any person obtaining a + ** copy of this software and/or associated documentation files (the + ** "Materials"), to deal in the Materials without restriction, including + ** without limitation the rights to use, copy, modify, merge, publish, + ** distribute, sublicense, and/or sell copies of the Materials, and to + ** permit persons to whom the Materials are furnished to do so, subject to + ** the following conditions: + ** + ** The above copyright notice and this permission notice shall be included + ** in all copies or substantial portions of the Materials. + ** + ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + */ + + /* This header defines a structure that can describe the layout of image + formats in memory. This means that the data format is transparent to + the application, and the expectation is that this should be used when + the layout is defined external to the API. Many Khronos APIs deliberately + keep the internal layout of images opaque, to allow proprietary layouts + and optimisations. This structure is not appropriate for describing + opaque layouts. */ + + /* Vendor ids */ + public enum KhrDfVendorId : uint + { + /* Standard Khronos descriptor */ + KhrDfVendoridKhronos = 0U, + KhrDfVendoridMax = 0xFFFFU + } + + + /* Descriptor types */ + public enum KhrDfKhrDescriptorType : uint + { + /* Default Khronos descriptor */ + KhrDfKhrDescriptortypeBasicformat = 0U, + KhrDfKhrDescriptortypeMax = 0xFFFFU + } + + + /* Model in which the color coordinate space is defined. + There is no requirement that a color format use all the + channel types that are defined in the color model. */ + public enum KhrDfModel : uint + { + /* No interpretation of color channels defined */ + KhrDfModelUnspecified = 0U, + + /* Color primaries (red, green, blue) + alpha, depth and stencil */ + KhrDfModelRgbsda = 1U, + + /* Color differences (Y', Cb, Cr) + alpha, depth and stencil */ + KhrDfModelYuvsda = 2U, + + /* Color differences (Y', I, Q) + alpha, depth and stencil */ + KhrDfModelYiqsda = 3U, + + /* Perceptual color (CIE L*a*b*) + alpha, depth and stencil */ + KhrDfModelLabsda = 4U, + + /* Subtractive colors (cyan, magenta, yellow, black) + alpha */ + KhrDfModelCmyka = 5U, + + /* Non-color coordinate data (X, Y, Z, W) */ + KhrDfModelXyzw = 6U, + + /* Hue, saturation, value, hue angle on color circle, plus alpha */ + KhrDfModelHsvaAng = 7U, + + /* Hue, saturation, lightness, hue angle on color circle, plus alpha */ + KhrDfModelHslaAng = 8U, + + /* Hue, saturation, value, hue on color hexagon, plus alpha */ + KhrDfModelHsvaHex = 7U, + + /* Hue, saturation, lightness, hue on color hexagon, plus alpha */ + KhrDfModelHslaHex = 8U, + + /* Lightweight approximate color difference (luma, orange, green) */ + KhrDfModelYcgcoa = 9U, + + /* Compressed formats start at 128. */ + /* These compressed formats should generally have a single sample, + sited at the 0,0 position of the texel block. Where multiple + channels are used to distinguish formats, these should be cosited. */ + /* Direct3D (and S3) compressed formats */ + /* Note that premultiplied status is recorded separately */ + /* DXT1 "channels" are RGB (0), Alpha (1) */ + /* DXT1/BC1 with one channel is opaque */ + /* DXT1/BC1 with a cosited alpha sample is transparent */ + KhrDfModelDxt1A = 128U, + KhrDfModelBc1A = 128U, + + /* DXT2/DXT3/BC2, with explicit 4-bit alpha */ + KhrDfModelDxt2 = 129U, + KhrDfModelDxt3 = 129U, + KhrDfModelBc2 = 129U, + + /* DXT4/DXT5/BC3, with interpolated alpha */ + KhrDfModelDxt4 = 130U, + KhrDfModelDxt5 = 130U, + KhrDfModelBc3 = 130U, + + /* BC4 - single channel interpolated 8-bit data */ + /* (The UNORM/SNORM variation is recorded in the channel data) */ + KhrDfModelBc4 = 131U, + + /* BC5 - two channel interpolated 8-bit data */ + /* (The UNORM/SNORM variation is recorded in the channel data) */ + KhrDfModelBc5 = 132U, + + /* BC6H - DX11 format for 16-bit float channels */ + KhrDfModelBc6H = 133U, + + /* BC7 - DX11 format */ + KhrDfModelBc7 = 134U, + /* Gap left for future desktop expansion */ + + /* Mobile compressed formats follow */ + /* A format of ETC1 indicates that the format shall be decodable + by an ETC1-compliant decoder and not rely on ETC2 features */ + KhrDfModeltc1 = 160U, + + /* A format of ETC2 is permitted to use ETC2 encodings on top of + the baseline ETC1 specification */ + /* The ETC2 format has channels "red", "green", "RGB" and "alpha", + which should be cosited samples */ + /* Punch-through alpha can be distinguished from full alpha by + the plane size in bytes required for the texel block */ + KhrDfModeltc2 = 161U, + + /* Adaptive Scalable Texture Compression */ + /* ASTC HDR vs LDR is determined by the float flag in the channel */ + /* ASTC block size can be distinguished by texel block size */ + KhrDfModelAstc = 162U, + + /* Proprietary formats (PVRTC, ATITC, etc.) should follow */ + KhrDfModelMax = 0xFFU + } + + /* Definition of channel names for each color model */ + public enum KhrDfModelChannels : uint + { + /* Unspecified format with nominal channel numbering */ + KhrDfChannelUnspecified0 = 0U, + KhrDfChannelUnspecified1 = 1U, + KhrDfChannelUnspecified2 = 2U, + KhrDfChannelUnspecified3 = 3U, + KhrDfChannelUnspecified4 = 4U, + KhrDfChannelUnspecified5 = 5U, + KhrDfChannelUnspecified6 = 6U, + KhrDfChannelUnspecified7 = 7U, + KhrDfChannelUnspecified8 = 8U, + KhrDfChannelUnspecified9 = 9U, + KhrDfChannelUnspecified10 = 10U, + KhrDfChannelUnspecified11 = 11U, + KhrDfChannelUnspecified12 = 12U, + KhrDfChannelUnspecified13 = 13U, + KhrDfChannelUnspecified14 = 14U, + KhrDfChannelUnspecified15 = 15U, + + /* MODEL_RGBSDA - red, green, blue, stencil, depth, alpha */ + KhrDfChannelRgbsdaRed = 0U, + KhrDfChannelRgbsdaR = 0U, + KhrDfChannelRgbsdaGreen = 1U, + KhrDfChannelRgbsdaG = 1U, + KhrDfChannelRgbsdaBlue = 2U, + KhrDfChannelRgbsdaB = 2U, + KhrDfChannelRgbsdaStencil = 13U, + KhrDfChannelRgbsdaS = 13U, + KhrDfChannelRgbsdaDepth = 14U, + KhrDfChannelRgbsdaD = 14U, + KhrDfChannelRgbsdaAlpha = 15U, + KhrDfChannelRgbsdaA = 15U, + + /* MODEL_YUVSDA - luma, Cb, Cr, stencil, depth, alpha */ + KhrDfChannelYuvsdaY = 0U, + KhrDfChannelYuvsdaCb = 1U, + KhrDfChannelYuvsdaU = 1U, + KhrDfChannelYuvsdaCr = 2U, + KhrDfChannelYuvsdaV = 2U, + KhrDfChannelYuvsdaStencil = 13U, + KhrDfChannelYuvsdaS = 13U, + KhrDfChannelYuvsdaDepth = 14U, + KhrDfChannelYuvsdaD = 14U, + KhrDfChannelYuvsdaAlpha = 15U, + KhrDfChannelYuvsdaA = 15U, + + /* MODEL_YIQSDA - luma, in-phase, quadrature, stencil, depth, alpha */ + KhrDfChannelYiqsdaY = 0U, + KhrDfChannelYiqsdaI = 1U, + KhrDfChannelYiqsdaQ = 2U, + KhrDfChannelYiqsdaStencil = 13U, + KhrDfChannelYiqsdaS = 13U, + KhrDfChannelYiqsdaDepth = 14U, + KhrDfChannelYiqsdaD = 14U, + KhrDfChannelYiqsdaAlpha = 15U, + KhrDfChannelYiqsdaA = 15U, + + /* MODEL_LABSDA - CIELAB/L*a*b* luma, red-green, blue-yellow, stencil, depth, alpha */ + KhrDfChannelLabsdaL = 0U, + KhrDfChannelLabsdaA = 1U, + KhrDfChannelLabsdaB = 2U, + KhrDfChannelLabsdaStencil = 13U, + KhrDfChannelLabsdaS = 13U, + KhrDfChannelLabsdaDepth = 14U, + KhrDfChannelLabsdaD = 14U, + KhrDfChannelLabsdaAlpha = 15U, + + /* NOTE: KHR_DF_CHANNEL_LABSDA_A is not a synonym for alpha! */ + /* MODEL_CMYKA - cyan, magenta, yellow, key/blacK, alpha */ + KhrDfChannelCmyksdaCyan = 0U, + KhrDfChannelCmyksdaC = 0U, + KhrDfChannelCmyksdaMagenta = 1U, + KhrDfChannelCmyksdaM = 1U, + KhrDfChannelCmyksdaYellow = 2U, + KhrDfChannelCmyksdaY = 2U, + KhrDfChannelCmyksdaKey = 3U, + KhrDfChannelCmyksdaBlack = 3U, + KhrDfChannelCmyksdaK = 3U, + KhrDfChannelCmyksdaAlpha = 15U, + KhrDfChannelCmyksdaA = 15U, + + /* MODEL_XYZW - coordinates x, y, z, w */ + KhrDfChannelXyzwX = 0U, + KhrDfChannelXyzwY = 1U, + KhrDfChannelXyzwZ = 2U, + KhrDfChannelXyzwW = 3U, + + /* MODEL_HSVA_ANG - value (luma), saturation, hue, alpha, angular projection, conical space */ + KhrDfChannelHsvaAngValue = 0U, + KhrDfChannelHsvaAngV = 0U, + KhrDfChannelHsvaAngSaturation = 1U, + KhrDfChannelHsvaAngS = 1U, + KhrDfChannelHsvaAngHue = 2U, + KhrDfChannelHsvaAngH = 2U, + KhrDfChannelHsvaAngAlpha = 15U, + KhrDfChannelHsvaAngA = 15U, + + /* MODEL_HSLA_ANG - lightness (luma), saturation, hue, alpha, angular projection, double conical space */ + KhrDfChannelHslaAngLightness = 0U, + KhrDfChannelHslaAngL = 0U, + KhrDfChannelHslaAngSaturation = 1U, + KhrDfChannelHslaAngS = 1U, + KhrDfChannelHslaAngHue = 2U, + KhrDfChannelHslaAngH = 2U, + KhrDfChannelHslaAngAlpha = 15U, + KhrDfChannelHslaAngA = 15U, + + /* MODEL_HSVA_HEX - value (luma), saturation, hue, alpha, hexagonal projection, conical space */ + KhrDfChannelHsvaHexValue = 0U, + KhrDfChannelHsvaHexV = 0U, + KhrDfChannelHsvaHexSaturation = 1U, + KhrDfChannelHsvaHexS = 1U, + KhrDfChannelHsvaHexHue = 2U, + KhrDfChannelHsvaHexH = 2U, + KhrDfChannelHsvaHexAlpha = 15U, + KhrDfChannelHsvaHexA = 15U, + + /* MODEL_HSLA_HEX - lightness (luma), saturation, hue, alpha, hexagonal projection, double conical space */ + KhrDfChannelHslaHexLightness = 0U, + KhrDfChannelHslaHexL = 0U, + KhrDfChannelHslaHexSaturation = 1U, + KhrDfChannelHslaHexS = 1U, + KhrDfChannelHslaHexHue = 2U, + KhrDfChannelHslaHexH = 2U, + KhrDfChannelHslaHexAlpha = 15U, + KhrDfChannelHslaHexA = 15U, + + /* MODEL_YCGCOA - luma, green delta, orange delta, alpha */ + KhrDfChannelYcgcoaY = 0U, + KhrDfChannelYcgcoaCg = 1U, + KhrDfChannelYcgcoaCo = 2U, + KhrDfChannelYcgcoaAlpha = 15U, + KhrDfChannelYcgcoaA = 15U, + + /* Compressed formats */ + /* MODEL_DXT1A/MODEL_BC1A */ + KhrDfChannelDxt1AColor = 0U, + KhrDfChannelBc1AColor = 0U, + KhrDfChannelDxt1AAlphapresent = 1U, + KhrDfChannelBc1AAlphapresent = 1U, + + /* MODEL_DXT2/3/MODEL_BC2 */ + KhrDfChannelDxt2Color = 0U, + KhrDfChannelDxt3Color = 0U, + KhrDfChannelBc2Color = 0U, + KhrDfChannelDxt2Alpha = 15U, + KhrDfChannelDxt3Alpha = 15U, + KhrDfChannelBc2Alpha = 15U, + + /* MODEL_DXT4/5/MODEL_BC3 */ + KhrDfChannelDxt4Color = 0U, + KhrDfChannelDxt5Color = 0U, + KhrDfChannelBc3Color = 0U, + KhrDfChannelDxt4Alpha = 15U, + KhrDfChannelDxt5Alpha = 15U, + KhrDfChannelBc3Alpha = 15U, + + /* MODEL_BC4 */ + KhrDfChannelBc4Data = 0U, + + /* MODEL_BC5 */ + KhrDfChannelBc5Red = 0U, + KhrDfChannelBc5R = 0U, + KhrDfChannelBc5Green = 1U, + KhrDfChannelBc5G = 1U, + + /* MODEL_BC6H */ + KhrDfChannelBc6HColor = 0U, + + /* MODEL_BC7 */ + KhrDfChannelBc7Data = 0U, + + /* MODELTC1 */ + KhrDfChanneltc1Data = 0U, + KhrDfChanneltc1Color = 0U, + + /* MODELTC2 */ + KhrDfChanneltc2Red = 0U, + KhrDfChanneltc2R = 0U, + KhrDfChanneltc2Green = 1U, + KhrDfChanneltc2G = 1U, + KhrDfChanneltc2Color = 2U, + KhrDfChanneltc2Alpha = 15U, + KhrDfChanneltc2A = 15U, + + /* MODEL_ASTC */ + KhrDfChannelAstcData = 0U, + + /* Common channel names shared by multiple formats */ + KhrDfChannelCommonLuma = 0U, + KhrDfChannelCommonL = 0U, + KhrDfChannelCommonStencil = 13U, + KhrDfChannelCommonS = 13U, + KhrDfChannelCommonDepth = 14U, + KhrDfChannelCommonD = 14U, + KhrDfChannelCommonAlpha = 15U, + KhrDfChannelCommonA = 15U + } + + + /* Definition of the primary colors in color coordinates. + This is implicitly responsible for defining the conversion + between RGB an YUV color spaces. + LAB and related absolute color models should use + KHR_DF_PRIMARIES_CIEXYZ. */ + public enum KhrDfPrimaries : uint + { + /* No color primaries defined */ + KhrDfPrimariesUnspecified = 0U, + + /* Color primaries of ITU-R BT.709 and sRGB */ + KhrDfPrimariesBt709 = 1U, + + /* Synonym for KHR_DF_PRIMARIES_BT709 */ + KhrDfPrimariesSrgb = 1U, + + /* Color primaries of ITU-R BT.601 (625-line EBU variant) */ + KhrDfPrimariesBt601Bu = 2U, + + /* Color primaries of ITU-R BT.601 (525-line SMPTE C variant) */ + KhrDfPrimariesBt601Smpte = 3U, + + /* Color primaries of ITU-R BT.2020 */ + KhrDfPrimariesBt2020 = 4U, + + /* CIE theoretical color coordinate space */ + KhrDfPrimariesCiexyz = 5U, + + /* Academy Color Encoding System primaries */ + KhrDfPrimariesAces = 6U, + KhrDfPrimariesMax = 0xFFU + } + + + /* Definition of the optical to digital transfer function + ("gamma correction"). Most transfer functions are not a pure + power function and also include a linear element. + LAB and related absolute color representations should use + KHR_DF_TRANSFER_UNSPECIFIED. */ + public enum KhrDfTransfer : uint + { + /* No transfer function defined */ + KhrDfTransferUnspecified = 0U, + + /* Linear transfer function (value proportional to intensity) */ + KhrDfTransferLinear = 1U, + + /* Perceptually-linear transfer function of sRGH (~2.4) */ + KhrDfTransferSrgb = 2U, + + /* Perceptually-linear transfer function of ITU specifications (~1/.45) */ + KhrDfTransferItu = 3U, + + /* Perceptually-linear gamma function of NTSC (simple 2.2 gamma) */ + KhrDfTransferNtsc = 4U, + + /* Sony S-log used by Sony video cameras */ + KhrDfTransferSlog = 5U, + + /* Sony S-log 2 used by Sony video cameras */ + KhrDfTransferSlog2 = 6U, + KhrDfTransferMax = 0xFFU + } + + public enum KhrDfFlags : uint + { + KhrDfFlagAlphaStraight = 0U, + KhrDfFlagAlphaPremultiplied = 1U + } + + [Flags] + public enum KhrDfSampleDatatypeQualifiers : uint + { + KhrDfSampleDatatypeLinear = 1U << 4, + KhrDfSampleDatatypexponent = 1U << 5, + KhrDfSampleDatatypeSigned = 1U << 6, + KhrDfSampleDatatypeFloat = 1U << 7 + } +} diff --git a/BCnEncoder.Net.TextureFormats/Khronos/Ktx2File.cs b/BCnEncoder.Net.TextureFormats/Khronos/Ktx2File.cs new file mode 100644 index 0000000..2384a17 --- /dev/null +++ b/BCnEncoder.Net.TextureFormats/Khronos/Ktx2File.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using BCnEncoder.Shared; + +namespace BCnEncoder.TextureFormats +{ + public class Ktx2File : ITextureFileFormat + { + /// + public bool SupportsLdr { get; } + + /// + public bool SupportsHdr { get; } + + /// + public bool SupportsCubeMap { get; } + + /// + public bool SupportsMipMaps { get; } + + /// + public bool SupportsArrays { get; } + + /// + public bool IsSupportedFormat(CompressionFormat format) + { + return false; + } + + /// + public void FromTextureData(BCnTextureData textureData) + { + } + + /// + public BCnTextureData ToTextureData() + { + return null; + } + + /// + public void ReadFromStream(Stream inputStream) + { + } + + /// + public void WriteToStream(Stream outputStream) + { + } + } + + [StructLayout(LayoutKind.Sequential)] + public unsafe struct Ktx2Header + { + public fixed byte Identifier[12]; + public VkFormat VkFormat; + public uint TypeSize; + public uint PixelWidth; + public uint PixelHeight; + public uint PixelDepth; + public uint LayerCount; + public uint FaceCount; + public uint LevelCount; + public SuperCompressionScheme SuperCompressionScheme; + + public bool VerifyHeader() + { + Span id = stackalloc byte[] { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; + for (var i = 0; i < id.Length; i++) + { + if (Identifier[i] != id[i]) return false; + } + return true; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct Ktx2Index + { + public uint DfdByteOffset; // Data format descriptor + public uint DfdByteLength; + public uint KvdByteOffset; // Key value data + public uint KvdByteLength; + public ulong SgdByteOffset; // SuperCompressionGlobalData + public ulong SgdByteLength; + } + + [StructLayout(LayoutKind.Sequential)] + public struct Ktx2LevelIndex // For each mip level + { + public ulong ByteOffset; + public ulong ByteLength; + public ulong UncompressedByteLength; + } + + public enum SuperCompressionScheme : uint + { + None = 0, + BasisLz = 1, + ZStandard = 2, + ZLib = 3 + } +} diff --git a/BCnEncoder.Net.TextureFormats/Khronos/KtxFile.cs b/BCnEncoder.Net.TextureFormats/Khronos/KtxFile.cs new file mode 100644 index 0000000..9b25212 --- /dev/null +++ b/BCnEncoder.Net.TextureFormats/Khronos/KtxFile.cs @@ -0,0 +1,486 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using BCnEncoder.Shared; + +namespace BCnEncoder.TextureFormats +{ + /// + /// A representation of a ktx file. + /// This class handles loading and saving ktx files into streams. + /// The full spec can be found here: https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ + /// + public class KtxFile : ITextureFileFormat + { + public KtxHeader header; + public List KeyValuePairs { get; } = new List(); + public List MipMaps { get; } = new List(); + + /// + public bool SupportsLdr => true; + + /// + public bool SupportsHdr => true; + + /// + public bool SupportsCubeMap => true; + + /// + public bool SupportsMipMaps => true; + + /// + public bool SupportsArrays => true; + + /// + public bool IsSupportedFormat(CompressionFormat format) + { + var (_, internalFormat, _) = format.GetGlFormat(); + return internalFormat != 0; + } + + /// + public void FromTextureData(BCnTextureData textureData) + { + if (!IsSupportedFormat(textureData.Format)) + { + throw new ArgumentException($"Unsupported format {textureData.Format} in textureData.", nameof(textureData)); + } + + var (glFormat, internalFormat, glType) = textureData.Format.GetGlFormat(); + + if (textureData.Format.IsBlockCompressedFormat()) + { + header = KtxHeader.InitializeCompressed(textureData.Width, textureData.Height, internalFormat, glFormat); + } + else + { + header = KtxHeader.InitializeUncompressed( + textureData.Width, + textureData.Height, + glType, + glFormat, + glType.GetTypeSize(), + internalFormat, + glFormat); + } + + header.NumberOfMipmapLevels = (uint)textureData.NumMips; + header.NumberOfFaces = (uint)textureData.NumFaces; + header.NumberOfArrayElements = textureData.IsArrayTexture ? (uint)textureData.NumArrayElements : 0; + + MipMaps.Clear(); + + for (var m = 0; m < textureData.NumMips; m++) + { + MipMaps.Add(new KtxMipmap( + (uint)textureData.Mips[m].SizeInBytes, + (uint)textureData.Mips[m].Width, + (uint)textureData.Mips[m].Height, + (uint)textureData.NumArrayElements, + (uint)textureData.NumFaces)); + + for (var a = 0; a < textureData.NumArrayElements; a++) + { + for (var f = 0; f < textureData.NumFaces; f++) + { + var index = a * textureData.NumFaces + f; + var face = (CubeMapFaceDirection)f; + + var data = textureData.Mips[m][face, a].Data; + if (!textureData.IsBlockCompressed) + { + data = TextureUnPacker.UnPackTexture( + data, + textureData.Mips[m].Width, + textureData.Mips[m].Height, + textureData.Format, + 4); + } + + MipMaps[m].SizeInBytes = (uint)data.Length; + MipMaps[m].SurfaceArray[index] = new KtxMipSurface( + data, + (uint)textureData.Mips[m].Width, + (uint)textureData.Mips[m].Height); + } + } + } + + } + + /// + public BCnTextureData ToTextureData() + { + var format = header.GlInternalFormat.GetCompressionFormat(header.GlFormat, header.GlType); + var textureData = new BCnTextureData( + format, + (int)header.PixelWidth, + (int)header.PixelHeight, + depth: 1, + numMips:(int)header.NumberOfMipmapLevels, + numArrayElements: (int) header.NumberOfArrayElements, + isCubeMap: header.NumberOfFaces > 1, + allocateBuffers: false, + alphaChannelHint: AlphaChannelHint.Premultiplied); + + for (var m = 0; m < textureData.NumMips; m++) + { + for (var a = 0; a < textureData.NumArrayElements; a++) + { + for (var f = 0; f < textureData.NumFaces; f++) + { + var index = a * textureData.NumFaces + f; + var face = (CubeMapFaceDirection)f; + + var data = MipMaps[m].SurfaceArray[index].Data; + + if (!textureData.IsBlockCompressed) + { + data = TextureUnPacker.PackTexture( + data, + textureData.Mips[m].Width, + textureData.Mips[m].Height, + textureData.Format, + 4); + } + + if (data.Length != textureData.Mips[m].SizeInBytes) + { + throw new TextureFormatException("KtxFile mipmap size different from expected!"); + } + + textureData.Mips[m][face, a].Data = data; + } + } + } + + return textureData; + } + + /// + public void ReadFromStream(Stream inputStream) + { + KeyValuePairs.Clear(); + MipMaps.Clear(); + + using (var br = new BinaryReader(inputStream, Encoding.UTF8, true)) + { + header = br.ReadStruct(); + + var keyValuePairBytesRead = 0; + while (keyValuePairBytesRead < header.BytesOfKeyValueData) + { + var kvp = KtxKeyValuePair.ReadKeyValuePair(br, out var read); + keyValuePairBytesRead += read; + KeyValuePairs.Add(kvp); + } + + var numberOfFaces = Math.Max(1, header.NumberOfFaces); + var numberOfArrayElements = Math.Max(1, header.NumberOfArrayElements); + + var totalAmountOfFaces = numberOfFaces * numberOfArrayElements; + + MipMaps.Capacity = (int)header.NumberOfMipmapLevels; + for (uint mipLevel = 0; mipLevel < header.NumberOfMipmapLevels; mipLevel++) + { + var isNonArrayCubemap = header.NumberOfFaces > 1 && header.NumberOfArrayElements == 0; + + var imageSize = br.ReadUInt32(); + var mipWidth = header.PixelWidth / (uint)Math.Pow(2, mipLevel); + var mipHeight = header.PixelHeight / (uint)Math.Pow(2, mipLevel); + + var faceSize = isNonArrayCubemap ? imageSize : imageSize / totalAmountOfFaces; + + MipMaps.Add(new KtxMipmap(faceSize, mipWidth, mipHeight, numberOfArrayElements, numberOfFaces)); + + for (uint face = 0; face < totalAmountOfFaces; face++) + { + var faceData = br.ReadBytes((int)faceSize); + MipMaps[(int)mipLevel].SurfaceArray[(int)face] = new KtxMipSurface(faceData, mipWidth, mipHeight); + if (isNonArrayCubemap) + { + var cubePadding = 3 - (imageSize + 3) % 4; + br.SkipPadding(cubePadding); + } + } + + var mipPaddingBytes = 3 - (imageSize + 3) % 4; + br.SkipPadding(mipPaddingBytes); + } + } + } + + /// + public void WriteToStream(Stream outputStream) + { + if (MipMaps.Count < 1 || MipMaps[0].NumberOfFaces < 1) + { + throw new InvalidOperationException("The KTX structure should have at least 1 mipmap level and 1 Face before writing to file."); + } + + using (var bw = new BinaryWriter(outputStream, Encoding.UTF8, true)) + { + var bytesOfKeyValueData = (uint)KeyValuePairs.Sum(x => x.GetSizeWithPadding()); + + header.BytesOfKeyValueData = bytesOfKeyValueData; + header.NumberOfFaces = MipMaps[0].NumberOfFaces; + header.NumberOfMipmapLevels = (uint)MipMaps.Count; + header.NumberOfArrayElements = MipMaps[0].NumberOfArrayElements > 1 ? + MipMaps[0].NumberOfArrayElements : 0; + + if (!header.VerifyHeader()) + { + throw new InvalidOperationException("Please verify the header validity before writing to file."); + } + + bw.WriteStruct(header); + + foreach (var keyValuePair in KeyValuePairs) + { + KtxKeyValuePair.WriteKeyValuePair(bw, keyValuePair); + } + + for (var mip = 0; mip < header.NumberOfMipmapLevels; mip++) + { + var imageSize = MipMaps[mip].SizeInBytes; + var isNonArrayCubemap = header.NumberOfFaces == 6 && header.NumberOfArrayElements == 0; + var numArrayElements = Math.Max(header.NumberOfArrayElements, 1); + + // From https://registry.khronos.org/KTX/specs/1.0/ktxspec.v1.html + // + // For most textures imageSize is the number of bytes of pixel data in the current LOD level. + // This includes all array layers, all z slices, all faces, all rows and all pixelsin each row for the mipmap level. + // It does not include any bytes in mipPadding. + // + // The exception is non-array cubemap textures + // (any texture where numberOfFaces is 6 and numberOfArrayElements is 0). + // For these textures imageSize is the number of bytes in each face of the texture for the current LOD level, + // not including bytes in cubePadding or mipPadding. + + if (isNonArrayCubemap) + { + imageSize *= numArrayElements * header.NumberOfFaces; + } + + bw.Write(imageSize); + + for (var f = 0; f < header.NumberOfFaces * numArrayElements; f++) + { + bw.Write(MipMaps[mip].SurfaceArray[f].Data); + + if (isNonArrayCubemap) + { + var cubePadding = 3 - (imageSize + 3) % 4; + bw.AddPadding(cubePadding); + } + } + + if (mip < header.NumberOfMipmapLevels - 1) + { + var mipPaddingBytes = 3 - (imageSize + 3) % 4; + bw.AddPadding(mipPaddingBytes); + } + } + } + } + + public static KtxFile Load(Stream stream) + { + var tex = new KtxFile(); + tex.ReadFromStream(stream); + return tex; + } + } + + + public class KtxKeyValuePair + { + public string Key { get; } + public byte[] Value { get; } + public KtxKeyValuePair(string key, byte[] value) + { + Key = key; + Value = value; + } + + public uint GetSizeWithPadding() + { + var keySpanLength = Encoding.UTF8.GetByteCount(Key); + var totalSize = (uint)(keySpanLength + 1 + Value.Length); + var paddingBytes = (int)(3 - (totalSize + 3) % 4); + + return (uint)(totalSize + paddingBytes); + } + + public static KtxKeyValuePair ReadKeyValuePair(BinaryReader br, out int bytesRead) + { + var totalSize = br.ReadUInt32(); + Span keyValueBytes = stackalloc byte[(int)totalSize]; + br.Read(keyValueBytes); + + // Find the key's null terminator + int i; + for (i = 0; i < totalSize; i++) + { + if (keyValueBytes[i] == 0) + { + break; + } + + if (i >= totalSize) + { + throw new InvalidDataException(); + } + } + + + var keySize = i; + var key = Encoding.UTF8.GetString(keyValueBytes.Slice(0, keySize)); + + var valueSize = (int)(totalSize - keySize - 1); + var valueBytes = keyValueBytes.Slice(i + 1, valueSize); + var value = new byte[valueSize]; + valueBytes.CopyTo(value); + + var paddingBytes = (int)(3 - (totalSize + 3) % 4); + br.SkipPadding(paddingBytes); + + bytesRead = (int)(totalSize + paddingBytes + sizeof(uint)); + return new KtxKeyValuePair(key, value); + } + + public static uint WriteKeyValuePair(BinaryWriter bw, KtxKeyValuePair pair) + { + var keySpanLength = Encoding.UTF8.GetByteCount(pair.Key); + Span keySpan = stackalloc byte[keySpanLength]; + Span valueSpan = pair.Value; + + var totalSize = (uint)(keySpan.Length + 1 + valueSpan.Length); + var paddingBytes = (int)(3 - (totalSize + 3) % 4); + + bw.Write(totalSize); + bw.Write(keySpan); + bw.Write((byte)0); + bw.Write(valueSpan); + + return (uint)(totalSize + paddingBytes); + } + } + + [StructLayout(LayoutKind.Sequential)] + public unsafe struct KtxHeader + { + public fixed byte Identifier[12]; + public uint Endianness; + public GlType GlType; + public uint GlTypeSize; + public GlFormat GlFormat; + public GlInternalFormat GlInternalFormat; + public GlFormat GlBaseInternalFormat; + public uint PixelWidth; + public uint PixelHeight; + public uint PixelDepth; + public uint NumberOfArrayElements; + public uint NumberOfFaces; + public uint NumberOfMipmapLevels; + public uint BytesOfKeyValueData; + + public bool VerifyHeader() + { + Span id = stackalloc byte[] { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; + for (var i = 0; i < id.Length; i++) + { + if (Identifier[i] != id[i]) return false; + } + return true; + } + + public static KtxHeader InitializeCompressed(int width, int height, GlInternalFormat internalFormat, GlFormat baseInternalFormat) + { + var header = new KtxHeader(); + Span id = stackalloc byte[] { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; + for (var i = 0; i < id.Length; i++) + { + header.Identifier[i] = id[i]; + } + header.Endianness = 0x04030201; + header.PixelWidth = (uint)width; + header.PixelHeight = (uint)height; + header.GlType = 0; + header.GlTypeSize = 1; + header.GlFormat = 0; + header.GlInternalFormat = internalFormat; + header.GlBaseInternalFormat = baseInternalFormat; + + return header; + } + + public static KtxHeader InitializeUncompressed(int width, int height, GlType type, GlFormat format, uint glTypeSize, GlInternalFormat internalFormat, GlFormat baseInternalFormat) + { + var header = new KtxHeader(); + Span id = stackalloc byte[] { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; + for (var i = 0; i < id.Length; i++) + { + header.Identifier[i] = id[i]; + } + header.Endianness = 0x04030201; + header.PixelWidth = (uint)width; + header.PixelHeight = (uint)height; + header.GlType = type; + header.GlTypeSize = glTypeSize; + header.GlFormat = format; + header.GlInternalFormat = internalFormat; + header.GlBaseInternalFormat = baseInternalFormat; + + return header; + } + } + + public class KtxMipSurface + { + public uint Width { get; set; } + public uint Height { get; set; } + public uint SizeInBytes { get; set; } + public byte[] Data { get; set; } + + public KtxMipSurface(byte[] data, uint width, uint height) + { + Width = width; + Height = height; + SizeInBytes = (uint)data.Length; + Data = data; + } + } + + public class KtxMipmap + { + /// + /// Size of a single face in bytes. + /// + public uint SizeInBytes { get; set; } + public uint Width { get; init; } + public uint Height { get; init; } + public uint NumberOfFaces { get; init; } + public uint NumberOfArrayElements { get; init; } + + /// + /// Length will be Number of Faces * Number of Array Elements + /// Index with (arrayIndex * NumFaces + faceIndex) + /// + public KtxMipSurface[] SurfaceArray { get; init; } + + + public KtxMipmap(uint sizeInBytes, uint width, uint height, uint numArrayElements, uint numberOfFaces) + { + SizeInBytes = sizeInBytes; + Width = Math.Max(1, width); + Height = Math.Max(1, height); + NumberOfArrayElements = Math.Max(1, numArrayElements); + NumberOfFaces = Math.Max(1, numberOfFaces); + SurfaceArray = new KtxMipSurface[numArrayElements * numberOfFaces]; + } + } +} diff --git a/BCnEncoder.Net.TextureFormats/Khronos/VkFormat.cs b/BCnEncoder.Net.TextureFormats/Khronos/VkFormat.cs new file mode 100644 index 0000000..537e11e --- /dev/null +++ b/BCnEncoder.Net.TextureFormats/Khronos/VkFormat.cs @@ -0,0 +1,408 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BCnEncoder.TextureFormats +{ + // From https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#formats-definition + public enum VkFormat : UInt32 + { + VkFormatUndefined = 0, + VkFormatR4G4UnormPack8 = 1, + VkFormatR4G4B4A4UnormPack16 = 2, + VkFormatB4G4R4A4UnormPack16 = 3, + VkFormatR5G6B5UnormPack16 = 4, + VkFormatB5G6R5UnormPack16 = 5, + VkFormatR5G5B5A1UnormPack16 = 6, + VkFormatB5G5R5A1UnormPack16 = 7, + VkFormatA1R5G5B5UnormPack16 = 8, + VkFormatR8Unorm = 9, + VkFormatR8Snorm = 10, + VkFormatR8Uscaled = 11, + VkFormatR8Sscaled = 12, + VkFormatR8Uint = 13, + VkFormatR8Sint = 14, + VkFormatR8Srgb = 15, + VkFormatR8G8Unorm = 16, + VkFormatR8G8Snorm = 17, + VkFormatR8G8Uscaled = 18, + VkFormatR8G8Sscaled = 19, + VkFormatR8G8Uint = 20, + VkFormatR8G8Sint = 21, + VkFormatR8G8Srgb = 22, + VkFormatR8G8B8Unorm = 23, + VkFormatR8G8B8Snorm = 24, + VkFormatR8G8B8Uscaled = 25, + VkFormatR8G8B8Sscaled = 26, + VkFormatR8G8B8Uint = 27, + VkFormatR8G8B8Sint = 28, + VkFormatR8G8B8Srgb = 29, + VkFormatB8G8R8Unorm = 30, + VkFormatB8G8R8Snorm = 31, + VkFormatB8G8R8Uscaled = 32, + VkFormatB8G8R8Sscaled = 33, + VkFormatB8G8R8Uint = 34, + VkFormatB8G8R8Sint = 35, + VkFormatB8G8R8Srgb = 36, + VkFormatR8G8B8A8Unorm = 37, + VkFormatR8G8B8A8Snorm = 38, + VkFormatR8G8B8A8Uscaled = 39, + VkFormatR8G8B8A8Sscaled = 40, + VkFormatR8G8B8A8Uint = 41, + VkFormatR8G8B8A8Sint = 42, + VkFormatR8G8B8A8Srgb = 43, + VkFormatB8G8R8A8Unorm = 44, + VkFormatB8G8R8A8Snorm = 45, + VkFormatB8G8R8A8Uscaled = 46, + VkFormatB8G8R8A8Sscaled = 47, + VkFormatB8G8R8A8Uint = 48, + VkFormatB8G8R8A8Sint = 49, + VkFormatB8G8R8A8Srgb = 50, + VkFormatA8B8G8R8UnormPack32 = 51, + VkFormatA8B8G8R8SnormPack32 = 52, + VkFormatA8B8G8R8UscaledPack32 = 53, + VkFormatA8B8G8R8SscaledPack32 = 54, + VkFormatA8B8G8R8UintPack32 = 55, + VkFormatA8B8G8R8SintPack32 = 56, + VkFormatA8B8G8R8SrgbPack32 = 57, + VkFormatA2R10G10B10UnormPack32 = 58, + VkFormatA2R10G10B10SnormPack32 = 59, + VkFormatA2R10G10B10UscaledPack32 = 60, + VkFormatA2R10G10B10SscaledPack32 = 61, + VkFormatA2R10G10B10UintPack32 = 62, + VkFormatA2R10G10B10SintPack32 = 63, + VkFormatA2B10G10R10UnormPack32 = 64, + VkFormatA2B10G10R10SnormPack32 = 65, + VkFormatA2B10G10R10UscaledPack32 = 66, + VkFormatA2B10G10R10SscaledPack32 = 67, + VkFormatA2B10G10R10UintPack32 = 68, + VkFormatA2B10G10R10SintPack32 = 69, + VkFormatR16Unorm = 70, + VkFormatR16Snorm = 71, + VkFormatR16Uscaled = 72, + VkFormatR16Sscaled = 73, + VkFormatR16Uint = 74, + VkFormatR16Sint = 75, + VkFormatR16Sfloat = 76, + VkFormatR16G16Unorm = 77, + VkFormatR16G16Snorm = 78, + VkFormatR16G16Uscaled = 79, + VkFormatR16G16Sscaled = 80, + VkFormatR16G16Uint = 81, + VkFormatR16G16Sint = 82, + VkFormatR16G16Sfloat = 83, + VkFormatR16G16B16Unorm = 84, + VkFormatR16G16B16Snorm = 85, + VkFormatR16G16B16Uscaled = 86, + VkFormatR16G16B16Sscaled = 87, + VkFormatR16G16B16Uint = 88, + VkFormatR16G16B16Sint = 89, + VkFormatR16G16B16Sfloat = 90, + VkFormatR16G16B16A16Unorm = 91, + VkFormatR16G16B16A16Snorm = 92, + VkFormatR16G16B16A16Uscaled = 93, + VkFormatR16G16B16A16Sscaled = 94, + VkFormatR16G16B16A16Uint = 95, + VkFormatR16G16B16A16Sint = 96, + VkFormatR16G16B16A16Sfloat = 97, + VkFormatR32Uint = 98, + VkFormatR32Sint = 99, + VkFormatR32Sfloat = 100, + VkFormatR32G32Uint = 101, + VkFormatR32G32Sint = 102, + VkFormatR32G32Sfloat = 103, + VkFormatR32G32B32Uint = 104, + VkFormatR32G32B32Sint = 105, + VkFormatR32G32B32Sfloat = 106, + VkFormatR32G32B32A32Uint = 107, + VkFormatR32G32B32A32Sint = 108, + VkFormatR32G32B32A32Sfloat = 109, + VkFormatR64Uint = 110, + VkFormatR64Sint = 111, + VkFormatR64Sfloat = 112, + VkFormatR64G64Uint = 113, + VkFormatR64G64Sint = 114, + VkFormatR64G64Sfloat = 115, + VkFormatR64G64B64Uint = 116, + VkFormatR64G64B64Sint = 117, + VkFormatR64G64B64Sfloat = 118, + VkFormatR64G64B64A64Uint = 119, + VkFormatR64G64B64A64Sint = 120, + VkFormatR64G64B64A64Sfloat = 121, + VkFormatB10G11R11UfloatPack32 = 122, + VkFormatE5B9G9R9UfloatPack32 = 123, + VkFormatD16Unorm = 124, + VkFormatX8D24UnormPack32 = 125, + VkFormatD32Sfloat = 126, + VkFormatS8Uint = 127, + VkFormatD16UnormS8Uint = 128, + VkFormatD24UnormS8Uint = 129, + VkFormatD32SfloatS8Uint = 130, + VkFormatBc1RgbUnormBlock = 131, + VkFormatBc1RgbSrgbBlock = 132, + VkFormatBc1RgbaUnormBlock = 133, + VkFormatBc1RgbaSrgbBlock = 134, + VkFormatBc2UnormBlock = 135, + VkFormatBc2SrgbBlock = 136, + VkFormatBc3UnormBlock = 137, + VkFormatBc3SrgbBlock = 138, + VkFormatBc4UnormBlock = 139, + VkFormatBc4SnormBlock = 140, + VkFormatBc5UnormBlock = 141, + VkFormatBc5SnormBlock = 142, + VkFormatBc6HUfloatBlock = 143, + VkFormatBc6HSfloatBlock = 144, + VkFormatBc7UnormBlock = 145, + VkFormatBc7SrgbBlock = 146, + VkFormatEtc2R8G8B8UnormBlock = 147, + VkFormatEtc2R8G8B8SrgbBlock = 148, + VkFormatEtc2R8G8B8A1UnormBlock = 149, + VkFormatEtc2R8G8B8A1SrgbBlock = 150, + VkFormatEtc2R8G8B8A8UnormBlock = 151, + VkFormatEtc2R8G8B8A8SrgbBlock = 152, + VkFormatEacR11UnormBlock = 153, + VkFormatEacR11SnormBlock = 154, + VkFormatEacR11G11UnormBlock = 155, + VkFormatEacR11G11SnormBlock = 156, + VkFormatAstc4X4UnormBlock = 157, + VkFormatAstc4X4SrgbBlock = 158, + VkFormatAstc5X4UnormBlock = 159, + VkFormatAstc5X4SrgbBlock = 160, + VkFormatAstc5X5UnormBlock = 161, + VkFormatAstc5X5SrgbBlock = 162, + VkFormatAstc6X5UnormBlock = 163, + VkFormatAstc6X5SrgbBlock = 164, + VkFormatAstc6X6UnormBlock = 165, + VkFormatAstc6X6SrgbBlock = 166, + VkFormatAstc8X5UnormBlock = 167, + VkFormatAstc8X5SrgbBlock = 168, + VkFormatAstc8X6UnormBlock = 169, + VkFormatAstc8X6SrgbBlock = 170, + VkFormatAstc8X8UnormBlock = 171, + VkFormatAstc8X8SrgbBlock = 172, + VkFormatAstc10X5UnormBlock = 173, + VkFormatAstc10X5SrgbBlock = 174, + VkFormatAstc10X6UnormBlock = 175, + VkFormatAstc10X6SrgbBlock = 176, + VkFormatAstc10X8UnormBlock = 177, + VkFormatAstc10X8SrgbBlock = 178, + VkFormatAstc10X10UnormBlock = 179, + VkFormatAstc10X10SrgbBlock = 180, + VkFormatAstc12X10UnormBlock = 181, + VkFormatAstc12X10SrgbBlock = 182, + VkFormatAstc12X12UnormBlock = 183, + VkFormatAstc12X12SrgbBlock = 184, + // Provided by VK_VERSION_1_1 + VkFormatG8B8G8R8422Unorm = 1000156000, + // Provided by VK_VERSION_1_1 + VkFormatB8G8R8G8422Unorm = 1000156001, + // Provided by VK_VERSION_1_1 + VkFormatG8B8R83Plane420Unorm = 1000156002, + // Provided by VK_VERSION_1_1 + VkFormatG8B8R82Plane420Unorm = 1000156003, + // Provided by VK_VERSION_1_1 + VkFormatG8B8R83Plane422Unorm = 1000156004, + // Provided by VK_VERSION_1_1 + VkFormatG8B8R82Plane422Unorm = 1000156005, + // Provided by VK_VERSION_1_1 + VkFormatG8B8R83Plane444Unorm = 1000156006, + // Provided by VK_VERSION_1_1 + VkFormatR10X6UnormPack16 = 1000156007, + // Provided by VK_VERSION_1_1 + VkFormatR10X6G10X6Unorm2Pack16 = 1000156008, + // Provided by VK_VERSION_1_1 + VkFormatR10X6G10X6B10X6A10X6Unorm4Pack16 = 1000156009, + // Provided by VK_VERSION_1_1 + VkFormatG10X6B10X6G10X6R10X6422Unorm4Pack16 = 1000156010, + // Provided by VK_VERSION_1_1 + VkFormatB10X6G10X6R10X6G10X6422Unorm4Pack16 = 1000156011, + // Provided by VK_VERSION_1_1 + VkFormatG10X6B10X6R10X63Plane420Unorm3Pack16 = 1000156012, + // Provided by VK_VERSION_1_1 + VkFormatG10X6B10X6R10X62Plane420Unorm3Pack16 = 1000156013, + // Provided by VK_VERSION_1_1 + VkFormatG10X6B10X6R10X63Plane422Unorm3Pack16 = 1000156014, + // Provided by VK_VERSION_1_1 + VkFormatG10X6B10X6R10X62Plane422Unorm3Pack16 = 1000156015, + // Provided by VK_VERSION_1_1 + VkFormatG10X6B10X6R10X63Plane444Unorm3Pack16 = 1000156016, + // Provided by VK_VERSION_1_1 + VkFormatR12X4UnormPack16 = 1000156017, + // Provided by VK_VERSION_1_1 + VkFormatR12X4G12X4Unorm2Pack16 = 1000156018, + // Provided by VK_VERSION_1_1 + VkFormatR12X4G12X4B12X4A12X4Unorm4Pack16 = 1000156019, + // Provided by VK_VERSION_1_1 + VkFormatG12X4B12X4G12X4R12X4422Unorm4Pack16 = 1000156020, + // Provided by VK_VERSION_1_1 + VkFormatB12X4G12X4R12X4G12X4422Unorm4Pack16 = 1000156021, + // Provided by VK_VERSION_1_1 + VkFormatG12X4B12X4R12X43Plane420Unorm3Pack16 = 1000156022, + // Provided by VK_VERSION_1_1 + VkFormatG12X4B12X4R12X42Plane420Unorm3Pack16 = 1000156023, + // Provided by VK_VERSION_1_1 + VkFormatG12X4B12X4R12X43Plane422Unorm3Pack16 = 1000156024, + // Provided by VK_VERSION_1_1 + VkFormatG12X4B12X4R12X42Plane422Unorm3Pack16 = 1000156025, + // Provided by VK_VERSION_1_1 + VkFormatG12X4B12X4R12X43Plane444Unorm3Pack16 = 1000156026, + // Provided by VK_VERSION_1_1 + VkFormatG16B16G16R16422Unorm = 1000156027, + // Provided by VK_VERSION_1_1 + VkFormatB16G16R16G16422Unorm = 1000156028, + // Provided by VK_VERSION_1_1 + VkFormatG16B16R163Plane420Unorm = 1000156029, + // Provided by VK_VERSION_1_1 + VkFormatG16B16R162Plane420Unorm = 1000156030, + // Provided by VK_VERSION_1_1 + VkFormatG16B16R163Plane422Unorm = 1000156031, + // Provided by VK_VERSION_1_1 + VkFormatG16B16R162Plane422Unorm = 1000156032, + // Provided by VK_VERSION_1_1 + VkFormatG16B16R163Plane444Unorm = 1000156033, + VkFormatG8B8R82Plane444Unorm = 1000330000, + VkFormatG10X6B10X6R10X62Plane444Unorm3Pack16 = 1000330001, + VkFormatG12X4B12X4R12X42Plane444Unorm3Pack16 = 1000330002, + VkFormatG16B16R162Plane444Unorm = 1000330003, + VkFormatA4R4G4B4UnormPack16 = 1000340000, + VkFormatA4B4G4R4UnormPack16 = 1000340001, + VkFormatAstc4X4SfloatBlock = 1000066000, + VkFormatAstc5X4SfloatBlock = 1000066001, + VkFormatAstc5X5SfloatBlock = 1000066002, + VkFormatAstc6X5SfloatBlock = 1000066003, + VkFormatAstc6X6SfloatBlock = 1000066004, + VkFormatAstc8X5SfloatBlock = 1000066005, + VkFormatAstc8X6SfloatBlock = 1000066006, + VkFormatAstc8X8SfloatBlock = 1000066007, + VkFormatAstc10X5SfloatBlock = 1000066008, + VkFormatAstc10X6SfloatBlock = 1000066009, + VkFormatAstc10X8SfloatBlock = 1000066010, + VkFormatAstc10X10SfloatBlock = 1000066011, + VkFormatAstc12X10SfloatBlock = 1000066012, + VkFormatAstc12X12SfloatBlock = 1000066013, + // Provided by VK_IMG_format_pvrtc + VkFormatPvrtc12BppUnormBlockImg = 1000054000, + // Provided by VK_IMG_format_pvrtc + VkFormatPvrtc14BppUnormBlockImg = 1000054001, + // Provided by VK_IMG_format_pvrtc + VkFormatPvrtc22BppUnormBlockImg = 1000054002, + // Provided by VK_IMG_format_pvrtc + VkFormatPvrtc24BppUnormBlockImg = 1000054003, + // Provided by VK_IMG_format_pvrtc + VkFormatPvrtc12BppSrgbBlockImg = 1000054004, + // Provided by VK_IMG_format_pvrtc + VkFormatPvrtc14BppSrgbBlockImg = 1000054005, + // Provided by VK_IMG_format_pvrtc + VkFormatPvrtc22BppSrgbBlockImg = 1000054006, + // Provided by VK_IMG_format_pvrtc + VkFormatPvrtc24BppSrgbBlockImg = 1000054007, + // Provided by VK_EXT_texture_compression_astc_hdr + VkFormatAstc4X4SfloatBlockExt = VkFormatAstc4X4SfloatBlock, + // Provided by VK_EXT_texture_compression_astc_hdr + VkFormatAstc5X4SfloatBlockExt = VkFormatAstc5X4SfloatBlock, + // Provided by VK_EXT_texture_compression_astc_hdr + VkFormatAstc5X5SfloatBlockExt = VkFormatAstc5X5SfloatBlock, + // Provided by VK_EXT_texture_compression_astc_hdr + VkFormatAstc6X5SfloatBlockExt = VkFormatAstc6X5SfloatBlock, + // Provided by VK_EXT_texture_compression_astc_hdr + VkFormatAstc6X6SfloatBlockExt = VkFormatAstc6X6SfloatBlock, + // Provided by VK_EXT_texture_compression_astc_hdr + VkFormatAstc8X5SfloatBlockExt = VkFormatAstc8X5SfloatBlock, + // Provided by VK_EXT_texture_compression_astc_hdr + VkFormatAstc8X6SfloatBlockExt = VkFormatAstc8X6SfloatBlock, + // Provided by VK_EXT_texture_compression_astc_hdr + VkFormatAstc8X8SfloatBlockExt = VkFormatAstc8X8SfloatBlock, + // Provided by VK_EXT_texture_compression_astc_hdr + VkFormatAstc10X5SfloatBlockExt = VkFormatAstc10X5SfloatBlock, + // Provided by VK_EXT_texture_compression_astc_hdr + VkFormatAstc10X6SfloatBlockExt = VkFormatAstc10X6SfloatBlock, + // Provided by VK_EXT_texture_compression_astc_hdr + VkFormatAstc10X8SfloatBlockExt = VkFormatAstc10X8SfloatBlock, + // Provided by VK_EXT_texture_compression_astc_hdr + VkFormatAstc10X10SfloatBlockExt = VkFormatAstc10X10SfloatBlock, + // Provided by VK_EXT_texture_compression_astc_hdr + VkFormatAstc12X10SfloatBlockExt = VkFormatAstc12X10SfloatBlock, + // Provided by VK_EXT_texture_compression_astc_hdr + VkFormatAstc12X12SfloatBlockExt = VkFormatAstc12X12SfloatBlock, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG8B8G8R8422UnormKhr = VkFormatG8B8G8R8422Unorm, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatB8G8R8G8422UnormKhr = VkFormatB8G8R8G8422Unorm, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG8B8R83Plane420UnormKhr = VkFormatG8B8R83Plane420Unorm, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG8B8R82Plane420UnormKhr = VkFormatG8B8R82Plane420Unorm, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG8B8R83Plane422UnormKhr = VkFormatG8B8R83Plane422Unorm, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG8B8R82Plane422UnormKhr = VkFormatG8B8R82Plane422Unorm, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG8B8R83Plane444UnormKhr = VkFormatG8B8R83Plane444Unorm, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatR10X6UnormPack16Khr = VkFormatR10X6UnormPack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatR10X6G10X6Unorm2Pack16Khr = VkFormatR10X6G10X6Unorm2Pack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatR10X6G10X6B10X6A10X6Unorm4Pack16Khr = VkFormatR10X6G10X6B10X6A10X6Unorm4Pack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG10X6B10X6G10X6R10X6422Unorm4Pack16Khr = VkFormatG10X6B10X6G10X6R10X6422Unorm4Pack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatB10X6G10X6R10X6G10X6422Unorm4Pack16Khr = VkFormatB10X6G10X6R10X6G10X6422Unorm4Pack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG10X6B10X6R10X63Plane420Unorm3Pack16Khr = VkFormatG10X6B10X6R10X63Plane420Unorm3Pack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG10X6B10X6R10X62Plane420Unorm3Pack16Khr = VkFormatG10X6B10X6R10X62Plane420Unorm3Pack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG10X6B10X6R10X63Plane422Unorm3Pack16Khr = VkFormatG10X6B10X6R10X63Plane422Unorm3Pack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG10X6B10X6R10X62Plane422Unorm3Pack16Khr = VkFormatG10X6B10X6R10X62Plane422Unorm3Pack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG10X6B10X6R10X63Plane444Unorm3Pack16Khr = VkFormatG10X6B10X6R10X63Plane444Unorm3Pack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatR12X4UnormPack16Khr = VkFormatR12X4UnormPack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatR12X4G12X4Unorm2Pack16Khr = VkFormatR12X4G12X4Unorm2Pack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatR12X4G12X4B12X4A12X4Unorm4Pack16Khr = VkFormatR12X4G12X4B12X4A12X4Unorm4Pack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG12X4B12X4G12X4R12X4422Unorm4Pack16Khr = VkFormatG12X4B12X4G12X4R12X4422Unorm4Pack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatB12X4G12X4R12X4G12X4422Unorm4Pack16Khr = VkFormatB12X4G12X4R12X4G12X4422Unorm4Pack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG12X4B12X4R12X43Plane420Unorm3Pack16Khr = VkFormatG12X4B12X4R12X43Plane420Unorm3Pack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG12X4B12X4R12X42Plane420Unorm3Pack16Khr = VkFormatG12X4B12X4R12X42Plane420Unorm3Pack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG12X4B12X4R12X43Plane422Unorm3Pack16Khr = VkFormatG12X4B12X4R12X43Plane422Unorm3Pack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG12X4B12X4R12X42Plane422Unorm3Pack16Khr = VkFormatG12X4B12X4R12X42Plane422Unorm3Pack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG12X4B12X4R12X43Plane444Unorm3Pack16Khr = VkFormatG12X4B12X4R12X43Plane444Unorm3Pack16, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG16B16G16R16422UnormKhr = VkFormatG16B16G16R16422Unorm, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatB16G16R16G16422UnormKhr = VkFormatB16G16R16G16422Unorm, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG16B16R163Plane420UnormKhr = VkFormatG16B16R163Plane420Unorm, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG16B16R162Plane420UnormKhr = VkFormatG16B16R162Plane420Unorm, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG16B16R163Plane422UnormKhr = VkFormatG16B16R163Plane422Unorm, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG16B16R162Plane422UnormKhr = VkFormatG16B16R162Plane422Unorm, + // Provided by VK_KHR_sampler_ycbcr_conversion + VkFormatG16B16R163Plane444UnormKhr = VkFormatG16B16R163Plane444Unorm, + // Provided by VK_EXT_ycbcr_2plane_444_formats + VkFormatG8B8R82Plane444UnormExt = VkFormatG8B8R82Plane444Unorm, + // Provided by VK_EXT_ycbcr_2plane_444_formats + VkFormatG10X6B10X6R10X62Plane444Unorm3Pack16Ext = VkFormatG10X6B10X6R10X62Plane444Unorm3Pack16, + // Provided by VK_EXT_ycbcr_2plane_444_formats + VkFormatG12X4B12X4R12X42Plane444Unorm3Pack16Ext = VkFormatG12X4B12X4R12X42Plane444Unorm3Pack16, + // Provided by VK_EXT_ycbcr_2plane_444_formats + VkFormatG16B16R162Plane444UnormExt = VkFormatG16B16R162Plane444Unorm, + // Provided by VK_EXT_4444_formats + VkFormatA4R4G4B4UnormPack16Ext = VkFormatA4R4G4B4UnormPack16, + // Provided by VK_EXT_4444_formats + VkFormatA4B4G4R4UnormPack16Ext = VkFormatA4B4G4R4UnormPack16, + } +} diff --git a/BCnEncoder.Net.TextureFormats/RadianceFile.cs b/BCnEncoder.Net.TextureFormats/RadianceFile.cs new file mode 100644 index 0000000..640ddd4 --- /dev/null +++ b/BCnEncoder.Net.TextureFormats/RadianceFile.cs @@ -0,0 +1,316 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using BCnEncoder.Shared; + +namespace BCnEncoder.TextureFormats +{ + public class RadianceFile : ITextureFileFormat + { + public enum ColorSpace + { + Rgbe, + Xyze + } + + public float exposure = -1; + public float gamma = -1; + public int width; + public int height; + public ColorSpace colorSpace; + + public byte[]? pixelData; + + + /// + public bool SupportsLdr => false; + + /// + public bool SupportsHdr => true; + + /// + public bool SupportsCubeMap => false; + + /// + public bool SupportsMipMaps => false; + + /// + public bool SupportsArrays => false; + + /// + public bool IsSupportedFormat(CompressionFormat format) + { + return format == CompressionFormat.Rgbe32 || format == CompressionFormat.Xyze32; + } + + /// + public void FromTextureData(BCnTextureData textureData) + { + if (!IsSupportedFormat(textureData.Format)) + { + textureData = textureData.ConvertTo(CompressionFormat.Rgbe32); + } + + width = textureData.Width; + height = textureData.Height; + colorSpace = textureData.Format == CompressionFormat.Rgbe32 ? ColorSpace.Rgbe : ColorSpace.Xyze; + pixelData = textureData.First.Data; + exposure = 1; + gamma = 1; + } + + /// + public BCnTextureData ToTextureData() + { + return BCnTextureData.FromSingle( + colorSpace == ColorSpace.Rgbe ? CompressionFormat.Rgbe32 : CompressionFormat.Xyze32, + width, + height, + pixelData, + AlphaChannelHint.Straight); + } + + + // StreamReader class does not work. Have to use custom string reading. + private static string ReadLineFromStream(Stream stream) + { + var i = 0; + var buffer = new char[512]; + char c; + do + { + var b = stream.ReadByte(); + if (b == -1) + { + return null; + } + c = (char)b; + buffer[i++] = c; + } while (c != (char)10); + return new string(buffer.AsSpan().Slice(0, i)).Trim(); + } + + private static void WriteLineToStream(BinaryWriter br, string s) + { + foreach (var c in s) + { + var b = (byte)c; + br.Write(b); + } + br.Write((byte)10); + } + + + /// + public void ReadFromStream(Stream stream) + { + + var line = ReadLineFromStream(stream); + + if (!(line == "#?RGBE" || line == "#?RADIANCE" || line == "#?AUTOPANO")) + { + throw new FileLoadException("Correct file type specifier was not found."); + } + + colorSpace = ColorSpace.Rgbe; + + do + { + line = ReadLineFromStream(stream); + + if (line == null) + { + throw new FileLoadException("Reached end of stream."); + } + + line = line.Trim(); + + if (line == "") + { + break; + } + + if (line.StartsWith("#")) // Found comment + { + continue; + } + + if (line == "FORMAT=32-bit_rle_rgbe") + { + colorSpace = ColorSpace.Rgbe; + } + + else if (line == "FORMAT=32-bit_rle_xyze") + { + colorSpace = ColorSpace.Xyze; + } + + else if (line.StartsWith("EXPOSURE=")) + { + exposure = float.Parse(line.Replace("EXPOSURE=", "").Trim(), CultureInfo.InvariantCulture); + } + + else if (line.StartsWith("GAMMA=")) + { + gamma = float.Parse(line.Replace("GAMMA=", "").Trim(), CultureInfo.InvariantCulture); + } + + } while (true); + + if (exposure < 0.000001) + { + exposure = 1.0f; + } + + if (gamma < 0.000001) + { + gamma = 1.0f; + } + + var imgSize = Regex.Replace(ReadLineFromStream(stream), @"\s+", " ").Split(' '); + var yStr = imgSize[0]; + height = int.Parse(imgSize[1]); + var xStr = imgSize[2]; + width = int.Parse(imgSize[3]); + + pixelData = new byte[width * height * 4]; + + ReadPixels(stream); + } + + private static void RleReadChannel(BinaryReader br, Span dest, int width) + { + var i = 0; + var data = new byte[2]; + while (i < width) + { + if (br.Read(data) == 0) + { + throw new FileLoadException("Not enough data in RLE"); + } + if (data[0] > 128) + { + // same byte is repeated many times + var len = data[0] - 128; + for (; len > 0; len--) + { + dest[i++] = data[1]; + } + } + else + { + // different byte sequence + dest[i++] = data[1]; + + var len = data[0] - 1; + if (len > 0) + { + if (br.Read(dest.Slice(i, len)) == 0) + { + throw new FileLoadException("Not enough data in RLE"); + } + i += len; + } + } + } + + if (i != width) + { + throw new FileLoadException("Scanline size was different from width"); + } + } + + private void ReadPixels(Stream stream) + { + pixelData = new byte[width * height * 4]; + + Span lineBytes = new byte[width * 4]; + + using var br = new BinaryReader(stream, Encoding.ASCII, true); + + var header = new byte[4]; + + for (var y = 0; y < height; y++) + { + br.Read(header); + + var isRle = header[0] == 2 && header[1] == 2 && + (header[2] << 8) + header[3] == width; // whether the scanline is rle or not + + if (isRle) + { + // for each channel + for (var i = 0; i < 4; i++) + { + RleReadChannel(br, lineBytes.Slice(width * i, width), width); + } + + for (var x = 0; x < width; x++) + { + pixelData[y * width * 4 + x * 4 + 0] = lineBytes[x + width * 0]; + pixelData[y * width * 4 + x * 4 + 1] = lineBytes[x + width * 1]; + pixelData[y * width * 4 + x * 4 + 2] = lineBytes[x + width * 2]; + pixelData[y * width * 4 + x * 4 + 3] = lineBytes[x + width * 3]; + } + } + else + { + br.Read(lineBytes.Slice(4)); + header.CopyTo(lineBytes); + + lineBytes.CopyTo(pixelData.AsSpan().Slice(y * width * 4, width * 4)); + } + } + } + + /// + public void WriteToStream(Stream stream) + { + using var br = new BinaryWriter(stream, Encoding.ASCII, true); + + WriteLineToStream(br, "#?RADIANCE"); + WriteLineToStream(br, "# Made with BCnEncoder.Net"); + if (colorSpace == ColorSpace.Rgbe) + { + WriteLineToStream(br, "FORMAT=32-bit_rle_rgbe"); + } + else + { + WriteLineToStream(br, "FORMAT=32-bit_rle_xyze"); + } + if (exposure > 0) + { + WriteLineToStream(br, "EXPOSURE=" + exposure.ToString(CultureInfo.InvariantCulture)); + } + if (gamma > 0) + { + WriteLineToStream(br, "GAMMA=" + gamma.ToString(CultureInfo.InvariantCulture)); + } + + WriteLineToStream(br, ""); // Start data with empty row + WriteLineToStream(br, $"-Y {height} +X {width}"); + + WritePixels(br); + } + + private void WritePixels(BinaryWriter br) + { + if (pixelData == null) + { + throw new NullReferenceException("pixelData is null."); + } + + br.Write(pixelData); + } + + public static RadianceFile Load(Stream stream) + { + var tex = new RadianceFile(); + tex.ReadFromStream(stream); + return tex; + } + } +} diff --git a/BCnEncoder.Net.TextureFormats/TextureExceptions.cs b/BCnEncoder.Net.TextureFormats/TextureExceptions.cs new file mode 100644 index 0000000..72c1adc --- /dev/null +++ b/BCnEncoder.Net.TextureFormats/TextureExceptions.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BCnEncoder.TextureFormats +{ + /// + /// Thrown when something goes wrong with converting to different texture file formats. + /// + public class TextureFormatException : FormatException + { + /// + public TextureFormatException(string message) : base(message) + { + } + } +} diff --git a/BCnEncoder.Net.TextureFormats/TextureUnPacker.cs b/BCnEncoder.Net.TextureFormats/TextureUnPacker.cs new file mode 100644 index 0000000..6ce091a --- /dev/null +++ b/BCnEncoder.Net.TextureFormats/TextureUnPacker.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Text; +using BCnEncoder.Shared; + +namespace BCnEncoder.TextureFormats +{ + internal static class TextureUnPacker + { + public static byte[] UnPackTexture(byte[] texData, int width, int height, CompressionFormat format, int byteAlignment) + { + if (format.IsBlockCompressedFormat()) + { + throw new ArgumentException("Format must be a non-block-compressed format", nameof(format)); + } + + var lineBytes = format.GetBytesPerBlock() * width; + + if (texData.Length != lineBytes * height) + { + throw new ArgumentException("Trying to un-pack a texture with unexpected size!", nameof(texData)); + } + + var alignedLineBytes = ((lineBytes + (byteAlignment - 1)) / byteAlignment) * byteAlignment; + + if (alignedLineBytes == lineBytes) + { + return texData; + } + + var newData = new byte[alignedLineBytes * height]; + for (var y = 0; y < height; y++) + { + var oldLine = texData.AsSpan(y * lineBytes, lineBytes); + var newLine = newData.AsSpan(y * alignedLineBytes, alignedLineBytes); + oldLine.CopyTo(newLine); + } + return newData; + } + + public static byte[] PackTexture(byte[] texData, int width, int height, CompressionFormat format, int byteAlignment) + { + if (format.IsBlockCompressedFormat()) + { + throw new ArgumentException("Format must be a non-block-compressed format", nameof(format)); + } + + var lineBytes = format.GetBytesPerBlock() * width; + var alignedLineBytes = ((lineBytes + (byteAlignment - 1)) / byteAlignment) * byteAlignment; + + if (lineBytes == alignedLineBytes && lineBytes == texData.Length) + { + return texData; + } + + if (texData.Length != alignedLineBytes * height) + { + if (texData.Length == lineBytes * height) // Already packed + { + return texData; + } + + throw new ArgumentException("Trying to pack a texture with unexpected size!", nameof(texData)); + } + + var newData = new byte[lineBytes * height]; + for (var y = 0; y < height; y++) + { + var oldLine = texData.AsSpan(y * alignedLineBytes, lineBytes); + var newLine = newData.AsSpan(y * lineBytes, lineBytes); + oldLine.CopyTo(newLine); + } + return newData; + } + } +}